Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Do not expose _ast #50

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/zql/ast-to-ivm/pipeline-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {expect, test} from 'vitest';
import {z} from 'zod';
import {makeTestContext} from '../context/context.js';
import {Materialite} from '../ivm/materialite.js';
import {EntityQueryImpl} from '../query/entity-query.js';
import {EntityQueryImpl, astForTesting as ast} from '../query/entity-query.js';
import {buildPipeline} from './pipeline-builder.js';

const e1 = z.object({
Expand All @@ -21,7 +21,7 @@ test('A simple select', () => {
let s = m.newStatelessSource<E1>();
let pipeline = buildPipeline(
() => s.stream,
q.select('id', 'a', 'b', 'c', 'd')._ast,
ast(q.select('id', 'a', 'b', 'c', 'd')),
);

let effectRunCount = 0;
Expand All @@ -39,7 +39,7 @@ test('A simple select', () => {
expect(effectRunCount).toBe(2);

s = m.newStatelessSource();
pipeline = buildPipeline(() => s.stream, q.select('a', 'd')._ast);
pipeline = buildPipeline(() => s.stream, ast(q.select('a', 'd')));
const expected2 = [
{a: 1, d: true},
{a: 2, d: false},
Expand All @@ -58,7 +58,7 @@ test('Count', () => {
const q = new EntityQueryImpl<{fields: E1}>(context, 'e1');
const m = new Materialite();
const s = m.newStatelessSource();
const pipeline = buildPipeline(() => s.stream, q.count()._ast);
const pipeline = buildPipeline(() => s.stream, ast(q.count()));

let effectRunCount = 0;
pipeline.effect(x => {
Expand All @@ -79,7 +79,7 @@ test('Where', () => {
const s = m.newStatelessSource();
const pipeline = buildPipeline(
() => s.stream,
q.select('id').where('a', '>', 1).where('b', '<', 2n)._ast,
ast(q.select('id').where('a', '>', 1).where('b', '<', 2n)),
);

let effectRunCount = 0;
Expand Down
28 changes: 14 additions & 14 deletions src/zql/query/entity-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {expect, expectTypeOf, test} from 'vitest';
import {z} from 'zod';
import {makeTestContext} from '../context/context.js';
import {Misuse} from '../error/misuse.js';
import {EntityQueryImpl} from './entity-query.js';
import {EntityQueryImpl, astForTesting as ast} from './entity-query.js';

const context = makeTestContext();
test('query types', () => {
Expand Down Expand Up @@ -79,19 +79,19 @@ test('ast: select', () => {
// each individual field is selectable on its own
Object.keys(dummyObject).forEach(k => {
const newq = q.select(k as keyof E1);
expect(newq._ast.select).toEqual([k]);
expect(ast(newq).select).toEqual([k]);
});

// all fields are selectable together
let newq = q.select(...(Object.keys(dummyObject) as (keyof E1)[]));
expect(newq._ast.select).toEqual(Object.keys(dummyObject));
expect(ast(newq).select).toEqual(Object.keys(dummyObject));

// we can call select many times to build up the selection set
newq = q;
Object.keys(dummyObject).forEach(k => {
newq = newq.select(k as keyof E1);
});
expect(newq._ast.select).toEqual(Object.keys(dummyObject));
expect(ast(newq).select).toEqual(Object.keys(dummyObject));

// we remove duplicates
newq = q;
Expand All @@ -101,7 +101,7 @@ test('ast: select', () => {
Object.keys(dummyObject).forEach(k => {
newq = newq.select(k as keyof E1);
});
expect(newq._ast.select).toEqual(Object.keys(dummyObject));
expect(ast(newq).select).toEqual(Object.keys(dummyObject));
});

test('ast: count', () => {
Expand All @@ -116,7 +116,7 @@ test('ast: count', () => {

// selection set is the literal `count`, not an array of fields
const q = new EntityQueryImpl<{fields: E1}>(context, 'e1').count();
expect(q._ast.select).toEqual('count');
expect(ast(q).select).toEqual('count');
});

test('ast: where', () => {
Expand All @@ -125,7 +125,7 @@ test('ast: where', () => {
// where is applied
q = q.where('id', '=', 'a');

expect({...q._ast, alias: 0}).toEqual({
expect({...ast(q), alias: 0}).toEqual({
alias: 0,
table: 'e1',
orderBy: [['id'], 'asc'],
Expand All @@ -144,7 +144,7 @@ test('ast: where', () => {
// additional wheres are anded
q = q.where('a', '>', 0);

expect({...q._ast, alias: 0}).toEqual({
expect({...ast(q), alias: 0}).toEqual({
alias: 0,
table: 'e1',
orderBy: [['id'], 'asc'],
Expand Down Expand Up @@ -172,7 +172,7 @@ test('ast: where', () => {

test('ast: limit', () => {
const q = new EntityQueryImpl<{fields: E1}>(context, 'e1');
expect({...q.limit(10)._ast, alias: 0}).toEqual({
expect({...ast(q.limit(10)), alias: 0}).toEqual({
orderBy: [['id'], 'asc'],
alias: 0,
table: 'e1',
Expand All @@ -184,17 +184,17 @@ test('ast: asc/desc', () => {
const q = new EntityQueryImpl<{fields: E1}>(context, 'e1');

// order methods update the ast
expect({...q.asc('id')._ast, alias: 0}).toEqual({
expect({...ast(q.asc('id')), alias: 0}).toEqual({
alias: 0,
table: 'e1',
orderBy: [['id'], 'asc'],
});
expect({...q.desc('id')._ast, alias: 0}).toEqual({
expect({...ast(q.desc('id')), alias: 0}).toEqual({
alias: 0,
table: 'e1',
orderBy: [['id'], 'desc'],
});
expect({...q.asc('id', 'a', 'b', 'c', 'd')._ast, alias: 0}).toEqual({
expect({...ast(q.asc('id', 'a', 'b', 'c', 'd')), alias: 0}).toEqual({
alias: 0,
table: 'e1',
orderBy: [['id', 'a', 'b', 'c', 'd'], 'asc'],
Expand Down Expand Up @@ -224,14 +224,14 @@ test('ast: independent of method call order', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
q = call(q) as any;
}
const inOrderToAST = q._ast;
const inOrderToAST = ast(q);

q = base;
for (const call of Object.values(calls).reverse()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
q = call(q) as any;
}
const reverseToAST = q._ast;
const reverseToAST = ast(q);

expect({
...inOrderToAST,
Expand Down
21 changes: 12 additions & 9 deletions src/zql/query/entity-query.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {Statement} from './statement.js';
import {AST, Operator, Primitive} from '../ast/ast.js';
import {Context} from '../context/context.js';
import {must} from '../error/asserts.js';
import {Misuse} from '../error/misuse.js';
import {EntitySchema} from '../schema/entity-schema.js';
import {Statement} from './statement.js';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type SelectedFields<T, Fields extends Selectable<any>[]> = Pick<
Expand Down Expand Up @@ -41,8 +42,6 @@ export interface EntityQuery<Schema extends EntitySchema, Return = []> {
readonly desc: (
...x: (keyof Schema['fields'])[]
) => EntityQuery<Schema, Return>;
// eslint-disable-next-line @typescript-eslint/naming-convention
readonly _ast: AST;

// TODO: we can probably skip the `prepare` step and just have `materialize`
// Although we'd need the prepare step in order to get a stmt to change bindings.
Expand All @@ -66,6 +65,9 @@ export class EntityQueryImpl<S extends EntitySchema, Return = []>
};
this.#name = tableName;
this.#context = context;

// TODO(arv): Guard this with TESTING once we have the infrastructure.
astWeakMap.set(this, this.#ast);
}

select<Fields extends Selectable<S>[]>(...x: Fields) {
Expand Down Expand Up @@ -157,12 +159,13 @@ export class EntityQueryImpl<S extends EntitySchema, Return = []>
});
}

// eslint-disable-next-line @typescript-eslint/naming-convention
get _ast() {
return this.#ast;
}

prepare(): Statement<Return> {
return new Statement<Return>(this.#context, this);
return new Statement<Return>(this.#context, this.#ast);
}
}

const astWeakMap = new WeakMap<EntityQueryImpl<EntitySchema, unknown>, AST>();

export function astForTesting(q: EntityQueryImpl<EntitySchema, unknown>): AST {
return must(astWeakMap.get(q));
}
44 changes: 21 additions & 23 deletions src/zql/query/statement.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import {Entity} from '../../generate.js';
import {buildPipeline, orderingProp} from '../ast-to-ivm/pipeline-builder.js';
import {Primitive} from '../ast/ast.js';
import {AST, Primitive} from '../ast/ast.js';
import {Context} from '../context/context.js';
import {invariant, must} from '../error/asserts.js';
import {compareEntityFields} from '../ivm/compare.js';
import {DifferenceStream} from '../ivm/graph/difference-stream.js';
import {ValueView} from '../ivm/view/primitive-view.js';
import {MutableTreeView} from '../ivm/view/tree-view.js';
import {View} from '../ivm/view/view.js';
import {EntitySchema} from '../schema/entity-schema.js';
import {EntityQuery, MakeHumanReadable} from './entity-query.js';
import {MakeHumanReadable} from './entity-query.js';

export interface IStatement<TReturn> {
subscribe(cb: (value: MakeHumanReadable<TReturn>) => void): () => void;
Expand All @@ -18,26 +17,25 @@ export interface IStatement<TReturn> {
destroy(): void;
}

export class Statement<TReturn> implements IStatement<TReturn> {
export class Statement<Return> implements IStatement<Return> {
readonly #pipeline;
readonly #ast;
readonly #context;
#materialization: View<
TReturn extends [] ? TReturn[number] : TReturn
> | null = null;
#materialization: View<Return extends [] ? Return[number] : Return> | null =
null;

constructor(c: Context, q: EntityQuery<EntitySchema, TReturn>) {
this.#ast = q._ast;
constructor(context: Context, ast: AST) {
this.#ast = ast;
this.#pipeline = buildPipeline(
<T extends Entity>(sourceName: string) =>
c.getSource(sourceName, this.#ast.orderBy)
context.getSource(sourceName, this.#ast.orderBy)
.stream as DifferenceStream<T>,
q._ast,
ast,
);
this.#context = c;
this.#context = context;
}

view(): View<TReturn> {
view(): View<Return> {
// TODO: invariants to throw if the statement is not completely bound before materialization.
if (this.#materialization === null) {
if (this.#ast.select === 'count') {
Expand All @@ -46,30 +44,30 @@ export class Statement<TReturn> implements IStatement<TReturn> {
this.#context.materialite,
this.#pipeline as DifferenceStream<number>,
0,
) as unknown as View<TReturn extends [] ? TReturn[number] : TReturn>;
) as unknown as View<Return extends [] ? Return[number] : Return>;
} else {
this.#materialization = new MutableTreeView<
TReturn extends [] ? TReturn[number] : never
Return extends [] ? Return[number] : never
>(
this.#context.materialite,
this.#pipeline as DifferenceStream<
TReturn extends [] ? TReturn[number] : never
Return extends [] ? Return[number] : never
>,
this.#ast.orderBy?.[1] === 'asc' ? ascComparator : descComparator,
true, // TODO: since we're going to control everything we can make this so.
this.#ast.limit,
) as unknown as View<TReturn extends [] ? TReturn[number] : TReturn>;
) as unknown as View<Return extends [] ? Return[number] : Return>;
}
}

// TODO: we'll want some ability to let a caller await
// the response of historical data.
this.#materialization.pullHistoricalData();

return this.#materialization as View<TReturn>;
return this.#materialization as View<Return>;
}

subscribe(cb: (value: MakeHumanReadable<TReturn>) => void) {
subscribe(cb: (value: MakeHumanReadable<Return>) => void) {
if (this.#materialization === null) {
this.view();
}
Expand All @@ -87,16 +85,16 @@ export class Statement<TReturn> implements IStatement<TReturn> {

if (this.#materialization?.hydrated) {
return Promise.resolve(this.#materialization.value) as Promise<
MakeHumanReadable<TReturn>
MakeHumanReadable<Return>
>;
}

return new Promise<MakeHumanReadable<TReturn>>(resolve => {
return new Promise<MakeHumanReadable<Return>>(resolve => {
const cleanup = must(this.#materialization).on(value => {
resolve(value as MakeHumanReadable<TReturn>);
resolve(value as MakeHumanReadable<Return>);
cleanup();
});
}) as Promise<MakeHumanReadable<TReturn>>;
}) as Promise<MakeHumanReadable<Return>>;
}

// For savvy users that want to subscribe directly to diffs.
Expand Down
Loading