@rocicorp/zero 1.9.0-canary.0 → 1.9.0-canary.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +1 -22
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/zero/package.js +2 -2
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +9 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +26 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +1 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/db/delete-lite-db.js +2 -1
- package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.d.ts +3 -2
- package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.js +13 -8
- package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +5 -1
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +4 -4
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +1 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +2 -2
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +1 -1
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +10 -5
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +45 -2
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +58 -7
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +4 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +3 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +4 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +7 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +24 -5
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +1 -1
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +77 -28
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config.yml +3 -2
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +2 -2
- package/out/zero-cache/src/services/replicator/change-processor.d.ts +6 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +26 -8
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +8 -2
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -22
- package/out/zero-cache/src/services/replicator/notifier.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.js +7 -1
- package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +16 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +7 -1
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +27 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +32 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +14 -6
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +44 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +59 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +18 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +11 -0
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +11 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +27 -0
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +6 -7
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +156 -101
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-server/src/custom.js +8 -2
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +8 -0
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +63 -24
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +5 -1
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zqlite/src/db.js +2 -2
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +13 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +5 -5
- package/out/zqlite/src/table-source.js +3 -3
- package/package.json +2 -2
|
@@ -171,13 +171,16 @@ function makeServerTableCRUD(schema) {
|
|
|
171
171
|
const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];
|
|
172
172
|
const targetedColumns = origAndServerNamesFor(Object.keys(value), schema);
|
|
173
173
|
const primaryKeyColumns = origAndServerNamesFor(schema.primaryKey, schema);
|
|
174
|
-
const
|
|
174
|
+
const updatedEntries = nonPrimaryKeyEntries(value, schema);
|
|
175
|
+
const conflictAction = updatedEntries.length === 0 ? sql`DO NOTHING` : sql`DO UPDATE SET ${sql.join(updatedEntries.map(([col, val]) => sql`${sql.ident(schema.columns[col].serverName ?? col)} = ${sqlInsertValue(val, serverTableSchema[serverNameFor(col, schema)])}`), ", ")}`;
|
|
176
|
+
const stmt = formatPgInternalConvert(sql`INSERT INTO ${sql.ident(serverName(schema))} (${sql.join(targetedColumns.map(([, serverName]) => sql.ident(serverName)), ",")}) VALUES (${sql.join(Object.entries(value).map(([col, val]) => sqlInsertValue(val, serverTableSchema[serverNameFor(col, schema)])), ", ")}) ON CONFLICT (${sql.join(primaryKeyColumns.map(([, serverName]) => sql.ident(serverName)), ", ")}) ${conflictAction}`);
|
|
175
177
|
await this[dbTxSymbol].query(stmt.text, stmt.values);
|
|
176
178
|
},
|
|
177
179
|
async update(value) {
|
|
178
180
|
value = removeUndefined(value);
|
|
179
181
|
const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];
|
|
180
|
-
const targetedColumns = origAndServerNamesFor(
|
|
182
|
+
const targetedColumns = origAndServerNamesFor(nonPrimaryKeyEntries(value, schema).map(([name]) => name), schema);
|
|
183
|
+
if (targetedColumns.length === 0) return;
|
|
181
184
|
const stmt = formatPgInternalConvert(sql`UPDATE ${sql.ident(serverName(schema))} SET ${sql.join(targetedColumns.map(([origName, serverName]) => sql`${sql.ident(serverName)} = ${sqlInsertValue(value[origName], serverTableSchema[serverName])}`), ", ")} WHERE ${primaryKeyClause(schema, serverTableSchema, value)}`);
|
|
182
185
|
await this[dbTxSymbol].query(stmt.text, stmt.values);
|
|
183
186
|
},
|
|
@@ -189,6 +192,9 @@ function makeServerTableCRUD(schema) {
|
|
|
189
192
|
}
|
|
190
193
|
};
|
|
191
194
|
}
|
|
195
|
+
function nonPrimaryKeyEntries(value, schema) {
|
|
196
|
+
return Object.entries(value).filter(([name]) => !schema.primaryKey.includes(name));
|
|
197
|
+
}
|
|
192
198
|
function serverName(x) {
|
|
193
199
|
return x.serverName ?? x.name;
|
|
194
200
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom.js","names":["#dbTransaction","#schema","#serverSchema","#tableCRUDs","#getOrFetchServerSchema"],"sources":["../../../../zero-server/src/custom.ts"],"sourcesContent":["import {assert} from '../../shared/src/asserts.ts';\nimport {mapValues} from '../../shared/src/objects.ts';\nimport {recordProxy} from '../../shared/src/record-proxy.ts';\nimport {\n formatPgInternalConvert,\n sql,\n sqlConvertColumnArg,\n} from '../../z2s/src/sql.ts';\nimport type {TableSchema} from '../../zero-schema/src/table-schema.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {\n ServerColumnSchema,\n ServerSchema,\n ServerTableSchema,\n} from '../../zero-types/src/server-schema.ts';\nimport {\n type CRUDExecutor,\n type CRUDKind,\n makeCRUDMutate,\n makeTransactionMutate,\n type SchemaCRUD,\n type TableCRUD,\n type TransactionMutate,\n} from '../../zql/src/mutate/crud.ts';\nimport type {\n DBTransaction,\n MutateCRUD,\n ServerTransaction,\n} from '../../zql/src/mutate/custom.ts';\nimport {createRunnableBuilder} from '../../zql/src/query/create-builder.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport type {\n HumanReadable,\n Query,\n RunOptions,\n} from '../../zql/src/query/query.ts';\nimport type {ConditionalSchemaQuery} from '../../zql/src/query/schema-query.ts';\nimport {getServerSchema} from './schema.ts';\n\nexport type CustomMutatorDefs<TDBTransaction> = {\n [namespaceOrKey: string]:\n | CustomMutatorImpl<TDBTransaction>\n | CustomMutatorDefs<TDBTransaction>;\n};\n\nexport type CustomMutatorImpl<\n TDBTransaction,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n TArgs = any,\n Context = unknown,\n> = (tx: TDBTransaction, args: TArgs, ctx: Context) => Promise<void>;\n\n/**\n * QueryDelegate implementation for server-side transactions.\n * Extends QueryDelegateBase to satisfy the QueryDelegate interface,\n * but overrides run() to execute against Postgres and throws on\n * preload()/materialize() which don't make sense server-side.\n */\nclass ServerTransactionQueryDelegate extends QueryDelegateBase {\n readonly #dbTransaction: DBTransaction<unknown>;\n readonly #schema: Schema;\n readonly #serverSchema: ServerSchema;\n\n readonly defaultQueryComplete = true;\n\n constructor(\n dbTransaction: DBTransaction<unknown>,\n schema: Schema,\n serverSchema: ServerSchema,\n ) {\n super();\n this.#dbTransaction = dbTransaction;\n this.#schema = schema;\n this.#serverSchema = serverSchema;\n }\n\n getSource(): never {\n throw new Error('not implemented');\n }\n\n override run<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n _options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n const queryInternals = asQueryInternals(query);\n return this.#dbTransaction.runQuery<TReturn>(\n queryInternals.ast,\n queryInternals.format,\n this.#schema,\n this.#serverSchema,\n );\n }\n\n override preload(): never {\n throw new Error('preload() is not supported in server transactions');\n }\n\n override materialize(): never {\n throw new Error('materialize() is not supported in server transactions');\n }\n}\n\nexport class TransactionImpl<\n TSchema extends Schema,\n TWrappedTransaction,\n> implements ServerTransaction<TSchema, TWrappedTransaction> {\n readonly location = 'server';\n readonly reason = 'authoritative';\n readonly dbTransaction: DBTransaction<TWrappedTransaction>;\n readonly clientID: string;\n readonly mutationID: number;\n readonly mutate: TransactionMutate<TSchema>;\n /**\n * @deprecated Use {@linkcode createBuilder} with `tx.run(zql.table.where(...))` instead.\n */\n readonly query: ConditionalSchemaQuery<TSchema>;\n\n readonly #schema: TSchema;\n readonly #serverSchema: ServerSchema;\n\n constructor(\n dbTransaction: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n mutate: TransactionMutate<TSchema>,\n schema: TSchema,\n serverSchema: ServerSchema,\n ) {\n this.dbTransaction = dbTransaction;\n this.clientID = clientID;\n this.mutationID = mutationID;\n this.mutate = mutate;\n this.#schema = schema;\n this.#serverSchema = serverSchema;\n\n const delegate = new ServerTransactionQueryDelegate(\n dbTransaction,\n schema,\n serverSchema,\n );\n this.query = createRunnableBuilder(delegate, schema);\n }\n\n run<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TTable, TSchema, TReturn>,\n _options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n const queryInternals = asQueryInternals(query);\n\n // Execute the query using the database-specific executor\n return this.dbTransaction.runQuery<TReturn>(\n queryInternals.ast,\n queryInternals.format,\n this.#schema,\n this.#serverSchema,\n );\n }\n}\n\nconst dbTxSymbol = Symbol();\n\nconst serverSchemaSymbol = Symbol();\n\ntype WithHiddenTxAndSchema = {\n [dbTxSymbol]: DBTransaction<unknown>;\n [serverSchemaSymbol]: ServerSchema;\n};\n\n/**\n * Factory for creating MutateCRUD instances efficiently.\n *\n * Pre-creates the SQL-generating TableCRUD methods once from the schema,\n * caches the serverSchema after first fetch, and only binds the transaction\n * at transaction time.\n *\n * Use this when you need to create many transactions and want to avoid\n * the overhead of re-creating CRUD methods for each one.\n */\nexport class CRUDMutatorFactory<S extends Schema> {\n readonly #schema: S;\n readonly #tableCRUDs: Record<string, TableCRUD<TableSchema>>;\n #serverSchema: ServerSchema | undefined;\n\n constructor(schema: S) {\n this.#schema = schema;\n // Pre-create TableCRUD methods for each table once\n this.#tableCRUDs = {};\n for (const tableSchema of Object.values(schema.tables)) {\n this.#tableCRUDs[tableSchema.name] = makeServerTableCRUD(tableSchema);\n }\n }\n\n /**\n * Gets the cached serverSchema, or fetches and caches it on first call.\n */\n async #getOrFetchServerSchema(\n dbTransaction: DBTransaction<unknown>,\n ): Promise<ServerSchema> {\n if (!this.#serverSchema) {\n this.#serverSchema = await getServerSchema(dbTransaction, this.#schema);\n }\n return this.#serverSchema;\n }\n\n /**\n * Creates a CRUDExecutor bound to the given transaction and serverSchema.\n * Uses the pre-created TableCRUD methods from construction time.\n */\n createExecutor(\n dbTransaction: DBTransaction<unknown>,\n serverSchema: ServerSchema,\n ): CRUDExecutor {\n const txHolder: WithHiddenTxAndSchema = {\n [dbTxSymbol]: dbTransaction,\n [serverSchemaSymbol]: serverSchema,\n };\n const boundCRUDs = recordProxy(this.#tableCRUDs, tableCRUD =>\n mapValues(tableCRUD, method => method.bind(txHolder)),\n ) as unknown as SchemaCRUD<S>;\n\n return (table: string, kind: CRUDKind, args: unknown) => {\n const tableCRUD = boundCRUDs[table as keyof S['tables']];\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return (tableCRUD as any)[kind](args);\n };\n }\n\n /**\n * Creates a full ServerTransaction.\n * Fetches/caches serverSchema automatically.\n */\n async createTransaction<TWrappedTransaction>(\n dbTransaction: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n ): Promise<TransactionImpl<S, TWrappedTransaction>> {\n const serverSchema = await this.#getOrFetchServerSchema(dbTransaction);\n const executor = this.createExecutor(dbTransaction, serverSchema);\n const mutate = makeTransactionMutate(this.#schema, executor);\n return new TransactionImpl(\n dbTransaction,\n clientID,\n mutationID,\n mutate,\n this.#schema,\n serverSchema,\n );\n }\n}\n\nexport async function makeServerTransaction<\n TSchema extends Schema,\n TWrappedTransaction,\n>(\n dbTransaction: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n schema: TSchema,\n) {\n const serverSchema = await getServerSchema(dbTransaction, schema);\n // Use the internal executor and shared function directly,\n // bypassing the validation in makeMutateCRUD/makeSchemaCRUD\n const executor = makeServerCRUDExecutor(schema, dbTransaction, serverSchema);\n const mutate = makeTransactionMutate(schema, executor);\n return new TransactionImpl(\n dbTransaction,\n clientID,\n mutationID,\n mutate,\n schema,\n serverSchema,\n );\n}\n\n/**\n * @deprecated Use transactions instead.\n *\n * Returns a curried function for backwards compatibility.\n */\nexport function makeSchemaCRUD<S extends Schema>(\n schema: S,\n): (\n dbTransaction: DBTransaction<unknown>,\n serverSchema: ServerSchema,\n) => MutateCRUD<S, true> {\n return (dbTransaction: DBTransaction<unknown>, serverSchema: ServerSchema) =>\n makeCRUDMutate(\n schema,\n true,\n makeServerCRUDExecutor(schema, dbTransaction, serverSchema),\n );\n}\n\nfunction removeUndefined<T extends Record<string, unknown>>(value: T): T {\n const valueWithoutUndefined: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value)) {\n if (val !== undefined) {\n valueWithoutUndefined[key] = val;\n }\n }\n return valueWithoutUndefined as T;\n}\n\n/**\n * Creates a CRUDExecutor for server-side SQL execution.\n *\n * For users with very large schemas it is expensive to re-create\n * all the CRUD mutators for each transaction. Instead, we create\n * the SQL-generating methods once up-front and then bind them to\n * the transaction as requested.\n */\nfunction makeServerCRUDExecutor<S extends Schema>(\n schema: S,\n dbTransaction: DBTransaction<unknown>,\n serverSchema: ServerSchema,\n): CRUDExecutor {\n // Pre-create TableCRUD methods for each table (optimization for large schemas)\n const tableCRUDs: Record<string, TableCRUD<TableSchema>> = {};\n for (const tableSchema of Object.values(schema.tables)) {\n tableCRUDs[tableSchema.name] = makeServerTableCRUD(tableSchema);\n }\n\n // Bind transaction context to the methods\n const txHolder: WithHiddenTxAndSchema = {\n [dbTxSymbol]: dbTransaction,\n [serverSchemaSymbol]: serverSchema,\n };\n const boundCRUDs = recordProxy(tableCRUDs, tableCRUD =>\n mapValues(tableCRUD, method => method.bind(txHolder)),\n ) as unknown as SchemaCRUD<S>;\n\n // Return executor that dispatches to bound methods\n return (table: string, kind: CRUDKind, args: unknown) => {\n const tableCRUD = boundCRUDs[table as keyof S['tables']];\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return (tableCRUD as any)[kind](args);\n };\n}\n\n/**\n * Creates SQL-generating TableCRUD methods for a table.\n * Methods use `this` context to access transaction and server schema.\n */\nfunction makeServerTableCRUD(schema: TableSchema): TableCRUD<TableSchema> {\n return {\n async insert(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n\n const targetedColumns = origAndServerNamesFor(Object.keys(value), schema);\n const primaryKeyColumns = origAndServerNamesFor(\n schema.primaryKey,\n schema,\n );\n\n const stmt = formatPgInternalConvert(\n sql`INSERT INTO ${sql.ident(serverName(schema))} (${sql.join(\n targetedColumns.map(([, serverName]) => sql.ident(serverName)),\n ',',\n )}) VALUES (${sql.join(\n Object.entries(value).map(([col, v]) =>\n sqlInsertValue(v, serverTableSchema[serverNameFor(col, schema)]),\n ),\n ', ',\n )}) ON CONFLICT (${sql.join(\n primaryKeyColumns.map(([, serverName]) => sql.ident(serverName)),\n ', ',\n )}) DO NOTHING`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n async upsert(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n const targetedColumns = origAndServerNamesFor(Object.keys(value), schema);\n const primaryKeyColumns = origAndServerNamesFor(\n schema.primaryKey,\n schema,\n );\n const stmt = formatPgInternalConvert(\n sql`INSERT INTO ${sql.ident(serverName(schema))} (${sql.join(\n targetedColumns.map(([, serverName]) => sql.ident(serverName)),\n ',',\n )}) VALUES (${sql.join(\n Object.entries(value).map(([col, val]) =>\n sqlInsertValue(val, serverTableSchema[serverNameFor(col, schema)]),\n ),\n ', ',\n )}) ON CONFLICT (${sql.join(\n primaryKeyColumns.map(([, serverName]) => sql.ident(serverName)),\n ', ',\n )}) DO UPDATE SET ${sql.join(\n Object.entries(value).map(\n ([col, val]) =>\n sql`${sql.ident(\n schema.columns[col].serverName ?? col,\n )} = ${sqlInsertValue(val, serverTableSchema[serverNameFor(col, schema)])}`,\n ),\n ', ',\n )}`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n async update(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n const targetedColumns = origAndServerNamesFor(Object.keys(value), schema);\n const stmt = formatPgInternalConvert(\n sql`UPDATE ${sql.ident(serverName(schema))} SET ${sql.join(\n targetedColumns.map(\n ([origName, serverName]) =>\n sql`${sql.ident(serverName)} = ${sqlInsertValue(value[origName], serverTableSchema[serverName])}`,\n ),\n ', ',\n )} WHERE ${primaryKeyClause(schema, serverTableSchema, value)}`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n async delete(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n const stmt = formatPgInternalConvert(\n sql`DELETE FROM ${sql.ident(\n serverName(schema),\n )} WHERE ${primaryKeyClause(schema, serverTableSchema, value)}`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n };\n}\n\nfunction serverName(x: {name: string; serverName?: string | undefined}) {\n return x.serverName ?? x.name;\n}\n\nfunction primaryKeyClause(\n schema: TableSchema,\n serverTableSchema: ServerTableSchema,\n row: Record<string, unknown>,\n) {\n const primaryKey = origAndServerNamesFor(schema.primaryKey, schema);\n return sql`${sql.join(\n primaryKey.map(\n ([origName, serverName]) =>\n sql`${sql.ident(serverName)} = ${sqlValue(row[origName], serverTableSchema[serverName])}`,\n ),\n ' AND ',\n )}`;\n}\n\nfunction origAndServerNamesFor(\n originalNames: readonly string[],\n schema: TableSchema,\n): [origName: string, serverName: string][] {\n return originalNames.map(\n name => [name, serverNameFor(name, schema)] as const,\n );\n}\n\nfunction serverNameFor(originalName: string, schema: TableSchema): string {\n const col = schema.columns[originalName];\n assert(\n col,\n `Column ${originalName} was not found in the Zero schema for the table ${schema.name}`,\n );\n return col.serverName ?? originalName;\n}\n\nfunction sqlValue(value: unknown, serverColumnSchema: ServerColumnSchema) {\n return sqlConvertColumnArg(serverColumnSchema, value, false, true);\n}\n\nfunction sqlInsertValue(\n value: unknown,\n serverColumnSchema: ServerColumnSchema,\n) {\n return sqlConvertColumnArg(serverColumnSchema, value, false, false);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"custom.js","names":["#dbTransaction","#schema","#serverSchema","#tableCRUDs","#getOrFetchServerSchema"],"sources":["../../../../zero-server/src/custom.ts"],"sourcesContent":["import {assert} from '../../shared/src/asserts.ts';\nimport {mapValues} from '../../shared/src/objects.ts';\nimport {recordProxy} from '../../shared/src/record-proxy.ts';\nimport {\n formatPgInternalConvert,\n sql,\n sqlConvertColumnArg,\n} from '../../z2s/src/sql.ts';\nimport type {TableSchema} from '../../zero-schema/src/table-schema.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {\n ServerColumnSchema,\n ServerSchema,\n ServerTableSchema,\n} from '../../zero-types/src/server-schema.ts';\nimport {\n type CRUDExecutor,\n type CRUDKind,\n makeCRUDMutate,\n makeTransactionMutate,\n type SchemaCRUD,\n type TableCRUD,\n type TransactionMutate,\n} from '../../zql/src/mutate/crud.ts';\nimport type {\n DBTransaction,\n MutateCRUD,\n ServerTransaction,\n} from '../../zql/src/mutate/custom.ts';\nimport {createRunnableBuilder} from '../../zql/src/query/create-builder.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport type {\n HumanReadable,\n Query,\n RunOptions,\n} from '../../zql/src/query/query.ts';\nimport type {ConditionalSchemaQuery} from '../../zql/src/query/schema-query.ts';\nimport {getServerSchema} from './schema.ts';\n\nexport type CustomMutatorDefs<TDBTransaction> = {\n [namespaceOrKey: string]:\n | CustomMutatorImpl<TDBTransaction>\n | CustomMutatorDefs<TDBTransaction>;\n};\n\nexport type CustomMutatorImpl<\n TDBTransaction,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n TArgs = any,\n Context = unknown,\n> = (tx: TDBTransaction, args: TArgs, ctx: Context) => Promise<void>;\n\n/**\n * QueryDelegate implementation for server-side transactions.\n * Extends QueryDelegateBase to satisfy the QueryDelegate interface,\n * but overrides run() to execute against Postgres and throws on\n * preload()/materialize() which don't make sense server-side.\n */\nclass ServerTransactionQueryDelegate extends QueryDelegateBase {\n readonly #dbTransaction: DBTransaction<unknown>;\n readonly #schema: Schema;\n readonly #serverSchema: ServerSchema;\n\n readonly defaultQueryComplete = true;\n\n constructor(\n dbTransaction: DBTransaction<unknown>,\n schema: Schema,\n serverSchema: ServerSchema,\n ) {\n super();\n this.#dbTransaction = dbTransaction;\n this.#schema = schema;\n this.#serverSchema = serverSchema;\n }\n\n getSource(): never {\n throw new Error('not implemented');\n }\n\n override run<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n _options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n const queryInternals = asQueryInternals(query);\n return this.#dbTransaction.runQuery<TReturn>(\n queryInternals.ast,\n queryInternals.format,\n this.#schema,\n this.#serverSchema,\n );\n }\n\n override preload(): never {\n throw new Error('preload() is not supported in server transactions');\n }\n\n override materialize(): never {\n throw new Error('materialize() is not supported in server transactions');\n }\n}\n\nexport class TransactionImpl<\n TSchema extends Schema,\n TWrappedTransaction,\n> implements ServerTransaction<TSchema, TWrappedTransaction> {\n readonly location = 'server';\n readonly reason = 'authoritative';\n readonly dbTransaction: DBTransaction<TWrappedTransaction>;\n readonly clientID: string;\n readonly mutationID: number;\n readonly mutate: TransactionMutate<TSchema>;\n /**\n * @deprecated Use {@linkcode createBuilder} with `tx.run(zql.table.where(...))` instead.\n */\n readonly query: ConditionalSchemaQuery<TSchema>;\n\n readonly #schema: TSchema;\n readonly #serverSchema: ServerSchema;\n\n constructor(\n dbTransaction: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n mutate: TransactionMutate<TSchema>,\n schema: TSchema,\n serverSchema: ServerSchema,\n ) {\n this.dbTransaction = dbTransaction;\n this.clientID = clientID;\n this.mutationID = mutationID;\n this.mutate = mutate;\n this.#schema = schema;\n this.#serverSchema = serverSchema;\n\n const delegate = new ServerTransactionQueryDelegate(\n dbTransaction,\n schema,\n serverSchema,\n );\n this.query = createRunnableBuilder(delegate, schema);\n }\n\n run<TTable extends keyof TSchema['tables'] & string, TReturn>(\n query: Query<TTable, TSchema, TReturn>,\n _options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n const queryInternals = asQueryInternals(query);\n\n // Execute the query using the database-specific executor\n return this.dbTransaction.runQuery<TReturn>(\n queryInternals.ast,\n queryInternals.format,\n this.#schema,\n this.#serverSchema,\n );\n }\n}\n\nconst dbTxSymbol = Symbol();\n\nconst serverSchemaSymbol = Symbol();\n\ntype WithHiddenTxAndSchema = {\n [dbTxSymbol]: DBTransaction<unknown>;\n [serverSchemaSymbol]: ServerSchema;\n};\n\n/**\n * Factory for creating MutateCRUD instances efficiently.\n *\n * Pre-creates the SQL-generating TableCRUD methods once from the schema,\n * caches the serverSchema after first fetch, and only binds the transaction\n * at transaction time.\n *\n * Use this when you need to create many transactions and want to avoid\n * the overhead of re-creating CRUD methods for each one.\n */\nexport class CRUDMutatorFactory<S extends Schema> {\n readonly #schema: S;\n readonly #tableCRUDs: Record<string, TableCRUD<TableSchema>>;\n #serverSchema: ServerSchema | undefined;\n\n constructor(schema: S) {\n this.#schema = schema;\n // Pre-create TableCRUD methods for each table once\n this.#tableCRUDs = {};\n for (const tableSchema of Object.values(schema.tables)) {\n this.#tableCRUDs[tableSchema.name] = makeServerTableCRUD(tableSchema);\n }\n }\n\n /**\n * Gets the cached serverSchema, or fetches and caches it on first call.\n */\n async #getOrFetchServerSchema(\n dbTransaction: DBTransaction<unknown>,\n ): Promise<ServerSchema> {\n if (!this.#serverSchema) {\n this.#serverSchema = await getServerSchema(dbTransaction, this.#schema);\n }\n return this.#serverSchema;\n }\n\n /**\n * Creates a CRUDExecutor bound to the given transaction and serverSchema.\n * Uses the pre-created TableCRUD methods from construction time.\n */\n createExecutor(\n dbTransaction: DBTransaction<unknown>,\n serverSchema: ServerSchema,\n ): CRUDExecutor {\n const txHolder: WithHiddenTxAndSchema = {\n [dbTxSymbol]: dbTransaction,\n [serverSchemaSymbol]: serverSchema,\n };\n const boundCRUDs = recordProxy(this.#tableCRUDs, tableCRUD =>\n mapValues(tableCRUD, method => method.bind(txHolder)),\n ) as unknown as SchemaCRUD<S>;\n\n return (table: string, kind: CRUDKind, args: unknown) => {\n const tableCRUD = boundCRUDs[table as keyof S['tables']];\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return (tableCRUD as any)[kind](args);\n };\n }\n\n /**\n * Creates a full ServerTransaction.\n * Fetches/caches serverSchema automatically.\n */\n async createTransaction<TWrappedTransaction>(\n dbTransaction: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n ): Promise<TransactionImpl<S, TWrappedTransaction>> {\n const serverSchema = await this.#getOrFetchServerSchema(dbTransaction);\n const executor = this.createExecutor(dbTransaction, serverSchema);\n const mutate = makeTransactionMutate(this.#schema, executor);\n return new TransactionImpl(\n dbTransaction,\n clientID,\n mutationID,\n mutate,\n this.#schema,\n serverSchema,\n );\n }\n}\n\nexport async function makeServerTransaction<\n TSchema extends Schema,\n TWrappedTransaction,\n>(\n dbTransaction: DBTransaction<TWrappedTransaction>,\n clientID: string,\n mutationID: number,\n schema: TSchema,\n) {\n const serverSchema = await getServerSchema(dbTransaction, schema);\n // Use the internal executor and shared function directly,\n // bypassing the validation in makeMutateCRUD/makeSchemaCRUD\n const executor = makeServerCRUDExecutor(schema, dbTransaction, serverSchema);\n const mutate = makeTransactionMutate(schema, executor);\n return new TransactionImpl(\n dbTransaction,\n clientID,\n mutationID,\n mutate,\n schema,\n serverSchema,\n );\n}\n\n/**\n * @deprecated Use transactions instead.\n *\n * Returns a curried function for backwards compatibility.\n */\nexport function makeSchemaCRUD<S extends Schema>(\n schema: S,\n): (\n dbTransaction: DBTransaction<unknown>,\n serverSchema: ServerSchema,\n) => MutateCRUD<S, true> {\n return (dbTransaction: DBTransaction<unknown>, serverSchema: ServerSchema) =>\n makeCRUDMutate(\n schema,\n true,\n makeServerCRUDExecutor(schema, dbTransaction, serverSchema),\n );\n}\n\nfunction removeUndefined<T extends Record<string, unknown>>(value: T): T {\n const valueWithoutUndefined: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value)) {\n if (val !== undefined) {\n valueWithoutUndefined[key] = val;\n }\n }\n return valueWithoutUndefined as T;\n}\n\n/**\n * Creates a CRUDExecutor for server-side SQL execution.\n *\n * For users with very large schemas it is expensive to re-create\n * all the CRUD mutators for each transaction. Instead, we create\n * the SQL-generating methods once up-front and then bind them to\n * the transaction as requested.\n */\nfunction makeServerCRUDExecutor<S extends Schema>(\n schema: S,\n dbTransaction: DBTransaction<unknown>,\n serverSchema: ServerSchema,\n): CRUDExecutor {\n // Pre-create TableCRUD methods for each table (optimization for large schemas)\n const tableCRUDs: Record<string, TableCRUD<TableSchema>> = {};\n for (const tableSchema of Object.values(schema.tables)) {\n tableCRUDs[tableSchema.name] = makeServerTableCRUD(tableSchema);\n }\n\n // Bind transaction context to the methods\n const txHolder: WithHiddenTxAndSchema = {\n [dbTxSymbol]: dbTransaction,\n [serverSchemaSymbol]: serverSchema,\n };\n const boundCRUDs = recordProxy(tableCRUDs, tableCRUD =>\n mapValues(tableCRUD, method => method.bind(txHolder)),\n ) as unknown as SchemaCRUD<S>;\n\n // Return executor that dispatches to bound methods\n return (table: string, kind: CRUDKind, args: unknown) => {\n const tableCRUD = boundCRUDs[table as keyof S['tables']];\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n return (tableCRUD as any)[kind](args);\n };\n}\n\n/**\n * Creates SQL-generating TableCRUD methods for a table.\n * Methods use `this` context to access transaction and server schema.\n */\nfunction makeServerTableCRUD(schema: TableSchema): TableCRUD<TableSchema> {\n return {\n async insert(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n\n const targetedColumns = origAndServerNamesFor(Object.keys(value), schema);\n const primaryKeyColumns = origAndServerNamesFor(\n schema.primaryKey,\n schema,\n );\n\n const stmt = formatPgInternalConvert(\n sql`INSERT INTO ${sql.ident(serverName(schema))} (${sql.join(\n targetedColumns.map(([, serverName]) => sql.ident(serverName)),\n ',',\n )}) VALUES (${sql.join(\n Object.entries(value).map(([col, v]) =>\n sqlInsertValue(v, serverTableSchema[serverNameFor(col, schema)]),\n ),\n ', ',\n )}) ON CONFLICT (${sql.join(\n primaryKeyColumns.map(([, serverName]) => sql.ident(serverName)),\n ', ',\n )}) DO NOTHING`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n async upsert(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n const targetedColumns = origAndServerNamesFor(Object.keys(value), schema);\n const primaryKeyColumns = origAndServerNamesFor(\n schema.primaryKey,\n schema,\n );\n const updatedEntries = nonPrimaryKeyEntries(value, schema);\n const conflictAction =\n updatedEntries.length === 0\n ? sql`DO NOTHING`\n : sql`DO UPDATE SET ${sql.join(\n updatedEntries.map(\n ([col, val]) =>\n sql`${sql.ident(\n schema.columns[col].serverName ?? col,\n )} = ${sqlInsertValue(val, serverTableSchema[serverNameFor(col, schema)])}`,\n ),\n ', ',\n )}`;\n const stmt = formatPgInternalConvert(\n sql`INSERT INTO ${sql.ident(serverName(schema))} (${sql.join(\n targetedColumns.map(([, serverName]) => sql.ident(serverName)),\n ',',\n )}) VALUES (${sql.join(\n Object.entries(value).map(([col, val]) =>\n sqlInsertValue(val, serverTableSchema[serverNameFor(col, schema)]),\n ),\n ', ',\n )}) ON CONFLICT (${sql.join(\n primaryKeyColumns.map(([, serverName]) => sql.ident(serverName)),\n ', ',\n )}) ${conflictAction}`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n async update(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n const targetedColumns = origAndServerNamesFor(\n nonPrimaryKeyEntries(value, schema).map(([name]) => name),\n schema,\n );\n if (targetedColumns.length === 0) {\n return;\n }\n const stmt = formatPgInternalConvert(\n sql`UPDATE ${sql.ident(serverName(schema))} SET ${sql.join(\n targetedColumns.map(\n ([origName, serverName]) =>\n sql`${sql.ident(serverName)} = ${sqlInsertValue(value[origName], serverTableSchema[serverName])}`,\n ),\n ', ',\n )} WHERE ${primaryKeyClause(schema, serverTableSchema, value)}`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n async delete(this: WithHiddenTxAndSchema, value) {\n value = removeUndefined(value);\n const serverTableSchema = this[serverSchemaSymbol][serverName(schema)];\n const stmt = formatPgInternalConvert(\n sql`DELETE FROM ${sql.ident(\n serverName(schema),\n )} WHERE ${primaryKeyClause(schema, serverTableSchema, value)}`,\n );\n const tx = this[dbTxSymbol];\n await tx.query(stmt.text, stmt.values);\n },\n };\n}\n\nfunction nonPrimaryKeyEntries(\n value: Record<string, unknown>,\n schema: TableSchema,\n) {\n return Object.entries(value).filter(\n ([name]) => !schema.primaryKey.includes(name),\n );\n}\n\nfunction serverName(x: {name: string; serverName?: string | undefined}) {\n return x.serverName ?? x.name;\n}\n\nfunction primaryKeyClause(\n schema: TableSchema,\n serverTableSchema: ServerTableSchema,\n row: Record<string, unknown>,\n) {\n const primaryKey = origAndServerNamesFor(schema.primaryKey, schema);\n return sql`${sql.join(\n primaryKey.map(\n ([origName, serverName]) =>\n sql`${sql.ident(serverName)} = ${sqlValue(row[origName], serverTableSchema[serverName])}`,\n ),\n ' AND ',\n )}`;\n}\n\nfunction origAndServerNamesFor(\n originalNames: readonly string[],\n schema: TableSchema,\n): [origName: string, serverName: string][] {\n return originalNames.map(\n name => [name, serverNameFor(name, schema)] as const,\n );\n}\n\nfunction serverNameFor(originalName: string, schema: TableSchema): string {\n const col = schema.columns[originalName];\n assert(\n col,\n `Column ${originalName} was not found in the Zero schema for the table ${schema.name}`,\n );\n return col.serverName ?? originalName;\n}\n\nfunction sqlValue(value: unknown, serverColumnSchema: ServerColumnSchema) {\n return sqlConvertColumnArg(serverColumnSchema, value, false, true);\n}\n\nfunction sqlInsertValue(\n value: unknown,\n serverColumnSchema: ServerColumnSchema,\n) {\n return sqlConvertColumnArg(serverColumnSchema, value, false, false);\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,14 @@ import type { Schema } from '../../zero-types/src/schema.ts';
|
|
|
2
2
|
import type { ServerSchema } from '../../zero-types/src/server-schema.ts';
|
|
3
3
|
import type { DBTransaction } from '../../zql/src/mutate/custom.ts';
|
|
4
4
|
export declare function getServerSchema<S extends Schema>(dbTransaction: DBTransaction<unknown>, schema: S): Promise<ServerSchema>;
|
|
5
|
+
/**
|
|
6
|
+
* The query behind {@linkcode getServerSchema}, exported so that it can be
|
|
7
|
+
* diffed against the `information_schema` query it replaced.
|
|
8
|
+
*/
|
|
9
|
+
export declare function serverSchemaQuery(schemaTablePairs: readonly (readonly [string, string])[]): {
|
|
10
|
+
text: string;
|
|
11
|
+
values: unknown[];
|
|
12
|
+
};
|
|
5
13
|
export type SchemaIncompatibilityError = {
|
|
6
14
|
type: 'typeError';
|
|
7
15
|
table: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../zero-server/src/schema.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAC3D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAkBlE,wBAAsB,eAAe,CAAC,CAAC,SAAS,MAAM,EACpD,aAAa,EAAE,aAAa,CAAC,OAAO,CAAC,EACrC,MAAM,EAAE,CAAC,GACR,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../zero-server/src/schema.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAC3D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAkBlE,wBAAsB,eAAe,CAAC,CAAC,SAAS,MAAM,EACpD,aAAa,EAAE,aAAa,CAAC,OAAO,CAAC,EACrC,MAAM,EAAE,CAAC,GACR,OAAO,CAAC,YAAY,CAAC,CAgFvB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,gBAAgB,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,GACvD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAC,CAiFnC;AAwCD,MAAM,MAAM,0BAA0B,GAClC;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GACD;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,YAAY,GACzB,0BAA0B,EAAE,CAkD9B"}
|
|
@@ -13,30 +13,7 @@ async function getServerSchema(dbTransaction, schema) {
|
|
|
13
13
|
return schemaTablePair;
|
|
14
14
|
});
|
|
15
15
|
if (schemaTablePairs.length === 0) return {};
|
|
16
|
-
const { text, values } =
|
|
17
|
-
SELECT
|
|
18
|
-
c.table_schema::text AS schema,
|
|
19
|
-
c.table_name::text AS table,
|
|
20
|
-
c.column_name::text AS column,
|
|
21
|
-
c.data_type::text AS "dataType",
|
|
22
|
-
c.character_maximum_length AS length,
|
|
23
|
-
c.numeric_precision AS precision,
|
|
24
|
-
c.numeric_scale AS scale,
|
|
25
|
-
t.typtype::text AS typtype,
|
|
26
|
-
t.typname::text AS typename,
|
|
27
|
-
CASE WHEN t.typelem <> 0 THEN et.typtype::text ELSE NULL END AS "elemTyptype",
|
|
28
|
-
CASE WHEN t.typelem <> 0 THEN et.typname::text ELSE NULL END AS "elemTypname"
|
|
29
|
-
FROM
|
|
30
|
-
information_schema.columns c
|
|
31
|
-
JOIN
|
|
32
|
-
pg_catalog.pg_type t ON c.udt_name = t.typname
|
|
33
|
-
LEFT JOIN
|
|
34
|
-
pg_catalog.pg_type et ON t.typelem = et.oid
|
|
35
|
-
JOIN
|
|
36
|
-
pg_catalog.pg_namespace n ON t.typnamespace = n.oid
|
|
37
|
-
WHERE
|
|
38
|
-
(c.table_schema, c.table_name) IN (${sql.join(schemaTablePairs.map(([schema, table]) => sql`(${schema}::text, ${table}::text)`), ",")})
|
|
39
|
-
`);
|
|
16
|
+
const { text, values } = serverSchemaQuery(schemaTablePairs);
|
|
40
17
|
const results = await dbTransaction.query(text, values);
|
|
41
18
|
const serverSchema = {};
|
|
42
19
|
for (const row of results) {
|
|
@@ -64,6 +41,68 @@ async function getServerSchema(dbTransaction, schema) {
|
|
|
64
41
|
assert(errors.length === 0, () => makeSchemaIncompatibleErrorMessage(errors));
|
|
65
42
|
return serverSchema;
|
|
66
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The query behind {@linkcode getServerSchema}, exported so that it can be
|
|
46
|
+
* diffed against the `information_schema` query it replaced.
|
|
47
|
+
*/
|
|
48
|
+
function serverSchemaQuery(schemaTablePairs) {
|
|
49
|
+
return formatPg(sql`
|
|
50
|
+
SELECT
|
|
51
|
+
n.nspname::text AS schema,
|
|
52
|
+
cl.relname::text AS table,
|
|
53
|
+
a.attname::text AS column,
|
|
54
|
+
CASE
|
|
55
|
+
WHEN ut.typelem <> 0 AND ut.typlen = -1 THEN 'ARRAY'
|
|
56
|
+
WHEN nut.nspname = 'pg_catalog' THEN pg_catalog.format_type(ut.oid, NULL)
|
|
57
|
+
ELSE 'USER-DEFINED'
|
|
58
|
+
END::text AS "dataType",
|
|
59
|
+
CASE
|
|
60
|
+
WHEN tm.typmod = -1 THEN NULL
|
|
61
|
+
WHEN ut.oid IN ('pg_catalog.bpchar'::regtype, 'pg_catalog.varchar'::regtype) THEN tm.typmod - 4
|
|
62
|
+
WHEN ut.oid IN ('pg_catalog.bit'::regtype, 'pg_catalog.varbit'::regtype) THEN tm.typmod
|
|
63
|
+
END AS length,
|
|
64
|
+
CASE ut.oid
|
|
65
|
+
WHEN 'pg_catalog.int2'::regtype THEN 16
|
|
66
|
+
WHEN 'pg_catalog.int4'::regtype THEN 32
|
|
67
|
+
WHEN 'pg_catalog.int8'::regtype THEN 64
|
|
68
|
+
WHEN 'pg_catalog.float4'::regtype THEN 24
|
|
69
|
+
WHEN 'pg_catalog.float8'::regtype THEN 53
|
|
70
|
+
WHEN 'pg_catalog.numeric'::regtype THEN CASE WHEN tm.typmod = -1 THEN NULL ELSE ((tm.typmod - 4) >> 16) & 65535 END
|
|
71
|
+
END AS precision,
|
|
72
|
+
CASE
|
|
73
|
+
WHEN ut.oid IN ('pg_catalog.int2'::regtype, 'pg_catalog.int4'::regtype, 'pg_catalog.int8'::regtype) THEN 0
|
|
74
|
+
WHEN ut.oid = 'pg_catalog.numeric'::regtype AND tm.typmod <> -1 THEN (tm.typmod - 4) & 65535
|
|
75
|
+
END AS scale,
|
|
76
|
+
ut.typtype::text AS typtype,
|
|
77
|
+
ut.typname::text AS typename,
|
|
78
|
+
CASE WHEN ut.typelem <> 0 THEN et.typtype::text ELSE NULL END AS "elemTyptype",
|
|
79
|
+
CASE WHEN ut.typelem <> 0 THEN et.typname::text ELSE NULL END AS "elemTypname"
|
|
80
|
+
FROM
|
|
81
|
+
pg_catalog.pg_attribute a
|
|
82
|
+
JOIN
|
|
83
|
+
pg_catalog.pg_class cl ON cl.oid = a.attrelid
|
|
84
|
+
JOIN
|
|
85
|
+
pg_catalog.pg_namespace n ON n.oid = cl.relnamespace
|
|
86
|
+
JOIN
|
|
87
|
+
pg_catalog.pg_type t ON t.oid = a.atttypid
|
|
88
|
+
JOIN
|
|
89
|
+
pg_catalog.pg_type ut ON ut.oid = CASE WHEN t.typtype = 'd' THEN t.typbasetype ELSE t.oid END
|
|
90
|
+
JOIN
|
|
91
|
+
pg_catalog.pg_namespace nut ON nut.oid = ut.typnamespace
|
|
92
|
+
LEFT JOIN
|
|
93
|
+
pg_catalog.pg_type et ON et.oid = ut.typelem
|
|
94
|
+
CROSS JOIN LATERAL
|
|
95
|
+
(SELECT CASE WHEN t.typtype = 'd' THEN t.typtypmod ELSE a.atttypmod END AS typmod) tm
|
|
96
|
+
WHERE
|
|
97
|
+
a.attnum > 0
|
|
98
|
+
AND NOT a.attisdropped
|
|
99
|
+
AND cl.relkind IN ('r', 'v', 'f', 'p')
|
|
100
|
+
AND NOT pg_catalog.pg_is_other_temp_schema(n.oid)
|
|
101
|
+
AND (pg_catalog.pg_has_role(cl.relowner, 'USAGE')
|
|
102
|
+
OR pg_catalog.has_column_privilege(cl.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'))
|
|
103
|
+
AND (n.nspname, cl.relname) IN (${sql.join(schemaTablePairs.map(([schema, table]) => sql`(${schema}::text, ${table}::text)`), ",")})
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
67
106
|
function makeSchemaIncompatibleErrorMessage(errors) {
|
|
68
107
|
if (errors.length === 0) return "No schema incompatibilities found.";
|
|
69
108
|
const messages = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../../zero-server/src/schema.ts"],"sourcesContent":["import {assert} from '../../shared/src/asserts.ts';\nimport type {Enum} from '../../shared/src/enum.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport * as PostgresTypeClass from '../../zero-cache/src/db/postgres-type-class-enum.ts';\nimport {dataTypeToZqlValueType} from '../../zero-cache/src/types/pg-data-type.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {ServerSchema} from '../../zero-types/src/server-schema.ts';\nimport type {DBTransaction} from '../../zql/src/mutate/custom.ts';\n\ntype PostgresTypeClass = Enum<typeof PostgresTypeClass>;\n\ntype ServerSchemaRow = {\n schema: string;\n table: string;\n column: string;\n dataType: string;\n length: string | null;\n precision: string | null;\n scale: string | null;\n typtype: PostgresTypeClass;\n typename: string;\n elemTyptype: PostgresTypeClass | null;\n elemTypname: string | null;\n};\n\nexport async function getServerSchema<S extends Schema>(\n dbTransaction: DBTransaction<unknown>,\n schema: S,\n): Promise<ServerSchema> {\n const schemaTablePairs: [string, string][] = Object.values(schema.tables).map(\n ({name, serverName}) => {\n let schemaTablePair: [string, string] = ['public', serverName ?? name];\n if (serverName) {\n const firstPeriod = serverName.indexOf('.');\n if (firstPeriod > -1) {\n schemaTablePair = [\n serverName.substring(0, firstPeriod),\n serverName.substring(firstPeriod + 1, serverName.length),\n ];\n }\n }\n return schemaTablePair;\n },\n );\n\n if (schemaTablePairs.length === 0) {\n return {}; // No pairs to query for\n }\n\n // Cast all inputs to text and all outputs to text to avoid\n // any conversions customer's DBTransaction impl has on other types.\n const inClause = sql.join(\n schemaTablePairs.map(\n ([schema, table]) => sql`(${schema}::text, ${table}::text)`,\n ),\n ',',\n );\n const query = sql`\n SELECT\n c.table_schema::text AS schema,\n c.table_name::text AS table,\n c.column_name::text AS column,\n c.data_type::text AS \"dataType\",\n c.character_maximum_length AS length,\n c.numeric_precision AS precision,\n c.numeric_scale AS scale,\n t.typtype::text AS typtype,\n t.typname::text AS typename,\n CASE WHEN t.typelem <> 0 THEN et.typtype::text ELSE NULL END AS \"elemTyptype\",\n CASE WHEN t.typelem <> 0 THEN et.typname::text ELSE NULL END AS \"elemTypname\"\n FROM\n information_schema.columns c\n JOIN\n pg_catalog.pg_type t ON c.udt_name = t.typname\n LEFT JOIN\n pg_catalog.pg_type et ON t.typelem = et.oid\n JOIN\n pg_catalog.pg_namespace n ON t.typnamespace = n.oid\n WHERE\n (c.table_schema, c.table_name) IN (${inClause})\n `;\n const {text, values} = formatPg(query);\n const results: Iterable<ServerSchemaRow> = (await dbTransaction.query(\n text,\n values,\n )) as Iterable<ServerSchemaRow>;\n\n const serverSchema: ServerSchema = {};\n\n for (const row of results) {\n const tableName =\n row.schema === 'public' ? row.table : `${row.schema}.${row.table}`;\n let tableSchema = serverSchema[tableName];\n if (!tableSchema) {\n tableSchema = {};\n serverSchema[tableName] = tableSchema;\n }\n const isArray = row.elemTyptype !== null;\n const isEnum = (row.elemTyptype ?? row.typtype) === PostgresTypeClass.Enum;\n let type = row.dataType.toLowerCase();\n if (isArray) {\n type = must(\n row.elemTypname,\n `Array column \"${row.column}\" in table \"${tableName}\" is missing element type name`,\n );\n } else if (isEnum) {\n type = row.typename;\n } else if (\n type === 'user-defined' &&\n dataTypeToZqlValueType(row.typename, false, false) !== undefined\n ) {\n type = row.typename;\n }\n if (\n (type === 'bpchar' ||\n type === 'character' ||\n type === 'character varying' ||\n type === 'varchar') &&\n row.length\n ) {\n type = `${type}(${row.length})`;\n }\n if (\n (type === 'numeric' || type === 'decimal') &&\n row.precision !== null &&\n row.scale !== null\n ) {\n type = `${type}(${row.precision}, ${row.scale})`;\n }\n tableSchema[row.column] = {\n type,\n isEnum,\n isArray,\n };\n }\n\n const errors = checkSchemasAreCompatible(schema, serverSchema);\n assert(errors.length === 0, () => makeSchemaIncompatibleErrorMessage(errors));\n\n return serverSchema;\n}\n\nfunction makeSchemaIncompatibleErrorMessage(\n errors: SchemaIncompatibilityError[],\n) {\n if (errors.length === 0) {\n return 'No schema incompatibilities found.';\n }\n\n const messages: string[] = [];\n\n for (const error of errors) {\n switch (error.type) {\n case 'missingTable':\n messages.push(\n `Table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'missingColumn':\n messages.push(\n `Column \"${error.column}\" in table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'typeError':\n messages.push(\n `Type mismatch for column \"${error.column}\" in table \"${error.table}\": ${error.requiredType === undefined ? `${error.pgType} is currently unsupported in Zero. Please file a bug at https://bugs.rocicorp.dev/` : `${error.pgType} should be mapped to ${error.requiredType} in Zero not ${error.declaredType}.`}`,\n );\n break;\n }\n }\n\n return [\n 'Schema incompatibility detected between your zero schema definition and the database:',\n '',\n ...messages.map(msg => ` - ${msg}`),\n '',\n 'Please update your schema definition to match the database or migrate your database to match the schema.',\n ].join('\\n');\n}\n\nexport type SchemaIncompatibilityError =\n | {\n type: 'typeError';\n table: string;\n column: string;\n pgType: string;\n declaredType: string;\n requiredType: string | undefined;\n }\n | {\n type: 'missingColumn';\n table: string;\n column: string;\n }\n | {\n type: 'missingTable';\n table: string;\n };\n\nexport function checkSchemasAreCompatible(\n schema: Schema,\n serverSchema: ServerSchema,\n): SchemaIncompatibilityError[] {\n const errors: SchemaIncompatibilityError[] = [];\n // Check that all tables in schema exist in serverSchema\n for (const table of Object.values(schema.tables)) {\n const serverTableName = table.serverName ?? table.name;\n\n if (!serverSchema[serverTableName]) {\n errors.push({\n type: 'missingTable',\n table: serverTableName,\n });\n continue;\n }\n\n // Check that all columns in the table exist in serverSchema\n for (const [columnName, column] of Object.entries(table.columns)) {\n const serverColumnName = column.serverName ?? columnName;\n\n if (!serverSchema[serverTableName][serverColumnName]) {\n errors.push({\n type: 'missingColumn',\n table: serverTableName,\n column: serverColumnName,\n });\n continue;\n }\n\n // Check type compatibility\n const serverColumn = serverSchema[serverTableName][serverColumnName];\n const declaredType = column.type;\n const pgType = serverColumn.type;\n const requiredType = dataTypeToZqlValueType(\n pgType,\n serverColumn.isEnum,\n serverColumn.isArray,\n );\n if (requiredType !== declaredType) {\n errors.push({\n type: 'typeError',\n table: serverTableName,\n column: serverColumnName,\n pgType,\n declaredType,\n requiredType,\n });\n }\n }\n }\n\n return errors;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../../zero-server/src/schema.ts"],"sourcesContent":["import {assert} from '../../shared/src/asserts.ts';\nimport type {Enum} from '../../shared/src/enum.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {formatPg, sql} from '../../z2s/src/sql.ts';\nimport * as PostgresTypeClass from '../../zero-cache/src/db/postgres-type-class-enum.ts';\nimport {dataTypeToZqlValueType} from '../../zero-cache/src/types/pg-data-type.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport type {ServerSchema} from '../../zero-types/src/server-schema.ts';\nimport type {DBTransaction} from '../../zql/src/mutate/custom.ts';\n\ntype PostgresTypeClass = Enum<typeof PostgresTypeClass>;\n\ntype ServerSchemaRow = {\n schema: string;\n table: string;\n column: string;\n dataType: string;\n length: string | null;\n precision: string | null;\n scale: string | null;\n typtype: PostgresTypeClass;\n typename: string;\n elemTyptype: PostgresTypeClass | null;\n elemTypname: string | null;\n};\n\nexport async function getServerSchema<S extends Schema>(\n dbTransaction: DBTransaction<unknown>,\n schema: S,\n): Promise<ServerSchema> {\n const schemaTablePairs: [string, string][] = Object.values(schema.tables).map(\n ({name, serverName}) => {\n let schemaTablePair: [string, string] = ['public', serverName ?? name];\n if (serverName) {\n const firstPeriod = serverName.indexOf('.');\n if (firstPeriod > -1) {\n schemaTablePair = [\n serverName.substring(0, firstPeriod),\n serverName.substring(firstPeriod + 1, serverName.length),\n ];\n }\n }\n return schemaTablePair;\n },\n );\n\n if (schemaTablePairs.length === 0) {\n return {}; // No pairs to query for\n }\n\n const {text, values} = serverSchemaQuery(schemaTablePairs);\n const results: Iterable<ServerSchemaRow> = (await dbTransaction.query(\n text,\n values,\n )) as Iterable<ServerSchemaRow>;\n\n const serverSchema: ServerSchema = {};\n\n for (const row of results) {\n const tableName =\n row.schema === 'public' ? row.table : `${row.schema}.${row.table}`;\n let tableSchema = serverSchema[tableName];\n if (!tableSchema) {\n tableSchema = {};\n serverSchema[tableName] = tableSchema;\n }\n const isArray = row.elemTyptype !== null;\n const isEnum = (row.elemTyptype ?? row.typtype) === PostgresTypeClass.Enum;\n let type = row.dataType.toLowerCase();\n if (isArray) {\n type = must(\n row.elemTypname,\n `Array column \"${row.column}\" in table \"${tableName}\" is missing element type name`,\n );\n } else if (isEnum) {\n type = row.typename;\n } else if (\n type === 'user-defined' &&\n dataTypeToZqlValueType(row.typename, false, false) !== undefined\n ) {\n type = row.typename;\n }\n if (\n (type === 'bpchar' ||\n type === 'character' ||\n type === 'character varying' ||\n type === 'varchar') &&\n row.length\n ) {\n type = `${type}(${row.length})`;\n }\n if (\n (type === 'numeric' || type === 'decimal') &&\n row.precision !== null &&\n row.scale !== null\n ) {\n type = `${type}(${row.precision}, ${row.scale})`;\n }\n tableSchema[row.column] = {\n type,\n isEnum,\n isArray,\n };\n }\n\n const errors = checkSchemasAreCompatible(schema, serverSchema);\n assert(errors.length === 0, () => makeSchemaIncompatibleErrorMessage(errors));\n\n return serverSchema;\n}\n\n/**\n * The query behind {@linkcode getServerSchema}, exported so that it can be\n * diffed against the `information_schema` query it replaced.\n */\nexport function serverSchemaQuery(\n schemaTablePairs: readonly (readonly [string, string])[],\n): {text: string; values: unknown[]} {\n // Cast all inputs to text and all outputs to text to avoid\n // any conversions customer's DBTransaction impl has on other types.\n const inClause = sql.join(\n schemaTablePairs.map(\n ([schema, table]) => sql`(${schema}::text, ${table}::text)`,\n ),\n ',',\n );\n // Read pg_catalog directly instead of information_schema.columns. The view\n // derives every column it returns through the information_schema._pg_*\n // functions and a per column privilege check, which is several times slower\n // than the query below. Server processes run this on the first transaction\n // they serve, so serverless deployments pay for it on every cold start.\n //\n // The expressions below reproduce the parts of the view's definition that we\n // select, down to the int4 wire types of length, precision and scale: the\n // _pg_char_max_length / _pg_numeric_precision / _pg_numeric_scale arithmetic,\n // the single level domain unwrap that data_type and udt_name perform (`ut`),\n // and the view's own relkind, attnum and privilege filters. pg_type is joined\n // by OID rather than by udt_name, which additionally avoids duplicate rows\n // when two schemas declare a type with the same name. The types those\n // functions special case are named through regtype, which the parser resolves\n // while planning.\n const query = sql`\n SELECT\n n.nspname::text AS schema,\n cl.relname::text AS table,\n a.attname::text AS column,\n CASE\n WHEN ut.typelem <> 0 AND ut.typlen = -1 THEN 'ARRAY'\n WHEN nut.nspname = 'pg_catalog' THEN pg_catalog.format_type(ut.oid, NULL)\n ELSE 'USER-DEFINED'\n END::text AS \"dataType\",\n CASE\n WHEN tm.typmod = -1 THEN NULL\n WHEN ut.oid IN ('pg_catalog.bpchar'::regtype, 'pg_catalog.varchar'::regtype) THEN tm.typmod - 4\n WHEN ut.oid IN ('pg_catalog.bit'::regtype, 'pg_catalog.varbit'::regtype) THEN tm.typmod\n END AS length,\n CASE ut.oid\n WHEN 'pg_catalog.int2'::regtype THEN 16\n WHEN 'pg_catalog.int4'::regtype THEN 32\n WHEN 'pg_catalog.int8'::regtype THEN 64\n WHEN 'pg_catalog.float4'::regtype THEN 24\n WHEN 'pg_catalog.float8'::regtype THEN 53\n WHEN 'pg_catalog.numeric'::regtype THEN CASE WHEN tm.typmod = -1 THEN NULL ELSE ((tm.typmod - 4) >> 16) & 65535 END\n END AS precision,\n CASE\n WHEN ut.oid IN ('pg_catalog.int2'::regtype, 'pg_catalog.int4'::regtype, 'pg_catalog.int8'::regtype) THEN 0\n WHEN ut.oid = 'pg_catalog.numeric'::regtype AND tm.typmod <> -1 THEN (tm.typmod - 4) & 65535\n END AS scale,\n ut.typtype::text AS typtype,\n ut.typname::text AS typename,\n CASE WHEN ut.typelem <> 0 THEN et.typtype::text ELSE NULL END AS \"elemTyptype\",\n CASE WHEN ut.typelem <> 0 THEN et.typname::text ELSE NULL END AS \"elemTypname\"\n FROM\n pg_catalog.pg_attribute a\n JOIN\n pg_catalog.pg_class cl ON cl.oid = a.attrelid\n JOIN\n pg_catalog.pg_namespace n ON n.oid = cl.relnamespace\n JOIN\n pg_catalog.pg_type t ON t.oid = a.atttypid\n JOIN\n pg_catalog.pg_type ut ON ut.oid = CASE WHEN t.typtype = 'd' THEN t.typbasetype ELSE t.oid END\n JOIN\n pg_catalog.pg_namespace nut ON nut.oid = ut.typnamespace\n LEFT JOIN\n pg_catalog.pg_type et ON et.oid = ut.typelem\n CROSS JOIN LATERAL\n (SELECT CASE WHEN t.typtype = 'd' THEN t.typtypmod ELSE a.atttypmod END AS typmod) tm\n WHERE\n a.attnum > 0\n AND NOT a.attisdropped\n AND cl.relkind IN ('r', 'v', 'f', 'p')\n AND NOT pg_catalog.pg_is_other_temp_schema(n.oid)\n AND (pg_catalog.pg_has_role(cl.relowner, 'USAGE')\n OR pg_catalog.has_column_privilege(cl.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'))\n AND (n.nspname, cl.relname) IN (${inClause})\n `;\n return formatPg(query);\n}\n\nfunction makeSchemaIncompatibleErrorMessage(\n errors: SchemaIncompatibilityError[],\n) {\n if (errors.length === 0) {\n return 'No schema incompatibilities found.';\n }\n\n const messages: string[] = [];\n\n for (const error of errors) {\n switch (error.type) {\n case 'missingTable':\n messages.push(\n `Table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'missingColumn':\n messages.push(\n `Column \"${error.column}\" in table \"${error.table}\" is defined in your zero schema but does not exist in the database.`,\n );\n break;\n case 'typeError':\n messages.push(\n `Type mismatch for column \"${error.column}\" in table \"${error.table}\": ${error.requiredType === undefined ? `${error.pgType} is currently unsupported in Zero. Please file a bug at https://bugs.rocicorp.dev/` : `${error.pgType} should be mapped to ${error.requiredType} in Zero not ${error.declaredType}.`}`,\n );\n break;\n }\n }\n\n return [\n 'Schema incompatibility detected between your zero schema definition and the database:',\n '',\n ...messages.map(msg => ` - ${msg}`),\n '',\n 'Please update your schema definition to match the database or migrate your database to match the schema.',\n ].join('\\n');\n}\n\nexport type SchemaIncompatibilityError =\n | {\n type: 'typeError';\n table: string;\n column: string;\n pgType: string;\n declaredType: string;\n requiredType: string | undefined;\n }\n | {\n type: 'missingColumn';\n table: string;\n column: string;\n }\n | {\n type: 'missingTable';\n table: string;\n };\n\nexport function checkSchemasAreCompatible(\n schema: Schema,\n serverSchema: ServerSchema,\n): SchemaIncompatibilityError[] {\n const errors: SchemaIncompatibilityError[] = [];\n // Check that all tables in schema exist in serverSchema\n for (const table of Object.values(schema.tables)) {\n const serverTableName = table.serverName ?? table.name;\n\n if (!serverSchema[serverTableName]) {\n errors.push({\n type: 'missingTable',\n table: serverTableName,\n });\n continue;\n }\n\n // Check that all columns in the table exist in serverSchema\n for (const [columnName, column] of Object.entries(table.columns)) {\n const serverColumnName = column.serverName ?? columnName;\n\n if (!serverSchema[serverTableName][serverColumnName]) {\n errors.push({\n type: 'missingColumn',\n table: serverTableName,\n column: serverColumnName,\n });\n continue;\n }\n\n // Check type compatibility\n const serverColumn = serverSchema[serverTableName][serverColumnName];\n const declaredType = column.type;\n const pgType = serverColumn.type;\n const requiredType = dataTypeToZqlValueType(\n pgType,\n serverColumn.isEnum,\n serverColumn.isArray,\n );\n if (requiredType !== declaredType) {\n errors.push({\n type: 'typeError',\n table: serverTableName,\n column: serverColumnName,\n pgType,\n declaredType,\n requiredType,\n });\n }\n }\n }\n\n return errors;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-delegate-base.d.ts","sourceRoot":"","sources":["../../../../../zql/src/query/query-delegate-base.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"query-delegate-base.d.ts","sourceRoot":"","sources":["../../../../../zql/src/query/query-delegate-base.ts"],"names":[],"mappings":"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"}
|
|
@@ -160,6 +160,7 @@ function materializeImpl(query, delegate, factory = arrayViewFactory, options) {
|
|
|
160
160
|
const queryID = customQueryID ? hashOfNameAndArgs(customQueryID.name, customQueryID.args) : queryHash;
|
|
161
161
|
const queryCompleteResolver = resolver();
|
|
162
162
|
let queryComplete = delegate.defaultQueryComplete;
|
|
163
|
+
let endToEndMetricRecorded = false;
|
|
163
164
|
const updateTTL = customQueryID ? (newTTL) => delegate.updateCustomQuery(customQueryID, newTTL) : (newTTL) => delegate.updateServerQuery(ast, newTTL);
|
|
164
165
|
const gotCallback = (got, error) => {
|
|
165
166
|
if (error) {
|
|
@@ -168,7 +169,10 @@ function materializeImpl(query, delegate, factory = arrayViewFactory, options) {
|
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
if (got) {
|
|
171
|
-
|
|
172
|
+
if (!endToEndMetricRecorded) {
|
|
173
|
+
delegate.addMetric("query-materialization-end-to-end", performance.now() - t0, queryID, ast);
|
|
174
|
+
endToEndMetricRecorded = true;
|
|
175
|
+
}
|
|
172
176
|
queryComplete = true;
|
|
173
177
|
queryCompleteResolver.resolve(true);
|
|
174
178
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-delegate-base.js","names":[],"sources":["../../../../../zql/src/query/query-delegate-base.ts"],"sourcesContent":["import {resolver} from '@rocicorp/resolver';\nimport type {AST} from '../../../zero-protocol/src/ast.ts';\nimport type {ErroredQuery} from '../../../zero-protocol/src/custom-queries.ts';\nimport {hashOfNameAndArgs} from '../../../zero-protocol/src/query-hash.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {buildPipeline} from '../builder/builder.ts';\nimport {ArrayView} from '../ivm/array-view.ts';\nimport type {FilterInput} from '../ivm/filter-operators.ts';\nimport {MemoryStorage} from '../ivm/memory-storage.ts';\nimport type {Input, InputBase, Storage} from '../ivm/operator.ts';\nimport type {Source, SourceInput} from '../ivm/source.ts';\nimport type {Format, ViewFactory} from '../ivm/view.ts';\nimport type {MetricMap} from './metrics-delegate.ts';\nimport type {CustomQueryID} from './named.ts';\nimport type {\n CommitListener,\n GotCallback,\n QueryDelegate,\n} from './query-delegate.ts';\nimport {asQueryInternals, type QueryInternals} from './query-internals.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n PreloadOptions,\n Query,\n RunOptions,\n} from './query.ts';\nimport {DEFAULT_PRELOAD_TTL_MS, DEFAULT_TTL_MS, type TTL} from './ttl.ts';\nimport type {TypedView} from './typed-view.ts';\n\n/**\n * Base class that provides default implementations for common QueryDelegate methods.\n * Subclasses can override specific methods as needed.\n */\nexport abstract class QueryDelegateBase implements QueryDelegate {\n /**\n * Default implementation that just calls applyViewUpdates synchronously.\n * Override if you need custom batching behavior (e.g., SolidJS).\n */\n batchViewUpdates<T>(applyViewUpdates: () => T): T {\n return applyViewUpdates();\n }\n\n /**\n * Default implementation returns MemoryStorage.\n * Override if you need custom storage.\n */\n createStorage(): Storage {\n return new MemoryStorage();\n }\n\n /**\n * Default implementation calls materializeImpl.\n * Override if you need custom materialization behavior.\n */\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: undefined,\n options?: MaterializeOptions,\n ): TypedView<HumanReadable<TReturn>>;\n\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: ViewFactory<TTable, TSchema, TReturn, T>,\n options?: MaterializeOptions,\n ): T;\n\n /**\n * Materialize a query into a custom view using a provided factory function.\n */\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: ViewFactory<TTable, TSchema, TReturn, T>,\n options?: MaterializeOptions,\n ): T;\n\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: ViewFactory<TTable, TSchema, TReturn, T>,\n options?: MaterializeOptions,\n ): T {\n return materializeImpl(query, this, factory, options);\n }\n\n /**\n * Default implementation calls runImpl.\n * Override if you need custom query execution (e.g., TestPGQueryDelegate).\n */\n run<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n return runImpl(query, this, options);\n }\n\n /**\n * Default implementation calls preloadImpl.\n * Override if you need custom preload behavior.\n */\n preload<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n options?: PreloadOptions,\n ): {\n cleanup: () => void;\n complete: Promise<void>;\n } {\n return preloadImpl(query, this, options);\n }\n\n /**\n * Default no-op implementation for decorateSourceInput.\n * Override if you need to wrap or instrument source inputs.\n */\n decorateSourceInput(input: SourceInput, _queryID: string): Input {\n return input;\n }\n\n /**\n * Default no-op implementation for decorateInput.\n * Override if you need to wrap or instrument inputs.\n */\n decorateInput(input: Input, _name: string): Input {\n return input;\n }\n\n /**\n * Default no-op implementation for decorateFilterInput.\n * Override if you need to wrap or instrument filter inputs.\n */\n decorateFilterInput(input: FilterInput, _name: string): FilterInput {\n return input;\n }\n\n /**\n * Default no-op implementation for addEdge.\n * Override if you need to track graph edges (e.g., visualization).\n */\n addEdge(_source: InputBase, _dest: InputBase): void {\n // No-op\n }\n\n /**\n * Default no-op implementation for addMetric.\n * Override if you need to collect metrics.\n */\n addMetric<K extends keyof MetricMap>(\n _metric: K,\n _value: number,\n ..._args: MetricMap[K]\n ): void {\n // No-op\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to track server queries (e.g., ZeroContext, test delegates).\n */\n addServerQuery(_ast: AST, _ttl: TTL, _gotCallback?: GotCallback): () => void {\n return () => {};\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to track custom queries (e.g., ZeroContext, test delegates).\n */\n addCustomQuery(\n _ast: AST,\n _customQueryID: CustomQueryID,\n _ttl: TTL,\n _gotCallback?: GotCallback,\n ): () => void {\n return () => {};\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to handle query updates.\n */\n updateServerQuery(_ast: AST, _ttl: TTL): void {\n // No-op\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to handle custom query updates.\n */\n updateCustomQuery(_customQueryID: CustomQueryID, _ttl: TTL): void {\n // No-op\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to flush query changes.\n */\n flushQueryChanges(): void {\n // No-op\n }\n\n /**\n * Called when a transaction commits. Override to add custom behavior.\n * Default implementation returns a no-op cleanup function.\n */\n onTransactionCommit(_cb: CommitListener): () => void {\n return () => {};\n }\n\n /**\n * Validates run options. Override to add custom validation.\n * Default implementation is a no-op.\n */\n assertValidRunOptions(_options?: RunOptions): void {\n // No-op\n }\n\n abstract readonly defaultQueryComplete: boolean;\n\n // BuilderDelegate methods - must be implemented\n abstract getSource(name: string): Source | undefined;\n}\n\n// oxlint-disable-next-line require-await\nexport async function runImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n>(\n query: Query<TTable, TSchema, TReturn>,\n delegate: QueryDelegate,\n options?: RunOptions,\n): Promise<HumanReadable<TReturn>> {\n delegate.assertValidRunOptions(options);\n const v: TypedView<HumanReadable<TReturn>> = materializeImpl(\n query,\n delegate,\n undefined,\n {\n ttl: options?.ttl,\n },\n );\n if (options?.type === 'complete') {\n return new Promise(resolve => {\n v.addListener((data, type) => {\n if (type === 'complete') {\n v.destroy();\n resolve(data as HumanReadable<TReturn>);\n } else if (type === 'error') {\n v.destroy();\n resolve(Promise.reject(data));\n }\n });\n });\n }\n\n options?.type satisfies 'unknown' | undefined;\n\n const ret = v.data;\n v.destroy();\n return ret;\n}\n\nexport function preloadImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n>(\n query: Query<TTable, TSchema, TReturn>,\n delegate: QueryDelegate,\n options?: PreloadOptions,\n): {\n cleanup: () => void;\n complete: Promise<void>;\n} {\n const qi = asQueryInternals(query);\n const ttl = options?.ttl ?? DEFAULT_PRELOAD_TTL_MS;\n const {resolve, promise: complete} = resolver<void>();\n const {customQueryID, ast} = qi;\n if (customQueryID) {\n const cleanup = delegate.addCustomQuery(ast, customQueryID, ttl, got => {\n if (got) {\n resolve();\n }\n });\n return {\n cleanup,\n complete,\n };\n }\n\n const cleanup = delegate.addServerQuery(ast, ttl, got => {\n if (got) {\n resolve();\n }\n });\n return {\n cleanup,\n complete,\n };\n}\n\nexport function materializeImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n>(\n query: Query<TTable, TSchema, TReturn>,\n delegate: QueryDelegate,\n factory: ViewFactory<\n TTable,\n TSchema,\n TReturn,\n T\n // oxlint-disable-next-line no-explicit-any\n > = arrayViewFactory as any,\n options?: MaterializeOptions,\n): T {\n let ttl: TTL = options?.ttl ?? DEFAULT_TTL_MS;\n\n const qi = asQueryInternals(query);\n const {ast, format, customQueryID} = qi;\n const queryHash = qi.hash();\n\n const queryID = customQueryID\n ? hashOfNameAndArgs(customQueryID.name, customQueryID.args)\n : queryHash;\n const queryCompleteResolver = resolver<true>();\n let queryComplete: boolean | ErroredQuery = delegate.defaultQueryComplete;\n const updateTTL = customQueryID\n ? (newTTL: TTL) => delegate.updateCustomQuery(customQueryID, newTTL)\n : (newTTL: TTL) => delegate.updateServerQuery(ast, newTTL);\n\n const gotCallback: GotCallback = (got, error) => {\n if (error) {\n queryCompleteResolver.reject(error);\n queryComplete = error;\n return;\n }\n\n if (got) {\n delegate.addMetric(\n 'query-materialization-end-to-end',\n performance.now() - t0,\n queryID,\n ast,\n );\n queryComplete = true;\n queryCompleteResolver.resolve(true);\n }\n };\n\n let removeCommitObserver: (() => void) | undefined;\n const onDestroy = () => {\n input.destroy();\n removeCommitObserver?.();\n removeAddedQuery();\n };\n\n const t0 = performance.now();\n\n const removeAddedQuery = customQueryID\n ? delegate.addCustomQuery(ast, customQueryID, ttl, gotCallback)\n : delegate.addServerQuery(ast, ttl, gotCallback);\n\n const input = buildPipeline(ast, delegate, queryID);\n\n const view = delegate.batchViewUpdates(() =>\n (factory ?? arrayViewFactory)(\n query,\n input,\n format,\n onDestroy,\n cb => {\n removeCommitObserver = delegate.onTransactionCommit(cb);\n },\n queryComplete || queryCompleteResolver.promise,\n updateTTL,\n ),\n );\n\n delegate.addMetric(\n 'query-materialization-client',\n performance.now() - t0,\n queryID,\n );\n\n return view as T;\n}\n\nfunction arrayViewFactory<\n TTable extends string,\n TSchema extends Schema,\n TReturn,\n>(\n _query: QueryInternals<TTable, TSchema, TReturn>,\n input: Input,\n format: Format,\n onDestroy: () => void,\n onTransactionCommit: (cb: () => void) => void,\n queryComplete: true | ErroredQuery | Promise<true>,\n updateTTL: (ttl: TTL) => void,\n): TypedView<HumanReadable<TReturn>> {\n const v = new ArrayView<HumanReadable<TReturn>>(\n input,\n format,\n queryComplete,\n updateTTL,\n );\n v.onDestroy = onDestroy;\n onTransactionCommit(() => v.flush());\n return v;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query-delegate-base.js","names":[],"sources":["../../../../../zql/src/query/query-delegate-base.ts"],"sourcesContent":["import {resolver} from '@rocicorp/resolver';\nimport type {AST} from '../../../zero-protocol/src/ast.ts';\nimport type {ErroredQuery} from '../../../zero-protocol/src/custom-queries.ts';\nimport {hashOfNameAndArgs} from '../../../zero-protocol/src/query-hash.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {buildPipeline} from '../builder/builder.ts';\nimport {ArrayView} from '../ivm/array-view.ts';\nimport type {FilterInput} from '../ivm/filter-operators.ts';\nimport {MemoryStorage} from '../ivm/memory-storage.ts';\nimport type {Input, InputBase, Storage} from '../ivm/operator.ts';\nimport type {Source, SourceInput} from '../ivm/source.ts';\nimport type {Format, ViewFactory} from '../ivm/view.ts';\nimport type {MetricMap} from './metrics-delegate.ts';\nimport type {CustomQueryID} from './named.ts';\nimport type {\n CommitListener,\n GotCallback,\n QueryDelegate,\n} from './query-delegate.ts';\nimport {asQueryInternals, type QueryInternals} from './query-internals.ts';\nimport type {\n HumanReadable,\n MaterializeOptions,\n PreloadOptions,\n Query,\n RunOptions,\n} from './query.ts';\nimport {DEFAULT_PRELOAD_TTL_MS, DEFAULT_TTL_MS, type TTL} from './ttl.ts';\nimport type {TypedView} from './typed-view.ts';\n\n/**\n * Base class that provides default implementations for common QueryDelegate methods.\n * Subclasses can override specific methods as needed.\n */\nexport abstract class QueryDelegateBase implements QueryDelegate {\n /**\n * Default implementation that just calls applyViewUpdates synchronously.\n * Override if you need custom batching behavior (e.g., SolidJS).\n */\n batchViewUpdates<T>(applyViewUpdates: () => T): T {\n return applyViewUpdates();\n }\n\n /**\n * Default implementation returns MemoryStorage.\n * Override if you need custom storage.\n */\n createStorage(): Storage {\n return new MemoryStorage();\n }\n\n /**\n * Default implementation calls materializeImpl.\n * Override if you need custom materialization behavior.\n */\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: undefined,\n options?: MaterializeOptions,\n ): TypedView<HumanReadable<TReturn>>;\n\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: ViewFactory<TTable, TSchema, TReturn, T>,\n options?: MaterializeOptions,\n ): T;\n\n /**\n * Materialize a query into a custom view using a provided factory function.\n */\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: ViewFactory<TTable, TSchema, TReturn, T>,\n options?: MaterializeOptions,\n ): T;\n\n materialize<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n >(\n query: Query<TTable, TSchema, TReturn>,\n factory?: ViewFactory<TTable, TSchema, TReturn, T>,\n options?: MaterializeOptions,\n ): T {\n return materializeImpl(query, this, factory, options);\n }\n\n /**\n * Default implementation calls runImpl.\n * Override if you need custom query execution (e.g., TestPGQueryDelegate).\n */\n run<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n options?: RunOptions,\n ): Promise<HumanReadable<TReturn>> {\n return runImpl(query, this, options);\n }\n\n /**\n * Default implementation calls preloadImpl.\n * Override if you need custom preload behavior.\n */\n preload<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n >(\n query: Query<TTable, TSchema, TReturn>,\n options?: PreloadOptions,\n ): {\n cleanup: () => void;\n complete: Promise<void>;\n } {\n return preloadImpl(query, this, options);\n }\n\n /**\n * Default no-op implementation for decorateSourceInput.\n * Override if you need to wrap or instrument source inputs.\n */\n decorateSourceInput(input: SourceInput, _queryID: string): Input {\n return input;\n }\n\n /**\n * Default no-op implementation for decorateInput.\n * Override if you need to wrap or instrument inputs.\n */\n decorateInput(input: Input, _name: string): Input {\n return input;\n }\n\n /**\n * Default no-op implementation for decorateFilterInput.\n * Override if you need to wrap or instrument filter inputs.\n */\n decorateFilterInput(input: FilterInput, _name: string): FilterInput {\n return input;\n }\n\n /**\n * Default no-op implementation for addEdge.\n * Override if you need to track graph edges (e.g., visualization).\n */\n addEdge(_source: InputBase, _dest: InputBase): void {\n // No-op\n }\n\n /**\n * Default no-op implementation for addMetric.\n * Override if you need to collect metrics.\n */\n addMetric<K extends keyof MetricMap>(\n _metric: K,\n _value: number,\n ..._args: MetricMap[K]\n ): void {\n // No-op\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to track server queries (e.g., ZeroContext, test delegates).\n */\n addServerQuery(_ast: AST, _ttl: TTL, _gotCallback?: GotCallback): () => void {\n return () => {};\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to track custom queries (e.g., ZeroContext, test delegates).\n */\n addCustomQuery(\n _ast: AST,\n _customQueryID: CustomQueryID,\n _ttl: TTL,\n _gotCallback?: GotCallback,\n ): () => void {\n return () => {};\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to handle query updates.\n */\n updateServerQuery(_ast: AST, _ttl: TTL): void {\n // No-op\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to handle custom query updates.\n */\n updateCustomQuery(_customQueryID: CustomQueryID, _ttl: TTL): void {\n // No-op\n }\n\n /**\n * Default no-op implementation.\n * Override if you need to flush query changes.\n */\n flushQueryChanges(): void {\n // No-op\n }\n\n /**\n * Called when a transaction commits. Override to add custom behavior.\n * Default implementation returns a no-op cleanup function.\n */\n onTransactionCommit(_cb: CommitListener): () => void {\n return () => {};\n }\n\n /**\n * Validates run options. Override to add custom validation.\n * Default implementation is a no-op.\n */\n assertValidRunOptions(_options?: RunOptions): void {\n // No-op\n }\n\n abstract readonly defaultQueryComplete: boolean;\n\n // BuilderDelegate methods - must be implemented\n abstract getSource(name: string): Source | undefined;\n}\n\n// oxlint-disable-next-line require-await\nexport async function runImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n>(\n query: Query<TTable, TSchema, TReturn>,\n delegate: QueryDelegate,\n options?: RunOptions,\n): Promise<HumanReadable<TReturn>> {\n delegate.assertValidRunOptions(options);\n const v: TypedView<HumanReadable<TReturn>> = materializeImpl(\n query,\n delegate,\n undefined,\n {\n ttl: options?.ttl,\n },\n );\n if (options?.type === 'complete') {\n return new Promise(resolve => {\n v.addListener((data, type) => {\n if (type === 'complete') {\n v.destroy();\n resolve(data as HumanReadable<TReturn>);\n } else if (type === 'error') {\n v.destroy();\n resolve(Promise.reject(data));\n }\n });\n });\n }\n\n options?.type satisfies 'unknown' | undefined;\n\n const ret = v.data;\n v.destroy();\n return ret;\n}\n\nexport function preloadImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n>(\n query: Query<TTable, TSchema, TReturn>,\n delegate: QueryDelegate,\n options?: PreloadOptions,\n): {\n cleanup: () => void;\n complete: Promise<void>;\n} {\n const qi = asQueryInternals(query);\n const ttl = options?.ttl ?? DEFAULT_PRELOAD_TTL_MS;\n const {resolve, promise: complete} = resolver<void>();\n const {customQueryID, ast} = qi;\n if (customQueryID) {\n const cleanup = delegate.addCustomQuery(ast, customQueryID, ttl, got => {\n if (got) {\n resolve();\n }\n });\n return {\n cleanup,\n complete,\n };\n }\n\n const cleanup = delegate.addServerQuery(ast, ttl, got => {\n if (got) {\n resolve();\n }\n });\n return {\n cleanup,\n complete,\n };\n}\n\nexport function materializeImpl<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends Schema,\n TReturn,\n T,\n>(\n query: Query<TTable, TSchema, TReturn>,\n delegate: QueryDelegate,\n factory: ViewFactory<\n TTable,\n TSchema,\n TReturn,\n T\n // oxlint-disable-next-line no-explicit-any\n > = arrayViewFactory as any,\n options?: MaterializeOptions,\n): T {\n let ttl: TTL = options?.ttl ?? DEFAULT_TTL_MS;\n\n const qi = asQueryInternals(query);\n const {ast, format, customQueryID} = qi;\n const queryHash = qi.hash();\n\n const queryID = customQueryID\n ? hashOfNameAndArgs(customQueryID.name, customQueryID.args)\n : queryHash;\n const queryCompleteResolver = resolver<true>();\n let queryComplete: boolean | ErroredQuery = delegate.defaultQueryComplete;\n let endToEndMetricRecorded = false;\n const updateTTL = customQueryID\n ? (newTTL: TTL) => delegate.updateCustomQuery(customQueryID, newTTL)\n : (newTTL: TTL) => delegate.updateServerQuery(ast, newTTL);\n\n const gotCallback: GotCallback = (got, error) => {\n if (error) {\n queryCompleteResolver.reject(error);\n queryComplete = error;\n return;\n }\n\n if (got) {\n if (!endToEndMetricRecorded) {\n delegate.addMetric(\n 'query-materialization-end-to-end',\n performance.now() - t0,\n queryID,\n ast,\n );\n endToEndMetricRecorded = true;\n }\n queryComplete = true;\n queryCompleteResolver.resolve(true);\n }\n };\n\n let removeCommitObserver: (() => void) | undefined;\n const onDestroy = () => {\n input.destroy();\n removeCommitObserver?.();\n removeAddedQuery();\n };\n\n const t0 = performance.now();\n\n const removeAddedQuery = customQueryID\n ? delegate.addCustomQuery(ast, customQueryID, ttl, gotCallback)\n : delegate.addServerQuery(ast, ttl, gotCallback);\n\n const input = buildPipeline(ast, delegate, queryID);\n\n const view = delegate.batchViewUpdates(() =>\n (factory ?? arrayViewFactory)(\n query,\n input,\n format,\n onDestroy,\n cb => {\n removeCommitObserver = delegate.onTransactionCommit(cb);\n },\n queryComplete || queryCompleteResolver.promise,\n updateTTL,\n ),\n );\n\n delegate.addMetric(\n 'query-materialization-client',\n performance.now() - t0,\n queryID,\n );\n\n return view as T;\n}\n\nfunction arrayViewFactory<\n TTable extends string,\n TSchema extends Schema,\n TReturn,\n>(\n _query: QueryInternals<TTable, TSchema, TReturn>,\n input: Input,\n format: Format,\n onDestroy: () => void,\n onTransactionCommit: (cb: () => void) => void,\n queryComplete: true | ErroredQuery | Promise<true>,\n updateTTL: (ttl: TTL) => void,\n): TypedView<HumanReadable<TReturn>> {\n const v = new ArrayView<HumanReadable<TReturn>>(\n input,\n format,\n queryComplete,\n updateTTL,\n );\n v.onDestroy = onDestroy;\n onTransactionCommit(() => v.flush());\n return v;\n}\n"],"mappings":"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"}
|
package/out/zqlite/src/db.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { manualSpan } from "../../otel/src/span.js";
|
|
2
2
|
import { version } from "../../otel/src/version.js";
|
|
3
3
|
import { trace } from "@opentelemetry/api";
|
|
4
|
-
import
|
|
4
|
+
import SQLite3Database, { SqliteError } from "@rocicorp/zero-sqlite3";
|
|
5
5
|
//#region ../zqlite/src/db.ts
|
|
6
6
|
var tracer = trace.getTracer("view-syncer", version);
|
|
7
7
|
var AUTO_VACUUM_INCREMENTAL = 2;
|
|
@@ -17,7 +17,7 @@ var Database = class {
|
|
|
17
17
|
constructor(lc, path, options, slowQueryThreshold = 100) {
|
|
18
18
|
try {
|
|
19
19
|
this.#lc = lc.withContext("class", "Database").withContext("path", path);
|
|
20
|
-
this.#db = new
|
|
20
|
+
this.#db = new SQLite3Database(path, options);
|
|
21
21
|
this.#threshold = slowQueryThreshold;
|
|
22
22
|
this.pragma("case_sensitive_like = ON");
|
|
23
23
|
const [{ page_size: pageSize }] = this.pragma("page_size");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-scalar-subqueries.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/resolve-scalar-subqueries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,SAAS,EAET,YAAY,EAEb,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AAEvE,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE;QACT,UAAU,EAAE,UAAU,EAAE,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mEAAmE;IACnE,GAAG,EAAE,GAAG,CAAC;IACT,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB;2CACuC;IACvC,aAAa,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,UAAU,EAAE,iBAAiB,EAAE,CAAC;CACjC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAC3B,WAAW,EAAE,GAAG,EAChB,UAAU,EAAE,MAAM,KACf,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC;AAErC;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAChD,OAAO,EAAE,cAAc,GACtB,aAAa,CAIf;
|
|
1
|
+
{"version":3,"file":"resolve-scalar-subqueries.d.ts","sourceRoot":"","sources":["../../../../zqlite/src/resolve-scalar-subqueries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,GAAG,EACH,SAAS,EAET,YAAY,EAEb,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wCAAwC,CAAC;AAEvE,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE;QACT,UAAU,EAAE,UAAU,EAAE,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mEAAmE;IACnE,GAAG,EAAE,GAAG,CAAC;IACT,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB;2CACuC;IACvC,aAAa,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,UAAU,EAAE,iBAAiB,EAAE,CAAC;CACjC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAC3B,WAAW,EAAE,GAAG,EAChB,UAAU,EAAE,MAAM,KACf,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC;AAErC;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAChD,OAAO,EAAE,cAAc,GACtB,aAAa,CAIf;AA6ID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,GAC/C,OAAO,CAiBT;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,SAAS,GACnB,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAI3B"}
|
|
@@ -79,7 +79,7 @@ function resolveScalarSubquery(condition, tableSpecs, execute, companions) {
|
|
|
79
79
|
childField,
|
|
80
80
|
resolvedValue: value
|
|
81
81
|
});
|
|
82
|
-
if (value === void 0 || value === null) return ALWAYS_FALSE;
|
|
82
|
+
if (value === void 0 || value === null) return condition.op === "EXISTS" ? ALWAYS_FALSE : ALWAYS_TRUE;
|
|
83
83
|
return {
|
|
84
84
|
type: "simple",
|
|
85
85
|
op: condition.op === "EXISTS" ? "=" : "IS NOT",
|
|
@@ -105,6 +105,18 @@ var ALWAYS_FALSE = {
|
|
|
105
105
|
value: 0
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
|
+
var ALWAYS_TRUE = {
|
|
109
|
+
type: "simple",
|
|
110
|
+
op: "=",
|
|
111
|
+
left: {
|
|
112
|
+
type: "literal",
|
|
113
|
+
value: 1
|
|
114
|
+
},
|
|
115
|
+
right: {
|
|
116
|
+
type: "literal",
|
|
117
|
+
value: 1
|
|
118
|
+
}
|
|
119
|
+
};
|
|
108
120
|
/**
|
|
109
121
|
* Checks if the subquery is guaranteed to return at most one deterministic row.
|
|
110
122
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-scalar-subqueries.js","names":[],"sources":["../../../../zqlite/src/resolve-scalar-subqueries.ts"],"sourcesContent":["import type {\n AST,\n Condition,\n CorrelatedSubqueryCondition,\n LiteralValue,\n SimpleCondition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {PrimaryKey} from '../../zero-protocol/src/primary-key.ts';\n\ntype TableSpecWithUniqueKeys = {\n tableSpec: {\n uniqueKeys: PrimaryKey[];\n };\n};\n\nexport type CompanionSubquery = {\n /** The original scalar subquery AST (the subquery table query). */\n ast: AST;\n /** The field in the subquery row whose value was resolved. */\n childField: string;\n /** The resolved value, `null` if a row matched but the field was `NULL`,\n * or `undefined` if no row matched. */\n resolvedValue: LiteralValue | null | undefined;\n};\n\nexport type ResolveResult = {\n ast: AST;\n companions: CompanionSubquery[];\n};\n\n/**\n * Callback that executes a scalar subquery and returns the value of\n * `childField` from the (at most one) matching row, or `undefined`\n * if no rows match.\n */\nexport type ScalarExecutor = (\n subqueryAST: AST,\n childField: string,\n) => LiteralValue | null | undefined;\n\n/**\n * Resolves \"simple\" scalar subqueries by calling the provided executor\n * and replacing them with literal conditions. A scalar subquery is simple\n * when all columns of at least one unique index on the subquery table are\n * equality-constrained by literal values in the subquery's WHERE clause\n * (using only AND conjunctions).\n *\n * Non-simple scalar subqueries are left untouched for the existing\n * EXISTS rewrite in buildPipelineInternal.\n *\n * Returns the resolved AST and a list of companion subquery ASTs whose\n * rows need to be synced to the client for the EXISTS rewrite to work.\n */\nexport function resolveSimpleScalarSubqueries(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n): ResolveResult {\n const companions: CompanionSubquery[] = [];\n const resolved = resolveASTRecursive(ast, tableSpecs, execute, companions);\n return {ast: resolved, companions};\n}\n\nfunction resolveASTRecursive(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): AST {\n const where = ast.where\n ? resolveCondition(ast.where, tableSpecs, execute, companions)\n : undefined;\n\n const related = ast.related?.map(r => ({\n ...r,\n subquery: resolveASTRecursive(r.subquery, tableSpecs, execute, companions),\n }));\n\n if (where !== ast.where || related !== ast.related) {\n return {...ast, where, related};\n }\n return ast;\n}\n\nfunction resolveCondition(\n condition: Condition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n switch (condition.type) {\n case 'correlatedSubquery':\n if (condition.scalar) {\n return resolveScalarSubquery(\n condition,\n tableSpecs,\n execute,\n companions,\n );\n }\n // Non-scalar correlated subquery: recurse into its subquery\n {\n const resolvedSubquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n if (resolvedSubquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery: resolvedSubquery},\n };\n }\n return condition;\n }\n case 'and':\n case 'or': {\n const resolved = condition.conditions.map(c =>\n resolveCondition(c, tableSpecs, execute, companions),\n );\n if (resolved.every((c, i) => c === condition.conditions[i])) {\n return condition;\n }\n return {type: condition.type, conditions: resolved};\n }\n default:\n return condition;\n }\n}\n\nfunction resolveScalarSubquery(\n condition: CorrelatedSubqueryCondition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n const parentField = condition.related.correlation.parentField[0];\n const childField = condition.related.correlation.childField[0];\n\n // Recursively resolve any scalar subqueries nested in the\n // subquery's own WHERE (and related) before evaluating this one.\n const subquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n\n if (!isSimpleSubquery(subquery, tableSpecs)) {\n // Return with the (possibly partially-resolved) subquery.\n if (subquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery},\n };\n }\n return condition;\n }\n\n const value = execute(subquery, childField);\n\n // Record the companion subquery AST so its rows are synced to the client.\n // The client rewrites scalar subqueries to EXISTS and needs those rows.\n companions.push({\n ast: subquery,\n childField,\n resolvedValue: value,\n });\n\n if (value === undefined || value === null) {\n // No rows or NULL value — both x = NULL and x != NULL are false in SQL\n return ALWAYS_FALSE;\n }\n\n const op = condition.op === 'EXISTS' ? '=' : 'IS NOT';\n return {\n type: 'simple',\n op,\n left: {type: 'column', name: parentField},\n right: {type: 'literal', value},\n } satisfies SimpleCondition;\n}\n\nconst ALWAYS_FALSE: SimpleCondition = {\n type: 'simple',\n op: '=',\n left: {type: 'literal', value: 1},\n right: {type: 'literal', value: 0},\n};\n\n/**\n * Checks if the subquery is guaranteed to return at most one deterministic row.\n *\n * This is true when all columns of at least one unique index on the subquery\n * table are equality-constrained by literal values in the WHERE clause\n * (using only AND conjunctions).\n */\nexport function isSimpleSubquery(\n subquery: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n): boolean {\n const spec = tableSpecs.get(subquery.table);\n if (!spec) {\n return false;\n }\n\n if (!subquery.where) {\n return false;\n }\n\n const constraints = extractLiteralEqualityConstraints(subquery.where);\n if (constraints.size === 0) {\n return false;\n }\n\n const {uniqueKeys} = spec.tableSpec;\n return uniqueKeys.some(key => key.every(col => constraints.has(col)));\n}\n\n/**\n * Extracts column=literal equality constraints from a condition tree,\n * only following AND conjunctions (not OR).\n */\nexport function extractLiteralEqualityConstraints(\n condition: Condition,\n): Map<string, LiteralValue> {\n const constraints = new Map<string, LiteralValue>();\n collectConstraints(condition, constraints);\n return constraints;\n}\n\nfunction collectConstraints(\n condition: Condition,\n constraints: Map<string, LiteralValue>,\n): void {\n switch (condition.type) {\n case 'simple':\n if (condition.op === '=') {\n if (\n condition.left.type === 'column' &&\n condition.right.type === 'literal'\n ) {\n constraints.set(condition.left.name, condition.right.value);\n }\n }\n break;\n case 'and':\n for (const c of condition.conditions) {\n collectConstraints(c, constraints);\n }\n break;\n // OR, correlatedSubquery (non-scalar) — don't contribute constraints\n default:\n break;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resolve-scalar-subqueries.js","names":[],"sources":["../../../../zqlite/src/resolve-scalar-subqueries.ts"],"sourcesContent":["import type {\n AST,\n Condition,\n CorrelatedSubqueryCondition,\n LiteralValue,\n SimpleCondition,\n} from '../../zero-protocol/src/ast.ts';\nimport type {PrimaryKey} from '../../zero-protocol/src/primary-key.ts';\n\ntype TableSpecWithUniqueKeys = {\n tableSpec: {\n uniqueKeys: PrimaryKey[];\n };\n};\n\nexport type CompanionSubquery = {\n /** The original scalar subquery AST (the subquery table query). */\n ast: AST;\n /** The field in the subquery row whose value was resolved. */\n childField: string;\n /** The resolved value, `null` if a row matched but the field was `NULL`,\n * or `undefined` if no row matched. */\n resolvedValue: LiteralValue | null | undefined;\n};\n\nexport type ResolveResult = {\n ast: AST;\n companions: CompanionSubquery[];\n};\n\n/**\n * Callback that executes a scalar subquery and returns the value of\n * `childField` from the (at most one) matching row, or `undefined`\n * if no rows match.\n */\nexport type ScalarExecutor = (\n subqueryAST: AST,\n childField: string,\n) => LiteralValue | null | undefined;\n\n/**\n * Resolves \"simple\" scalar subqueries by calling the provided executor\n * and replacing them with literal conditions. A scalar subquery is simple\n * when all columns of at least one unique index on the subquery table are\n * equality-constrained by literal values in the subquery's WHERE clause\n * (using only AND conjunctions).\n *\n * Non-simple scalar subqueries are left untouched for the existing\n * EXISTS rewrite in buildPipelineInternal.\n *\n * Returns the resolved AST and a list of companion subquery ASTs whose\n * rows need to be synced to the client for the EXISTS rewrite to work.\n */\nexport function resolveSimpleScalarSubqueries(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n): ResolveResult {\n const companions: CompanionSubquery[] = [];\n const resolved = resolveASTRecursive(ast, tableSpecs, execute, companions);\n return {ast: resolved, companions};\n}\n\nfunction resolveASTRecursive(\n ast: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): AST {\n const where = ast.where\n ? resolveCondition(ast.where, tableSpecs, execute, companions)\n : undefined;\n\n const related = ast.related?.map(r => ({\n ...r,\n subquery: resolveASTRecursive(r.subquery, tableSpecs, execute, companions),\n }));\n\n if (where !== ast.where || related !== ast.related) {\n return {...ast, where, related};\n }\n return ast;\n}\n\nfunction resolveCondition(\n condition: Condition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n switch (condition.type) {\n case 'correlatedSubquery':\n if (condition.scalar) {\n return resolveScalarSubquery(\n condition,\n tableSpecs,\n execute,\n companions,\n );\n }\n // Non-scalar correlated subquery: recurse into its subquery\n {\n const resolvedSubquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n if (resolvedSubquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery: resolvedSubquery},\n };\n }\n return condition;\n }\n case 'and':\n case 'or': {\n const resolved = condition.conditions.map(c =>\n resolveCondition(c, tableSpecs, execute, companions),\n );\n if (resolved.every((c, i) => c === condition.conditions[i])) {\n return condition;\n }\n return {type: condition.type, conditions: resolved};\n }\n default:\n return condition;\n }\n}\n\nfunction resolveScalarSubquery(\n condition: CorrelatedSubqueryCondition,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n execute: ScalarExecutor,\n companions: CompanionSubquery[],\n): Condition {\n const parentField = condition.related.correlation.parentField[0];\n const childField = condition.related.correlation.childField[0];\n\n // Recursively resolve any scalar subqueries nested in the\n // subquery's own WHERE (and related) before evaluating this one.\n const subquery = resolveASTRecursive(\n condition.related.subquery,\n tableSpecs,\n execute,\n companions,\n );\n\n if (!isSimpleSubquery(subquery, tableSpecs)) {\n // Return with the (possibly partially-resolved) subquery.\n if (subquery !== condition.related.subquery) {\n return {\n ...condition,\n related: {...condition.related, subquery},\n };\n }\n return condition;\n }\n\n const value = execute(subquery, childField);\n\n // Record the companion subquery AST so its rows are synced to the client.\n // The client rewrites scalar subqueries to EXISTS and needs those rows.\n companions.push({\n ast: subquery,\n childField,\n resolvedValue: value,\n });\n\n if (value === undefined || value === null) {\n // No row matched, or the matched row's `childField` is NULL (which can\n // never satisfy the correlation, since `parentField = NULL` is never\n // true). Either way the correlated EXISTS is false for *every* parent\n // row, so the gate collapses to a constant: false for EXISTS, and its\n // negation — true — for NOT EXISTS.\n return condition.op === 'EXISTS' ? ALWAYS_FALSE : ALWAYS_TRUE;\n }\n\n const op = condition.op === 'EXISTS' ? '=' : 'IS NOT';\n return {\n type: 'simple',\n op,\n left: {type: 'column', name: parentField},\n right: {type: 'literal', value},\n } satisfies SimpleCondition;\n}\n\nconst ALWAYS_FALSE: SimpleCondition = {\n type: 'simple',\n op: '=',\n left: {type: 'literal', value: 1},\n right: {type: 'literal', value: 0},\n};\n\nconst ALWAYS_TRUE: SimpleCondition = {\n type: 'simple',\n op: '=',\n left: {type: 'literal', value: 1},\n right: {type: 'literal', value: 1},\n};\n\n/**\n * Checks if the subquery is guaranteed to return at most one deterministic row.\n *\n * This is true when all columns of at least one unique index on the subquery\n * table are equality-constrained by literal values in the WHERE clause\n * (using only AND conjunctions).\n */\nexport function isSimpleSubquery(\n subquery: AST,\n tableSpecs: Map<string, TableSpecWithUniqueKeys>,\n): boolean {\n const spec = tableSpecs.get(subquery.table);\n if (!spec) {\n return false;\n }\n\n if (!subquery.where) {\n return false;\n }\n\n const constraints = extractLiteralEqualityConstraints(subquery.where);\n if (constraints.size === 0) {\n return false;\n }\n\n const {uniqueKeys} = spec.tableSpec;\n return uniqueKeys.some(key => key.every(col => constraints.has(col)));\n}\n\n/**\n * Extracts column=literal equality constraints from a condition tree,\n * only following AND conjunctions (not OR).\n */\nexport function extractLiteralEqualityConstraints(\n condition: Condition,\n): Map<string, LiteralValue> {\n const constraints = new Map<string, LiteralValue>();\n collectConstraints(condition, constraints);\n return constraints;\n}\n\nfunction collectConstraints(\n condition: Condition,\n constraints: Map<string, LiteralValue>,\n): void {\n switch (condition.type) {\n case 'simple':\n if (condition.op === '=') {\n if (\n condition.left.type === 'column' &&\n condition.right.type === 'literal'\n ) {\n constraints.set(condition.left.name, condition.right.value);\n }\n }\n break;\n case 'and':\n for (const c of condition.conditions) {\n collectConstraints(c, constraints);\n }\n break;\n // OR, correlatedSubquery (non-scalar) — don't contribute constraints\n default:\n break;\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { must } from "../../shared/src/must.js";
|
|
|
3
3
|
import { buildSelectQuery } from "./query-builder.js";
|
|
4
4
|
import { compileInline } from "./internal/sql-inline.js";
|
|
5
5
|
import { SQLiteStatFanout } from "./sqlite-stat-fanout.js";
|
|
6
|
-
import
|
|
6
|
+
import SQLite3Database from "@rocicorp/zero-sqlite3";
|
|
7
7
|
//#region ../zqlite/src/sqlite-cost-model.ts
|
|
8
8
|
/**
|
|
9
9
|
* Creates a SQLite-based cost model for query planning.
|
|
@@ -66,13 +66,13 @@ function removeCorrelatedSubqueries(condition) {
|
|
|
66
66
|
function getScanstatusLoops(stmt) {
|
|
67
67
|
const loops = [];
|
|
68
68
|
for (let idx = 0;; idx++) {
|
|
69
|
-
const selectId = stmt.scanStatus(idx,
|
|
69
|
+
const selectId = stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_SELECTID, 1);
|
|
70
70
|
if (selectId === void 0) break;
|
|
71
71
|
loops.push({
|
|
72
72
|
selectId: must(selectId),
|
|
73
|
-
parentId: must(stmt.scanStatus(idx,
|
|
74
|
-
explain: must(stmt.scanStatus(idx,
|
|
75
|
-
est: must(stmt.scanStatus(idx,
|
|
73
|
+
parentId: must(stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_PARENTID, 1)),
|
|
74
|
+
explain: must(stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EXPLAIN, 1)),
|
|
75
|
+
est: must(stmt.scanStatus(idx, SQLite3Database.SQLITE_SCANSTAT_EST, 1))
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
return loops.sort((a, b) => a.selectId - b.selectId);
|
|
@@ -8,7 +8,7 @@ import { timeSampled } from "../../otel/src/maybe-time.js";
|
|
|
8
8
|
import { compile, format, sql } from "./internal/sql.js";
|
|
9
9
|
import { StatementCache } from "./internal/statement-cache.js";
|
|
10
10
|
import { buildSelectQuery, toSQLiteType } from "./query-builder.js";
|
|
11
|
-
import
|
|
11
|
+
import SQLite3Database from "@rocicorp/zero-sqlite3";
|
|
12
12
|
//#region ../zqlite/src/table-source.ts
|
|
13
13
|
var eventCount = 0;
|
|
14
14
|
/**
|
|
@@ -154,10 +154,10 @@ var TableSource = class {
|
|
|
154
154
|
let totalNvisit = 0;
|
|
155
155
|
const planLines = [];
|
|
156
156
|
for (let i = 0;; i++) {
|
|
157
|
-
const nvisit = cachedStatement.statement.scanStatus(i,
|
|
157
|
+
const nvisit = cachedStatement.statement.scanStatus(i, SQLite3Database.SQLITE_SCANSTAT_NVISIT, 1);
|
|
158
158
|
if (nvisit === void 0) break;
|
|
159
159
|
totalNvisit += Number(nvisit);
|
|
160
|
-
const explain = cachedStatement.statement.scanStatus(i,
|
|
160
|
+
const explain = cachedStatement.statement.scanStatus(i, SQLite3Database.SQLITE_SCANSTAT_EXPLAIN, 1);
|
|
161
161
|
if (typeof explain === "string" && explain.length > 0) planLines.push(explain);
|
|
162
162
|
}
|
|
163
163
|
if (totalNvisit !== 0) debug.recordNVisit(this.#table, sqlAndBindings.text, totalNvisit);
|