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

implement a reduce operator & ZQL group-by on top of it #45

Merged
merged 12 commits into from
Mar 29, 2024
2 changes: 1 addition & 1 deletion src/zql/ast-to-ivm/pipeline-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function applySelect(
orderBy: Ordering | undefined,
) {
return stream.map(x => {
let ret: Partial<Record<string, unknown>>;
let ret: Record<string, unknown>;
if (select.length === 0) {
ret = {...x};
} else {
Expand Down
9 changes: 4 additions & 5 deletions src/zql/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Replicache, TEST_LICENSE_KEY} from 'replicache';
import {nanoid} from 'nanoid';
import fc from 'fast-check';
import {EntityQuery} from './query/entity-query.js';
import {agg} from './query/agg.js';
import * as agg from './query/agg.js';

export async function tickAFewTimes(n = 10, time = 0) {
for (let i = 0; i < n; i++) {
Expand Down Expand Up @@ -406,7 +406,7 @@ test('group by', async () => {
priority: 'high',
assignee: 'charles',
created: new Date('2024-01-01').getTime(),
updated: new Date().getTime(),
updated: Date.now(),
},
{
id: 'b',
Expand All @@ -415,7 +415,7 @@ test('group by', async () => {
priority: 'medium',
assignee: 'bob',
created: new Date('2024-01-02').getTime(),
updated: new Date().getTime(),
updated: Date.now(),
},
{
id: 'c',
Expand All @@ -424,11 +424,10 @@ test('group by', async () => {
priority: 'low',
assignee: 'alice',
created: new Date('2024-01-03').getTime(),
updated: new Date().getTime(),
updated: Date.now(),
},
] as const;
await Promise.all(issues.map(r.mutate.initIssue));
await new Promise(resolve => setTimeout(resolve, 0));
const stmt = q
.select('status', agg.count('status', 'count'))
.groupBy('status')
Expand Down
69 changes: 0 additions & 69 deletions src/zql/ivm/graph/operators/operator-index.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use a unit test

tantaman marked this conversation as resolved.
Outdated
Show resolved Hide resolved

This file was deleted.

210 changes: 107 additions & 103 deletions src/zql/query/agg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,106 +35,110 @@ export type AggArray<Field extends string, Alias extends string> = {
aggregate: 'array';
} & AggregateBase<Field, Alias>;

export const agg: {
min<Field extends string>(field: Field): Min<Field, Field>;
min<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Min<Field, Alias>;

max<Field extends string>(field: Field): Max<Field, Field>;
max<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Max<Field, Alias>;

sum<Field extends string>(field: Field): Sum<Field, Field>;
sum<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Sum<Field, Alias>;

count<Field extends string>(field: Field): Count<Field, Field>;
count<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Count<Field, Alias>;

avg<Field extends string>(field: Field): Avg<Field, Field>;
avg<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Avg<Field, Alias>;

array<Field extends string>(field: Field): AggArray<Field, Field>;
array<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): AggArray<Field, Alias>;
} = {
min<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Min<Field, Alias> {
return {
aggregate: 'min',
field,
alias: alias ?? (field as unknown as Alias),
};
},

max<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Max<Field, Alias> {
return {
aggregate: 'max',
field,
alias: alias ?? (field as unknown as Alias),
};
},

sum<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Sum<Field, Alias> {
return {
aggregate: 'sum',
field,
alias: alias ?? (field as unknown as Alias),
};
},

count<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Count<Field, Alias> {
return {
aggregate: 'count',
field,
alias: alias ?? (field as unknown as Alias),
};
},

avg<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Avg<Field, Alias> {
return {
aggregate: 'avg',
field,
alias: alias ?? (field as unknown as Alias),
};
},

array<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): AggArray<Field, Alias> {
return {
aggregate: 'array',
field,
alias: alias ?? (field as unknown as Alias),
};
},
};
export function min<Field extends string>(field: Field): Min<Field, Field>;
export function min<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Min<Field, Alias>;
export function min<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Min<Field, Alias> {
return {
aggregate: 'min',
field,
alias: alias ?? (field as unknown as Alias),
};
}

export function max<Field extends string>(field: Field): Max<Field, Field>;
export function max<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Max<Field, Alias>;
export function max<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Max<Field, Alias> {
return {
aggregate: 'max',
field,
alias: alias ?? (field as unknown as Alias),
};
}

export function sum<Field extends string>(field: Field): Sum<Field, Field>;
export function sum<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Sum<Field, Alias>;
export function sum<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Sum<Field, Alias> {
return {
aggregate: 'sum',
field,
alias: alias ?? (field as unknown as Alias),
};
}

export function count<Field extends string>(field: Field): Count<Field, Field>;
export function count<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Count<Field, Alias>;
export function count<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Count<Field, Alias> {
return {
aggregate: 'count',
field,
alias: alias ?? (field as unknown as Alias),
};
}

export function avg<Field extends string>(field: Field): Avg<Field, Field>;
export function avg<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): Avg<Field, Alias>;
export function avg<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): Avg<Field, Alias> {
return {
aggregate: 'avg',
field,
alias: alias ?? (field as unknown as Alias),
};
}

export function array<Field extends string>(
field: Field,
): AggArray<Field, Field>;
export function array<Field extends string, Alias extends string>(
field: Field,
alias: Alias,
): AggArray<Field, Alias>;
export function array<Field extends string, Alias extends string>(
field: Field,
alias?: Alias | undefined,
): AggArray<Field, Alias> {
return {
aggregate: 'array',
field,
alias: alias ?? (field as unknown as Alias),
};
}

export function isAggregate<Field extends string, Alias extends string>(
x: unknown,
): x is Aggregate<Field, Alias> {
return (
x !== null &&
typeof x === 'object' &&
typeof (x as Record<string, unknown>).aggregate === 'string'
);
}
16 changes: 10 additions & 6 deletions src/zql/query/entity-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {z} from 'zod';
import {makeTestContext} from '../context/context.js';
import {Misuse} from '../error/misuse.js';
import {EntityQuery, astForTesting as ast} from './entity-query.js';
import {agg} from './agg.js';
import * as agg from './agg.js';

const context = makeTestContext();
test('query types', () => {
Expand Down Expand Up @@ -53,14 +53,18 @@ test('query types', () => {
q.where(sym, '==', true);

// @ts-expect-error - 'x' is not a field that we can aggregate on
q.select(agg.array('x'));
q.select(agg.array('x')).groupBy('id');

expectTypeOf(q.select('id', agg.array('str')).prepare().exec()).toMatchTypeOf<
Promise<readonly {id: string; str: readonly string[]}[]>
>();
expectTypeOf(
q.select('id', agg.array('str')).groupBy('optStr').prepare().exec(),
).toMatchTypeOf<Promise<readonly {id: string; str: readonly string[]}[]>>();

expectTypeOf(
q.select('id', agg.array('str', 'alias')).prepare().exec(),
q
.select('id', agg.array('str', 'alias'))
.groupBy('optStr')
.prepare()
.exec(),
).toMatchTypeOf<Promise<readonly {id: string; alias: readonly string[]}[]>>();
});

Expand Down
Loading
Loading