Skip to content

Commit

Permalink
feat(zero-pg): implement makeSchemaQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
tantaman committed Feb 14, 2025
1 parent 6e12518 commit 6a812d1
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 72 deletions.
62 changes: 3 additions & 59 deletions packages/zero-pg/src/custom.pg-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,20 @@
import {testDBs} from '../../zero-cache/src/test/db.ts';
import {beforeEach, describe, expect, test} from 'vitest';
import type {PostgresDB} from '../../zero-cache/src/types/pg.ts';
import {createSchema} from '../../zero-schema/src/builder/schema-builder.ts';
import {
boolean,
number,
string,
table,
} from '../../zero-schema/src/builder/table-builder.ts';

import type {DBTransaction} from './db.ts';
import {makeSchemaCRUD} from './custom.ts';
import {Transaction} from './test/util.ts';
import type {SchemaCRUD} from '../../zql/src/mutate/custom.ts';

const schema = createSchema(1, {
tables: [
table('basic')
.columns({
id: string(),
a: number(),
b: string(),
c: boolean().optional(),
})
.primaryKey('id'),
table('names')
.from('divergent_names')
.columns({
id: string().from('divergent_id'),
a: number().from('divergent_a'),
b: string().from('divergent_b'),
c: boolean().from('divergent_c').optional(),
})
.primaryKey('id'),
table('compoundPk')
.columns({
a: string(),
b: number(),
c: string().optional(),
})
.primaryKey('a', 'b'),
],
relationships: [],
});
import {schema, schemaSql} from './test/schema.ts';

describe('makeSchemaCRUD', () => {
let pg: PostgresDB;
let crudProvider: (tx: DBTransaction<unknown>) => SchemaCRUD<typeof schema>;

beforeEach(async () => {
pg = await testDBs.create('makeSchemaCRUD-test');
await pg.unsafe(`
CREATE TABLE basic (
id TEXT PRIMARY KEY,
a INTEGER,
b TEXT,
C BOOLEAN
);
CREATE TABLE divergent_names (
divergent_id TEXT PRIMARY KEY,
divergent_a INTEGER,
divergent_b TEXT,
divergent_c BOOLEAN
);
CREATE TABLE "compoundPk" (
a TEXT,
b INTEGER,
c TEXT,
PRIMARY KEY (a, b)
);
`);
await pg.unsafe(schemaSql);

crudProvider = makeSchemaCRUD(schema);
});
Expand Down
9 changes: 0 additions & 9 deletions packages/zero-pg/src/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,6 @@ function makeTableCRUD(schema: TableSchema): TableCRUD<TableSchema> {
};
}

export function makeSchemaQuery<S extends Schema>(
_schema: Schema,
): (dbTransaction: DBTransaction<unknown>) => SchemaQuery<S> {
return (_dbTransaction: DBTransaction<unknown>) =>
// TODO: Implement this
// eslint-disable-next-line @typescript-eslint/no-explicit-any
({}) as any;
}

function serverName(x: {name: string; serverName?: string | undefined}) {
return x.serverName ?? x.name;
}
Expand Down
36 changes: 36 additions & 0 deletions packages/zero-pg/src/query.pg-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {beforeEach, describe, expect, test} from 'vitest';
import type {SchemaQuery} from '../../zql/src/mutate/custom.ts';
import type {PostgresDB} from '../../zero-cache/src/types/pg.ts';
import {schema, schemaSql, seedDataSql} from './test/schema.ts';
import {testDBs} from '../../zero-cache/src/test/db.ts';
import {makeSchemaQuery} from './query.ts';
import {Transaction} from './test/util.ts';
import type {DBTransaction} from './db.ts';

describe('makeSchemaQuery', () => {
let pg: PostgresDB;
let queryProvider: (tx: DBTransaction<unknown>) => SchemaQuery<typeof schema>;

beforeEach(async () => {
pg = await testDBs.create('makeSchemaQuery-test');
await pg.unsafe(schemaSql);
await pg.unsafe(seedDataSql);

queryProvider = makeSchemaQuery(schema);
});

test('select', async () => {
await pg.begin(async tx => {
const query = queryProvider(new Transaction(tx));
const result = await query.basic.run();
expect(result).toEqual([{id: '1', a: 2, b: 'foo', c: true}]);

// TODO: z2s needs to be schema-aware so it can re-map names
// const result2 = await query.names.run();
// expect(result2).toEqual([{id: '2', a: 3, b: 'bar', c: false}]);

const result3 = await query.compoundPk.run();
expect(result3).toEqual([{a: 'a', b: 1, c: 'c'}]);
});
});
});
98 changes: 98 additions & 0 deletions packages/zero-pg/src/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type {Schema} from '../../zero-schema/src/builder/schema-builder.ts';
import type {SchemaQuery} from '../../zql/src/mutate/custom.ts';
import type {DBTransaction} from './db.ts';
import type {AST} from '../../zero-protocol/src/ast.ts';
import type {Format} from '../../zql/src/ivm/view.ts';
import {AbstractQuery} from '../../zql/src/query/query-impl.ts';
import type {HumanReadable, PullRow, Query} from '../../zql/src/query/query.ts';
import type {TypedView} from '../../zql/src/query/typed-view.ts';
import {formatPg} from '../../z2s/src/sql.ts';
import {compile} from '../../z2s/src/compiler.ts';

export function makeSchemaQuery<S extends Schema>(
schema: S,
): (dbTransaction: DBTransaction<unknown>) => SchemaQuery<S> {
class SchemaQueryHandler {
readonly #dbTransaction: DBTransaction<unknown>;
constructor(dbTransaction: DBTransaction<unknown>) {
this.#dbTransaction = dbTransaction;
}

get(
target: Record<
string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Omit<Query<S, string, any>, 'materialize' | 'preload'>
>,
prop: string,
) {
if (prop in target) {
return target[prop];
}

const q = new Z2SQuery(schema, prop, this.#dbTransaction);
target[prop] = q;
return q;
}
}

return (dbTransaction: DBTransaction<unknown>) =>
new Proxy({}, new SchemaQueryHandler(dbTransaction)) as SchemaQuery<S>;
}

export class Z2SQuery<
TSchema extends Schema,
TTable extends keyof TSchema['tables'] & string,
TReturn = PullRow<TTable, TSchema>,
> extends AbstractQuery<TSchema, TTable, TReturn> {
readonly #dbTransaction: DBTransaction<unknown>;

constructor(
schema: TSchema,
tableName: TTable,
dbTransaction: DBTransaction<unknown>,
ast: AST = {table: tableName},
format?: Format | undefined,
) {
super(schema, tableName, ast, format);
this.#dbTransaction = dbTransaction;
}

protected readonly _system = 'permissions';

protected _newQuery<
TSchema extends Schema,
TTable extends keyof TSchema['tables'] & string,
TReturn,
>(
schema: TSchema,
tableName: TTable,
ast: AST,
format: Format | undefined,
): Query<TSchema, TTable, TReturn> {
return new Z2SQuery(schema, tableName, this.#dbTransaction, ast, format);
}

async run(): Promise<HumanReadable<TReturn>> {
const sqlQuery = formatPg(compile(this._completeAst(), this.format));
const result = await this.#dbTransaction.query(
sqlQuery.text,
sqlQuery.values,
);
if (Array.isArray(result)) {
return result as HumanReadable<TReturn>;
}
return [...result] as HumanReadable<TReturn>;
}

preload(): {
cleanup: () => void;
complete: Promise<void>;
} {
throw new Error('Z2SQuery cannot be preloaded');
}

materialize(): TypedView<HumanReadable<TReturn>> {
throw new Error('Z2SQuery cannot be materialized');
}
}
64 changes: 64 additions & 0 deletions packages/zero-pg/src/test/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {createSchema} from '../../../zero-schema/src/builder/schema-builder.ts';
import {
boolean,
number,
string,
table,
} from '../../../zero-schema/src/builder/table-builder.ts';

export const schema = createSchema(1, {
tables: [
table('basic')
.columns({
id: string(),
a: number(),
b: string(),
c: boolean().optional(),
})
.primaryKey('id'),
table('names')
.from('divergent_names')
.columns({
id: string().from('divergent_id'),
a: number().from('divergent_a'),
b: string().from('divergent_b'),
c: boolean().from('divergent_c').optional(),
})
.primaryKey('id'),
table('compoundPk')
.columns({
a: string(),
b: number(),
c: string().optional(),
})
.primaryKey('a', 'b'),
],
relationships: [],
});

export const schemaSql = `CREATE TABLE basic (
id TEXT PRIMARY KEY,
a INTEGER,
b TEXT,
C BOOLEAN
);
CREATE TABLE divergent_names (
divergent_id TEXT PRIMARY KEY,
divergent_a INTEGER,
divergent_b TEXT,
divergent_c BOOLEAN
);
CREATE TABLE "compoundPk" (
a TEXT,
b INTEGER,
c TEXT,
PRIMARY KEY (a, b)
);`;

export const seedDataSql = `
INSERT INTO basic (id, a, b, c) VALUES ('1', 2, 'foo', true);
INSERT INTO divergent_names (divergent_id, divergent_a, divergent_b, divergent_c) VALUES ('2', 3, 'bar', false);
INSERT INTO "compoundPk" (a, b, c) VALUES ('a', 1, 'c');
`;
2 changes: 1 addition & 1 deletion packages/zero-pg/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import * as v from '../../shared/src/valita.ts';
import {pushBodySchema} from '../../zero-protocol/src/push.ts';
import {
makeSchemaCRUD,
makeSchemaQuery,
TransactionImpl,
type CustomMutatorDefs,
} from './custom.ts';
Expand All @@ -23,6 +22,7 @@ import {
type SchemaCRUD,
type SchemaQuery,
} from '../../zql/src/mutate/custom.ts';
import {makeSchemaQuery} from './query.ts';

export type PushHandler = (
headers: Headers,
Expand Down
6 changes: 3 additions & 3 deletions packages/zql/src/query/static-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ export class StaticQuery<
}

materialize(): TypedView<HumanReadable<TReturn>> {
throw new Error('AuthQuery cannot be materialized');
throw new Error('StaticQuery cannot be materialized');
}

run(): Promise<HumanReadable<TReturn>> {
throw new Error('AuthQuery cannot be run');
throw new Error('StaticQuery cannot be run');
}

preload(): {
cleanup: () => void;
complete: Promise<void>;
} {
throw new Error('AuthQuery cannot be preloaded');
throw new Error('StaticQuery cannot be preloaded');
}
}

0 comments on commit 6a812d1

Please sign in to comment.