Skip to content

Commit

Permalink
feature(orm): support Query.use in joins.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcj committed Feb 23, 2023
1 parent e9dce6a commit 2ac4866
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 13 deletions.
2 changes: 1 addition & 1 deletion packages/orm/src/memory-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const find = <T extends OrmEntity>(adapter: MemoryDatabaseAdapter, classSchema:
let filtered = model.filter ? findQueryList<T>(items, model.filter) : items;

if (model.hasJoins()) {
throw new Error('MemoryDatabaseAdapter does not support joins. Please use another lightweight adapter like SQLite.');
console.log('MemoryDatabaseAdapter does not support joins. Please use another lightweight adapter like SQLite.');
}

if (model.sort) {
Expand Down
16 changes: 11 additions & 5 deletions packages/orm/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,23 +222,27 @@ export class BaseQuery<T extends OrmEntity> {
*
* This allows to use more dynamic query composition functions.
*
* To support joins queries `AnyQuery` is necessary as query type.
*
* @example
* ```typescript
* function joinFrontendData(query: Query<Product>) {
* function joinFrontendData(query: AnyQuery<Product>) {
* return query
* .useJoinWith('images').select('sort').end()
* .useJoinWith('brand').select('id', 'name', 'website').end()
* }
*
* const products = await database.query(Product).use(joinFrontendData).find();
* ```
* @reflection never
*/
use<Q, R, A extends any[]>(modifier: (query: Q, ...args: A) => R, ...args: A): R {
return modifier(this as any, ...args);
use<Q, R, A extends any[]>(modifier: (query: Q, ...args: A) => R, ...args: A): this extends JoinDatabaseQuery<any, any> ? this : Exclude<R, JoinDatabaseQuery<any, any>> {
return modifier(this as any, ...args) as any;
}

/**
* Same as `use`, but the method indicates it is terminating the query.
* @reflection never
*/
fetch<Q, R>(modifier: (query: Q) => R): R {
return modifier(this as any);
Expand Down Expand Up @@ -275,7 +279,7 @@ export class BaseQuery<T extends OrmEntity> {
}

withGroupConcat<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & { [C in [AS] as `${AS}`]: T[K][] }> {
return this.aggregateField(field, 'group_concat', as);
return this.aggregateField(field, 'group_concat', as) as any;
}

withCount<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & { [K in [AS] as `${AS}`]: number }> {
Expand Down Expand Up @@ -494,7 +498,7 @@ export class BaseQuery<T extends OrmEntity> {
join<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K, type: 'left' | 'inner' = 'left', populate: boolean = false): this {
const propertySchema = this.classSchema.getProperty(field as string);
if (!propertySchema.isReference() && !propertySchema.isBackReference()) {
throw new Error(`Field ${String(field)} is not marked as reference. Use @t.reference()`);
throw new Error(`Field ${String(field)} is not marked as reference. Use Reference type`);
}
const c = this.clone();

Expand Down Expand Up @@ -963,3 +967,5 @@ export class JoinDatabaseQuery<T extends OrmEntity, PARENT extends BaseQuery<any
return this.parentQuery;
}
}

export type AnyQuery<T extends OrmEntity> = JoinDatabaseQuery<T, any> | Query<T>;
31 changes: 24 additions & 7 deletions packages/orm/tests/query.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BackReference, deserialize, PrimaryKey } from '@deepkit/type';
import { BackReference, deserialize, PrimaryKey, Reference } from '@deepkit/type';
import { expect, test } from '@jest/globals';
import { assert, IsExact } from 'conditional-type-checks';
import { Database } from '../src/database.js';
import { MemoryDatabaseAdapter, MemoryQuery } from '../src/memory-db.js';
import { Query } from '../src/query.js';
import { AnyQuery, BaseQuery, Query } from '../src/query.js';
import { OrmEntity } from '../src/type.js';

test('query select', async () => {
Expand Down Expand Up @@ -70,10 +70,17 @@ test('query filter', async () => {
});

test('query lift', async () => {
class UserImage {
id!: number & PrimaryKey;
path!: string;
size!: number;
}

class User {
id!: number & PrimaryKey;
username!: string;
openBillings: number = 0;
image?: UserImage & Reference;
}

const database = new Database(new MemoryDatabaseAdapter());
Expand Down Expand Up @@ -108,18 +115,22 @@ test('query lift', async () => {
}
}

function filterBillingDue(q: Query<User>) {
function filterBillingDue(q: AnyQuery<User>) {
return q.filterField('openBillings', { $gt: 0 });
}

function filterMinBilling(q: Query<User>, min: number) {
function filterMinBilling(q: AnyQuery<User>, min: number) {
return q.filterField('openBillings', { $gt: min });
}

function allUserNames(q: Query<User>) {
return q.findField('username');
}

function filterImageSize(q: AnyQuery<UserImage>) {
return q.filterField('size', { $gt: 0 });
}

class OverwriteHello<T extends OrmEntity> extends Query<T> {
hello() {
return 'nope';
Expand All @@ -144,12 +155,12 @@ test('query lift', async () => {

{
const items = await q.lift(UserQuery).find();
assert<IsExact<{ username: string, openBillings: number, id: number & PrimaryKey }[], typeof items>>(true);
assert<IsExact<{ username: string, openBillings: number, id: number & PrimaryKey, image?: UserImage & Reference }[], typeof items>>(true);
}

{
const items = await q.lift(UserQuery).find();
assert<IsExact<{ username: string, openBillings: number, id: number & PrimaryKey }[], typeof items>>(true);
assert<IsExact<{ username: string, openBillings: number, id: number & PrimaryKey, image?: UserImage & Reference }[], typeof items>>(true);
}

{
Expand All @@ -159,7 +170,7 @@ test('query lift', async () => {

{
const items = await UserQuery.from(q).find();
assert<IsExact<{ username: string, openBillings: number, id: number & PrimaryKey }[], typeof items>>(true);
assert<IsExact<{ username: string, openBillings: number, id: number & PrimaryKey, image?: UserImage & Reference }[], typeof items>>(true);
}

{
Expand Down Expand Up @@ -228,6 +239,12 @@ test('query lift', async () => {
expect(items).toEqual(['bar']);
assert<IsExact<string[], typeof items>>(true);
}

{
const items = await q.useJoinWith('image').use(filterImageSize).end().fetch(allUserNames);
expect(items).toEqual(['foo', 'bar']);
assert<IsExact<string[], typeof items>>(true);
}
});


Expand Down

0 comments on commit 2ac4866

Please sign in to comment.