Skip to content

Commit

Permalink
whereLambda and mapLambda tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tantaman committed Jul 5, 2022
1 parent 7bff37f commit 1b094bb
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 38 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ coverage

# parcel cache
.cache

generated/
3 changes: 0 additions & 3 deletions examples/todo-mvc-mem/.gitpod.yml

This file was deleted.

2 changes: 2 additions & 0 deletions packages/cache-runtime-ts/src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export default class Cache {
}
}

// TODO: this should take into account engine and db too...
// could have duplicative type names. E.g., `User` generated for `Memory` and `SQL` storage.
function concatId<T>(id: SID_of<T>, typename: string) {
return (id + '-' + typename) as SID_of<T>;
}
44 changes: 44 additions & 0 deletions packages/integration-tests-ts/src/__tests__/where-lambda.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { asId, Context, context, viewer } from '@aphro/runtime-ts';
import User from '../generated/User.js';
import { default as UserMem } from '../generated-memory/User.js';
import createGraph from './createGraph.js';
import { initDb } from './testBase.js';

let ctx: Context;
beforeAll(async () => {
const resolver = await initDb();
ctx = context(viewer(asId('me')), resolver);
await createGraph(ctx);
});

// Note: "where(lambda)" can never be hoisted
// and thus should rarely, if ever, be used.
test('Where lambda', async () => {
const [u1sql, u1mem] = await Promise.all([
User.queryAll(ctx)
.where(u => u.name === 'U1')
.genxOnlyValue(),
UserMem.queryAll(ctx)
.where(u => u.name === 'U1')
.genxOnlyValue(),
]);

expect(u1sql.name).toEqual('U1');
expect(u1mem.name).toEqual('U1');
});

test('Map', async () => {
const [sqlNames, memNames] = await Promise.all([
User.queryAll(ctx)
.orderByName()
.map(u => u.name)
.gen(),
UserMem.queryAll(ctx)
.orderByName()
.map(u => u.name)
.gen(),
]);

expect(sqlNames).toEqual(['U1', 'U2', 'U3', 'U4']);
expect(memNames).toEqual(['U1', 'U2', 'U3', 'U4']);
});
18 changes: 13 additions & 5 deletions packages/query-runtime-ts/src/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export type Expression =
| ReturnType<typeof hop>
| ReturnType<typeof modelLoad>
| ReturnType<typeof count>
| CountLoadExpression<any>;
| CountLoadExpression<any>
| ReturnType<typeof map>;
/*
declare module '@mono/model/query' {
interface Expressions<ReturnType> {
Expand Down Expand Up @@ -102,11 +103,11 @@ export function after<T>(
// Needs to be more robust as we need to know if field and value are hoistable to the backend.
// So this should be some spec that references the schema in some way.
export function filter<Tm, Tv>(
getter: FieldGetter<Tm, Tv>,
getter: FieldGetter<Tm, Tv> | null,
predicate: Predicate<Tv>,
): {
type: 'filter';
getter: FieldGetter<Tm, Tv>;
getter: FieldGetter<Tm, Tv> | null;
predicate: Predicate<Tv>;
} & DerivedExpression<Tm, Tm> {
return {
Expand All @@ -116,12 +117,19 @@ export function filter<Tm, Tv>(
chainAfter(iterable) {
// TODO:
// @ts-ignore
return iterable.filter(m => predicate.call(getter.get(m)));
return iterable.filter(m => predicate.call(getter == null ? m : getter.get(m)));
},
};
}

export function map<T, R>() {}
export function map<T, R>(fn: (f: T) => R): { type: 'map' } & DerivedExpression<T, R> {
return {
type: 'map',
chainAfter(iterable) {
return iterable.map(fn);
},
};
}

export function orderBy<Tm, Tv>(
getter: FieldGetter<Tm, Tv>,
Expand Down
16 changes: 15 additions & 1 deletion packages/query-runtime-ts/src/Predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export type Predicate<Tv> =
| StartsWith
| EndsWith
| ContainsString
| ExcludesString;
| ExcludesString
| Lambda<Tv>;

export class Equal<Tv> {
constructor(public readonly value: Tv) {}
Expand Down Expand Up @@ -184,6 +185,15 @@ export class ExcludesString {
}
}

export class Lambda<Tv> {
readonly type = 'lambda';
constructor(public readonly value: (m: Tv) => boolean) {}

call(what: Tv): boolean {
return this.value(what);
}
}

const P = {
equals<Tv>(value: Tv) {
return new Equal(value);
Expand Down Expand Up @@ -233,6 +243,10 @@ const P = {
excludesString(value: string) {
return new ExcludesString(value);
},

lambda<Tv>(fn: (v: Tv) => boolean) {
return new Lambda(fn);
},
};

export default P;
Expand Down
50 changes: 21 additions & 29 deletions packages/query-runtime-ts/src/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import {
count,
EmptySourceExpression,
Expression,
filter,
HopExpression,
map,
SourceExpression,
} from './Expression.js';
import HopPlan from './HopPlan.js';
import LiveResult from './live/LiveResult.js';
import Plan, { IPlan } from './Plan.js';
import P from './Predicate.js';

export enum UpdateType {
CREATE = 1,
Expand All @@ -34,7 +37,7 @@ export interface Query<T> {

// map<R>(fn: (t: T) => R): Query<R>;
// flatMap<R>(fn: (t: T) => R[]): Query<R>;
// filter(fn: (t: T) => boolean): Query<T>;
// whereLambda(fn: (t: T) => boolean): Query<T>;
// take(n: number): Query<T>;
// after(cursor: Cursor<T>): Query<T>
count(): IterableDerivedQuery<number>;
Expand Down Expand Up @@ -88,10 +91,6 @@ abstract class BaseQuery<T> implements Query<T> {
// return
// }

// filter(fn: (t: T) => boolean): Query<T> {

// }

abstract plan(): IPlan;
abstract implicatedDatasets(): Set<string>;
}
Expand Down Expand Up @@ -153,7 +152,7 @@ export abstract class DerivedQuery<TOut> extends BaseQuery<TOut> {
}

/**
* This needs to match the constructor (sand priorQuery which is `this`) and serves the same purpose.
* This needs to match the constructor (sans priorQuery which is `this`) and serves the same purpose.
* 1. TypeScript does not allow `ConsistentConstruct`
* 2. TypeScript does not allow abstract static methods
* 3. TypeScript does not allow `new self()` to instantiate
Expand All @@ -163,7 +162,18 @@ export abstract class DerivedQuery<TOut> extends BaseQuery<TOut> {
*
* `derive` is used for lambda filters
*/
protected abstract derive(expression: Expression): ThisType<TOut>;
protected abstract derive(expression: Expression);

where(fn: (t: TOut) => boolean): this {
return this.derive(filter<TOut, TOut>(null, P.lambda(fn)));
}

map<TMapped>(fn: (t: TOut) => TMapped): DerivedQuery<TMapped> {
return this.derive(map(fn));
}

// union(): ThisType<TOut> {
// }

plan() {
const plan = this.#priorQuery.plan();
Expand All @@ -179,37 +189,19 @@ export abstract class DerivedQuery<TOut> extends BaseQuery<TOut> {
}
}

/*
Derived query example:
SlideQuery extends DerivedQuery {
static create() {
return new SlideQuery(
Factory.createSourceQueryFor(schema) // e.g., new SQLSourceQuery(),
// convert raw db result into model load.
// we'd want to move this expression to the end in plan optimizaiton.
new ModelLoadExpression(schema),
);
}
whereName(predicate: Predicate) {
return new SlideQuery(
this, // the prior query
new ModelFilterExpression(field, predicate)
);
}
}
*/

export class IterableDerivedQuery<TOut> extends DerivedQuery<TOut> {
constructor(ctx: Context, priorQuery: Query<any>, expression: Expression) {
super(ctx, priorQuery, expression);
}

protected derive<TNOut>(expression: Expression): ThisType<TNOut> {
protected derive<TNOut>(expression: Expression): IterableDerivedQuery<TNOut> {
return new IterableDerivedQuery(this.ctx, this, expression);
}
}

// and regular old IterableQuery to allow making anything
// into a query

export class EmptyQuery extends SourceQuery<void> {
constructor(ctx: Context) {
super(ctx, new EmptySourceExpression());
Expand Down
4 changes: 4 additions & 0 deletions packages/query-runtime-ts/src/sql/specAndOpsToQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ function getFilter(spec: NodeSpec | JunctionEdgeSpec, f: ReturnType<typeof filte
'%' + f.predicate.value + '%',
)}`;
}
case 'lambda':
throw new Error(
`Lambdas cannot be optimized to SQL! This expression should not have been hoisted for ${spec.storage.tablish}, ${getter.fieldName}`,
);
}

return sql`${sql.ident(spec.storage.tablish, getter.fieldName)} ${sql.__dangerous__rawValue(
Expand Down

0 comments on commit 1b094bb

Please sign in to comment.