qubu 0.0.0 → 0.3.3
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/dist/canonical-BbnqavJm.mjs +972 -0
- package/dist/codegen.d.mts +107 -0
- package/dist/codegen.mjs +1199 -0
- package/dist/column-CXMxx8Hq.mjs +118 -0
- package/dist/complete-D5Djh-zo.mjs +1788 -0
- package/dist/complete-types-BdFqUfbb.d.mts +371 -0
- package/dist/core.d.mts +33 -0
- package/dist/core.mjs +36 -0
- package/dist/ddl.d.mts +118 -0
- package/dist/ddl.mjs +1120 -0
- package/dist/dialect-b2-Z6uBF.mjs +13 -0
- package/dist/diff.d.mts +2 -0
- package/dist/diff.mjs +883 -0
- package/dist/drizzle-mysql.d.mts +24 -0
- package/dist/drizzle-mysql.mjs +72 -0
- package/dist/drizzle-postgres.d.mts +24 -0
- package/dist/drizzle-postgres.mjs +73 -0
- package/dist/drizzle-sqlite.d.mts +24 -0
- package/dist/drizzle-sqlite.mjs +82 -0
- package/dist/drizzle.d.mts +13 -0
- package/dist/drizzle.mjs +2 -0
- package/dist/errors-BGCoLe_r.mjs +14 -0
- package/dist/errors-Dxv73YJu.mjs +26 -0
- package/dist/explain-CkIK13L_.mjs +98 -0
- package/dist/index-Ds7-mhJi.d.mts +146 -0
- package/dist/index-Dug5HnLB.d.mts +204 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1276 -0
- package/dist/introspection.d.mts +117 -0
- package/dist/introspection.mjs +4341 -0
- package/dist/json-CUZlv4HT.mjs +169 -0
- package/dist/migration.d.mts +215 -0
- package/dist/migration.mjs +1161 -0
- package/dist/mysql-DqkqXB6A.mjs +355 -0
- package/dist/mysql.d.mts +5 -0
- package/dist/mysql.mjs +50 -0
- package/dist/naming-QVCOnSj2.mjs +20 -0
- package/dist/omit-OxV58AwX.mjs +5 -0
- package/dist/on-conflict-BxnxubMb.mjs +143 -0
- package/dist/postgres-DEBBeh52.mjs +235 -0
- package/dist/postgres.d.mts +123 -0
- package/dist/postgres.mjs +52 -0
- package/dist/registry-BufIskVN.mjs +569 -0
- package/dist/relational-DCZrrNia.mjs +77 -0
- package/dist/runtime-BTr-MTlo.mjs +193 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +5 -0
- package/dist/serialize-PF1cfH2P.mjs +556 -0
- package/dist/snapshot-CWPgzxNx.mjs +392 -0
- package/dist/snapshot.d.mts +4 -0
- package/dist/snapshot.mjs +8 -0
- package/dist/source-DUoJVXmL.mjs +68 -0
- package/dist/sqlite-BU6DBxef.mjs +320 -0
- package/dist/sqlite.d.mts +5 -0
- package/dist/sqlite.mjs +48 -0
- package/dist/standard-BTVYKh_F.mjs +12 -0
- package/dist/table-llv9tsZ8.mjs +384 -0
- package/dist/types-4Q076HKo.d.mts +3636 -0
- package/dist/types-BX0mckiU.d.mts +644 -0
- package/dist/types-CO1KaRNc.d.mts +46 -0
- package/dist/types-Cec0xzo4.mjs +116 -0
- package/dist/value-BvilP0oz.mjs +29 -0
- package/dist/vite/ambient.d.ts +449 -0
- package/dist/vite.d.mts +35 -0
- package/dist/vite.mjs +498 -0
- package/docs/config.json +21 -0
- package/docs/dialects-and-execution.md +427 -0
- package/docs/getting-started.md +96 -0
- package/docs/guides/compose-queries.md +225 -0
- package/docs/guides/drizzle.md +129 -0
- package/docs/guides/extensions/dialects.md +40 -0
- package/docs/guides/extensions/overview.md +36 -0
- package/docs/guides/extensions/sources-and-clauses.md +82 -0
- package/docs/guides/extensions/typed-expressions.md +94 -0
- package/docs/guides/extensions/unsafe-syntax.md +27 -0
- package/docs/guides/json.md +65 -0
- package/docs/guides/mutations.md +138 -0
- package/docs/guides/select/conditions.md +110 -0
- package/docs/guides/select/grouping-and-windows.md +82 -0
- package/docs/guides/select/ordering-and-pagination.md +78 -0
- package/docs/guides/select/overview.md +139 -0
- package/docs/guides/sql-templates.md +157 -0
- package/docs/guides/vite-plugin.md +92 -0
- package/docs/index.md +111 -0
- package/docs/query-model/fragments.md +109 -0
- package/docs/query-model/result-shapes.md +125 -0
- package/docs/query-model/source-scope.md +182 -0
- package/docs/reference/introspection-support.md +196 -0
- package/docs/reference/mysql-snapshot.md +55 -0
- package/docs/reference/postgres-snapshot.md +61 -0
- package/docs/reference/sqlite-snapshot.md +53 -0
- package/docs/reference/supported-surface.md +104 -0
- package/docs/schema/catalog-model.md +58 -0
- package/docs/schema/code-generation.md +139 -0
- package/docs/schema/columns-and-writes.md +130 -0
- package/docs/schema/constraints-and-indexes.md +180 -0
- package/docs/schema/ddl-emission.md +75 -0
- package/docs/schema/diff.md +75 -0
- package/docs/schema/introspection.md +214 -0
- package/docs/schema/migration-plans.md +76 -0
- package/docs/schema/snapshots.md +93 -0
- package/docs/schema/storage-and-schema-sql.md +104 -0
- package/docs/schema/tables-and-names.md +106 -0
- package/docs/sql-semantic-types.md +141 -0
- package/docs/troubleshooting.md +151 -0
- package/package.json +132 -10
- package/skills/qubu/SKILL.md +42 -0
- package/skills/qubu/agents/openai.yaml +6 -0
- package/readme.md +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1276 @@
|
|
|
1
|
+
import { a as assertDialectCapability } from "./json-CUZlv4HT.mjs";
|
|
2
|
+
import { c as render, n as call, o as cast, r as schemaCall, t as createClause } from "./types-Cec0xzo4.mjs";
|
|
3
|
+
import { n as queryValidationError, t as QueryValidationError } from "./errors-Dxv73YJu.mjs";
|
|
4
|
+
import { c as makeExpression, h as parenthesize, i as identifier, l as makeSchemaExpression, m as isFragment, o as isExpression, p as fragment, t as createColumnReference, u as markExpressionCategory } from "./column-CXMxx8Hq.mjs";
|
|
5
|
+
import { i as value, t as asValue } from "./value-BvilP0oz.mjs";
|
|
6
|
+
import { a as isDistinctFrom, c as lt, d as notLike, f as expressionOperand, i as gte, l as lte, m as renderOperands, n as eq, o as isNotDistinctFrom, p as isNullOperand, r as gt, s as like, u as ne } from "./relational-DCZrrNia.mjs";
|
|
7
|
+
import { t as omit } from "./omit-OxV58AwX.mjs";
|
|
8
|
+
import { n as snakeCaseIdentifier, t as resolveSqlNames } from "./naming-QVCOnSj2.mjs";
|
|
9
|
+
import { r as exposeColumns, t as createSource } from "./source-DUoJVXmL.mjs";
|
|
10
|
+
import { S as identityColumn, T as lateral, a as column, b as externalGeneratedColumn, c as json, d as nullable, f as numeric, g as uuid, h as timestamp, i as boolean, l as nativeColumn, m as text, n as bigint, o as date, p as portableStorage, r as binary, s as integer, t as table, u as nativeStorage, w as alias, x as generatedColumn, y as externalDefault } from "./table-llv9tsZ8.mjs";
|
|
11
|
+
import { C as index, S as unsafeSchemaSql, c as check, d as references, f as unique, i as schema, l as foreignKey, o as catalogCheck, p as uniqueConstraint, s as catalogForeignKey, u as primaryKey } from "./registry-BufIskVN.mjs";
|
|
12
|
+
//#region src/execution.ts
|
|
13
|
+
function qubu(adapter) {
|
|
14
|
+
const client = createClient(adapter);
|
|
15
|
+
if (!isTransactionalQueryAdapter(adapter)) return client;
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
...client,
|
|
18
|
+
transaction(callback, options) {
|
|
19
|
+
return adapter.transaction(async (transactionAdapter) => callback(createClient(transactionAdapter)), options);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function createClient(adapter) {
|
|
24
|
+
const client = {
|
|
25
|
+
adapter,
|
|
26
|
+
execute(query, options) {
|
|
27
|
+
return execute(query, adapter, options);
|
|
28
|
+
},
|
|
29
|
+
rows(query, options) {
|
|
30
|
+
return executeRows(query, adapter, options);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const streaming = isStreamingQueryAdapter(adapter);
|
|
34
|
+
const explainable = isExplainableQueryAdapter(adapter);
|
|
35
|
+
if (!streaming && !explainable) return Object.freeze(client);
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
...client,
|
|
38
|
+
...streaming ? { stream(query, options) {
|
|
39
|
+
return stream(query, adapter, options);
|
|
40
|
+
} } : {},
|
|
41
|
+
...explainable ? { explain(query, options) {
|
|
42
|
+
return explain(query, adapter, options);
|
|
43
|
+
} } : {}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function isTransactionalQueryAdapter(adapter) {
|
|
47
|
+
return typeof adapter.transaction === "function";
|
|
48
|
+
}
|
|
49
|
+
function isStreamingQueryAdapter(adapter) {
|
|
50
|
+
return typeof adapter.stream === "function";
|
|
51
|
+
}
|
|
52
|
+
function isExplainableQueryAdapter(adapter) {
|
|
53
|
+
return typeof adapter.explain === "function";
|
|
54
|
+
}
|
|
55
|
+
function execute(first, second, options = {}) {
|
|
56
|
+
return executeInternal(first, second, options);
|
|
57
|
+
}
|
|
58
|
+
async function executeRows(first, second, options = {}) {
|
|
59
|
+
return (await executeInternal(first, second, options)).rows;
|
|
60
|
+
}
|
|
61
|
+
async function explain(first, second, options = {}) {
|
|
62
|
+
const query = isQuery(first) ? first : second;
|
|
63
|
+
const adapter = isQuery(first) ? second : first;
|
|
64
|
+
return adapter.explain(createExplainRequest(query, adapter, options));
|
|
65
|
+
}
|
|
66
|
+
function stream(first, second, options = {}) {
|
|
67
|
+
const query = isQuery(first) ? first : second;
|
|
68
|
+
const adapter = isQuery(first) ? second : first;
|
|
69
|
+
assertStreamableQuery(query);
|
|
70
|
+
return adapter.stream(createExecutionRequest(query, adapter, options));
|
|
71
|
+
}
|
|
72
|
+
async function executeInternal(first, second, options) {
|
|
73
|
+
const query = isQuery(first) ? first : second;
|
|
74
|
+
const adapter = isQuery(first) ? second : first;
|
|
75
|
+
const request = createExecutionRequest(query, adapter, options);
|
|
76
|
+
return adapter.execute(request);
|
|
77
|
+
}
|
|
78
|
+
function createExecutionRequest(query, adapter, options) {
|
|
79
|
+
const statement = render(query, { dialect: options.dialect ?? adapter.dialect });
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
statement,
|
|
82
|
+
queryKind: query.queryKind,
|
|
83
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function createExplainRequest(query, adapter, options) {
|
|
87
|
+
const request = createExecutionRequest(query, adapter, options);
|
|
88
|
+
if (options.analyze === true && query.queryKind !== "select" && query.queryKind !== "set") throw queryValidationError({
|
|
89
|
+
code: "invalid-explain-query",
|
|
90
|
+
context: "execution.explain",
|
|
91
|
+
path: ["analyze"],
|
|
92
|
+
message: `EXPLAIN ANALYZE is only available for read queries; received ${query.queryKind}`,
|
|
93
|
+
hint: "Remove analyze for a plan-only mutation EXPLAIN."
|
|
94
|
+
});
|
|
95
|
+
const dialect = options.dialect ?? adapter.dialect;
|
|
96
|
+
if (dialect.explain === void 0) throw queryValidationError({
|
|
97
|
+
code: "unsupported-explain-dialect",
|
|
98
|
+
context: "execution.explain",
|
|
99
|
+
path: ["dialect"],
|
|
100
|
+
message: `Dialect "${dialect.name}" does not provide an EXPLAIN rendering policy`,
|
|
101
|
+
hint: "Use PostgreSQL, SQLite, MySQL, or a custom dialect with an explain policy."
|
|
102
|
+
});
|
|
103
|
+
const statement = Object.freeze({
|
|
104
|
+
text: dialect.explain.render(request.statement.text, query.queryKind, options),
|
|
105
|
+
parameters: request.statement.parameters
|
|
106
|
+
});
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
...request,
|
|
109
|
+
statement
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function assertStreamableQuery(query) {
|
|
113
|
+
if (query.queryKind === "select" || query.queryKind === "set") return;
|
|
114
|
+
throw queryValidationError({
|
|
115
|
+
code: "invalid-stream-query",
|
|
116
|
+
context: "execution.stream",
|
|
117
|
+
path: ["queryKind"],
|
|
118
|
+
message: `Only SELECT and set-operation queries can be streamed; received ${query.queryKind}`,
|
|
119
|
+
hint: "Use execute() or executeRows() for mutation queries."
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function isQuery(value) {
|
|
123
|
+
return "render" in value && typeof value.render === "function";
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/expressions/case.ts
|
|
127
|
+
function caseWhen(condition, thenValue, elseValue) {
|
|
128
|
+
const thenExpression = asValue(thenValue);
|
|
129
|
+
const elseExpression = asValue(elseValue);
|
|
130
|
+
return makeSchemaExpression("operator", (context) => {
|
|
131
|
+
context.append("CASE WHEN ");
|
|
132
|
+
context.render(condition);
|
|
133
|
+
context.append(" THEN ");
|
|
134
|
+
context.render(thenExpression);
|
|
135
|
+
context.append(" ELSE ");
|
|
136
|
+
context.render(elseExpression);
|
|
137
|
+
context.append(" END");
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/expressions/sql.ts
|
|
142
|
+
function sqlTemplate(strings, values) {
|
|
143
|
+
return makeExpression("sql", (context) => {
|
|
144
|
+
strings.forEach((text, index) => {
|
|
145
|
+
context.append(text);
|
|
146
|
+
if (index >= values.length) return;
|
|
147
|
+
const value = values[index];
|
|
148
|
+
if (!isFragment(value)) {
|
|
149
|
+
context.parameter(value);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (isQueryFragment(value)) context.renderRelation(value);
|
|
153
|
+
else context.render(value);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function createSqlTag() {
|
|
158
|
+
return (strings, ...values) => sqlTemplate(strings, values);
|
|
159
|
+
}
|
|
160
|
+
const sqlTag = createSqlTag();
|
|
161
|
+
sqlTag.type = createSqlTag;
|
|
162
|
+
/**
|
|
163
|
+
* Build trusted SQL syntax while binding every ordinary substitution as a
|
|
164
|
+
* parameter. Qubu fragments remain composable substitutions.
|
|
165
|
+
*
|
|
166
|
+
* @remarks Template text is trusted and is not parsed. Use `identifier()` from
|
|
167
|
+
* `qubu/core` for runtime identifiers and `unsafeExpression()` for deliberately
|
|
168
|
+
* dynamic syntax. Use `sql.type<Output, SqlType>()` when the fragment's result
|
|
169
|
+
* is known.
|
|
170
|
+
*/
|
|
171
|
+
const sql = Object.freeze(sqlTag);
|
|
172
|
+
function isQueryFragment(value) {
|
|
173
|
+
return "queryKind" in value && "row" in value;
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/expressions/functions/aggregate.ts
|
|
177
|
+
function count(expression) {
|
|
178
|
+
return makeExpression("function", (context) => {
|
|
179
|
+
context.append("COUNT(");
|
|
180
|
+
if (expression) context.render(expression);
|
|
181
|
+
else context.append("*");
|
|
182
|
+
context.append(")");
|
|
183
|
+
}, "aggregate");
|
|
184
|
+
}
|
|
185
|
+
function countDistinct(expression) {
|
|
186
|
+
return makeExpression("function", (context) => {
|
|
187
|
+
context.append("COUNT(DISTINCT ");
|
|
188
|
+
context.render(expression);
|
|
189
|
+
context.append(")");
|
|
190
|
+
}, "aggregate");
|
|
191
|
+
}
|
|
192
|
+
function sum(expression) {
|
|
193
|
+
return markExpressionCategory(call("SUM", expression), "aggregate");
|
|
194
|
+
}
|
|
195
|
+
function avg(expression) {
|
|
196
|
+
return markExpressionCategory(call("AVG", expression), "aggregate");
|
|
197
|
+
}
|
|
198
|
+
function min(expression) {
|
|
199
|
+
return markExpressionCategory(call("MIN", expression), "aggregate");
|
|
200
|
+
}
|
|
201
|
+
function max(expression) {
|
|
202
|
+
return markExpressionCategory(call("MAX", expression), "aggregate");
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/expressions/functions/string.ts
|
|
206
|
+
function lower(expression) {
|
|
207
|
+
return schemaCall("LOWER", expression);
|
|
208
|
+
}
|
|
209
|
+
function upper(expression) {
|
|
210
|
+
return schemaCall("UPPER", expression);
|
|
211
|
+
}
|
|
212
|
+
function coalesce(...expressions) {
|
|
213
|
+
return schemaCall("COALESCE", ...expressions);
|
|
214
|
+
}
|
|
215
|
+
function concat(...argumentsToConcat) {
|
|
216
|
+
return schemaCall("CONCAT", ...argumentsToConcat);
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/expressions/functions/window.ts
|
|
220
|
+
function over(expression, window) {
|
|
221
|
+
return makeExpression("function", (context) => {
|
|
222
|
+
context.render(expression);
|
|
223
|
+
context.append(" OVER (");
|
|
224
|
+
let hasPart = false;
|
|
225
|
+
if (window?.partitionBy && window.partitionBy.length > 0) {
|
|
226
|
+
context.append("PARTITION BY ");
|
|
227
|
+
window.partitionBy.forEach((part, index) => {
|
|
228
|
+
if (index > 0) context.append(", ");
|
|
229
|
+
context.render(part);
|
|
230
|
+
});
|
|
231
|
+
hasPart = true;
|
|
232
|
+
}
|
|
233
|
+
if (window?.orderBy && window.orderBy.length > 0) {
|
|
234
|
+
if (hasPart) context.append(" ");
|
|
235
|
+
context.append("ORDER BY ");
|
|
236
|
+
window.orderBy.forEach((part, index) => {
|
|
237
|
+
if (index > 0) context.append(", ");
|
|
238
|
+
context.render(part);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
context.append(")");
|
|
242
|
+
}, "window");
|
|
243
|
+
}
|
|
244
|
+
function rowNumber() {
|
|
245
|
+
return markExpressionCategory(call("ROW_NUMBER"), "window");
|
|
246
|
+
}
|
|
247
|
+
function rank() {
|
|
248
|
+
return markExpressionCategory(call("RANK"), "window");
|
|
249
|
+
}
|
|
250
|
+
function denseRank() {
|
|
251
|
+
return markExpressionCategory(call("DENSE_RANK"), "window");
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/expressions/subquery.ts
|
|
255
|
+
function scalar(query) {
|
|
256
|
+
if (Object.keys(query.row).length !== 1) throw queryValidationError({
|
|
257
|
+
code: "invalid-subquery",
|
|
258
|
+
context: "expression.scalar.query",
|
|
259
|
+
path: [
|
|
260
|
+
"scalar",
|
|
261
|
+
"query",
|
|
262
|
+
"row"
|
|
263
|
+
],
|
|
264
|
+
message: "scalar() requires a query with exactly one selected column",
|
|
265
|
+
hint: "Select exactly one named field before wrapping the query in scalar()."
|
|
266
|
+
});
|
|
267
|
+
return makeExpression("subquery", (context) => context.renderRelation(parenthesize(query)));
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/expressions/json.ts
|
|
271
|
+
const jsonPathBrand = Symbol("json-path");
|
|
272
|
+
/**
|
|
273
|
+
* Build a root-relative JSON path from object keys and array indexes.
|
|
274
|
+
*
|
|
275
|
+
* @throws {TypeError} When an index is negative, fractional, or unsafe.
|
|
276
|
+
*/
|
|
277
|
+
function jsonPath(...segments) {
|
|
278
|
+
for (const [index, segment] of segments.entries()) if (typeof segment === "number" && (!Number.isSafeInteger(segment) || segment < 0)) throw queryValidationError({
|
|
279
|
+
code: "invalid-json-path",
|
|
280
|
+
context: "expression.json-path",
|
|
281
|
+
path: ["jsonPath", index],
|
|
282
|
+
message: "JSON path indexes must be non-negative safe integers",
|
|
283
|
+
hint: "Use a non-negative safe integer for each JSON array index."
|
|
284
|
+
});
|
|
285
|
+
return Object.freeze({
|
|
286
|
+
[jsonPathBrand]: true,
|
|
287
|
+
pathKind: "json",
|
|
288
|
+
segments: Object.freeze([...segments])
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function jsonScalar(document, path, kind) {
|
|
292
|
+
return makeSchemaExpression("function", (context) => {
|
|
293
|
+
assertDialectCapability(context.dialect, "json");
|
|
294
|
+
if (!context.dialect.json) throw new Error(`Dialect "${context.dialect.name}" advertises JSON support without a JSON renderer`);
|
|
295
|
+
context.dialect.json.renderScalar(context, document, path.segments, kind);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/** Extract a JSON string, returning SQL NULL for missing, null, or non-string values. */
|
|
299
|
+
function jsonText(document, path) {
|
|
300
|
+
return jsonScalar(document, path, "text");
|
|
301
|
+
}
|
|
302
|
+
/** Extract a JSON number, returning SQL NULL for missing, null, or non-number values. */
|
|
303
|
+
function jsonNumber(document, path) {
|
|
304
|
+
return jsonScalar(document, path, "number");
|
|
305
|
+
}
|
|
306
|
+
/** Extract a JSON boolean, returning SQL NULL for missing, null, or non-boolean values. */
|
|
307
|
+
function jsonBoolean(document, path) {
|
|
308
|
+
return jsonScalar(document, path, "boolean");
|
|
309
|
+
}
|
|
310
|
+
/** Test whether a path exists, including when its value is JSON null. */
|
|
311
|
+
function jsonExists(document, path) {
|
|
312
|
+
return makeSchemaExpression("function", (context) => {
|
|
313
|
+
assertDialectCapability(context.dialect, "json");
|
|
314
|
+
if (!context.dialect.json) throw new Error(`Dialect "${context.dialect.name}" advertises JSON support without a JSON renderer`);
|
|
315
|
+
context.dialect.json.renderExists(context, document, path.segments);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/expressions/operators/arithmetic.ts
|
|
320
|
+
function arithmetic(operator, left, right) {
|
|
321
|
+
const rightExpression = expressionOperand(right);
|
|
322
|
+
return makeSchemaExpression("operator", (context) => {
|
|
323
|
+
context.append("(");
|
|
324
|
+
context.render(left);
|
|
325
|
+
context.append(` ${operator} `);
|
|
326
|
+
context.render(rightExpression);
|
|
327
|
+
context.append(")");
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
const add = (left, right) => arithmetic("+", left, right);
|
|
331
|
+
const subtract = (left, right) => arithmetic("-", left, right);
|
|
332
|
+
const multiply = (left, right) => arithmetic("*", left, right);
|
|
333
|
+
const divide = (left, right) => arithmetic("/", left, right);
|
|
334
|
+
const modulo = (left, right) => arithmetic("%", left, right);
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/expressions/operators/logic.ts
|
|
337
|
+
function composeConditions(conditions, separator, name) {
|
|
338
|
+
if (conditions.length === 0) throw queryValidationError({
|
|
339
|
+
code: "invalid-boolean-expression",
|
|
340
|
+
context: `expression.${name}`,
|
|
341
|
+
path: [name],
|
|
342
|
+
message: `${name}() requires at least one condition`,
|
|
343
|
+
hint: `Pass at least one condition to ${name}(), or use omit for a conditional predicate.`
|
|
344
|
+
});
|
|
345
|
+
const presentConditions = conditions.filter((condition) => condition !== omit);
|
|
346
|
+
if (presentConditions.length === 0) return omit;
|
|
347
|
+
return makeSchemaExpression("operator", (context) => {
|
|
348
|
+
context.append("(");
|
|
349
|
+
renderOperands(context, presentConditions, separator);
|
|
350
|
+
context.append(")");
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function and(...conditions) {
|
|
354
|
+
return composeConditions(conditions, " AND ", "and");
|
|
355
|
+
}
|
|
356
|
+
function or(...conditions) {
|
|
357
|
+
return composeConditions(conditions, " OR ", "or");
|
|
358
|
+
}
|
|
359
|
+
function not(condition) {
|
|
360
|
+
return makeSchemaExpression("operator", (context) => {
|
|
361
|
+
context.append("(NOT ");
|
|
362
|
+
context.render(condition);
|
|
363
|
+
context.append(")");
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/expressions/operators/comparison/range.ts
|
|
368
|
+
function between(expression, lower, upper) {
|
|
369
|
+
if (isNullOperand(lower) || isNullOperand(upper)) throw new TypeError("between() does not accept NULL bounds; use an explicit NULL predicate");
|
|
370
|
+
const lowerExpression = expressionOperand(lower);
|
|
371
|
+
const upperExpression = expressionOperand(upper);
|
|
372
|
+
return makeSchemaExpression("operator", (context) => {
|
|
373
|
+
context.append("(");
|
|
374
|
+
context.render(expression);
|
|
375
|
+
context.append(" BETWEEN ");
|
|
376
|
+
context.render(lowerExpression);
|
|
377
|
+
context.append(" AND ");
|
|
378
|
+
context.render(upperExpression);
|
|
379
|
+
context.append(")");
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function inList(expression, values) {
|
|
383
|
+
const valueExpressions = values.map(expressionOperand);
|
|
384
|
+
return makeSchemaExpression("operator", (context) => {
|
|
385
|
+
if (values.length === 0) {
|
|
386
|
+
context.append("(1 = 0)");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
context.append("(");
|
|
390
|
+
context.render(expression);
|
|
391
|
+
context.append(" IN (");
|
|
392
|
+
valueExpressions.forEach((value, index) => {
|
|
393
|
+
if (index > 0) context.append(", ");
|
|
394
|
+
context.render(value);
|
|
395
|
+
});
|
|
396
|
+
context.append("))");
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function notIn(expression, values) {
|
|
400
|
+
const valueExpressions = values.map(expressionOperand);
|
|
401
|
+
return makeSchemaExpression("operator", (context) => {
|
|
402
|
+
if (values.length === 0) {
|
|
403
|
+
context.append("(1 = 1)");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
context.append("(");
|
|
407
|
+
context.render(expression);
|
|
408
|
+
context.append(" NOT IN (");
|
|
409
|
+
valueExpressions.forEach((value, index) => {
|
|
410
|
+
if (index > 0) context.append(", ");
|
|
411
|
+
context.render(value);
|
|
412
|
+
});
|
|
413
|
+
context.append("))");
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/expressions/operators/comparison/subquery.ts
|
|
418
|
+
function inQuery(expression, query) {
|
|
419
|
+
return makeExpression("subquery", (context) => {
|
|
420
|
+
context.append("(");
|
|
421
|
+
context.render(expression);
|
|
422
|
+
context.append(" IN ");
|
|
423
|
+
context.renderRelation(parenthesize(query));
|
|
424
|
+
context.append(")");
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function exists(query) {
|
|
428
|
+
return makeExpression("subquery", (context) => {
|
|
429
|
+
context.append("EXISTS ");
|
|
430
|
+
context.renderRelation(parenthesize(query));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function notExists(query) {
|
|
434
|
+
return makeExpression("subquery", (context) => {
|
|
435
|
+
context.append("NOT EXISTS ");
|
|
436
|
+
context.renderRelation(parenthesize(query));
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/expressions/operators/comparison/null.ts
|
|
441
|
+
function isNull(expression) {
|
|
442
|
+
return makeSchemaExpression("operator", (context) => {
|
|
443
|
+
context.append("(");
|
|
444
|
+
context.render(expression);
|
|
445
|
+
context.append(" IS NULL)");
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function isNotNull(expression) {
|
|
449
|
+
return makeSchemaExpression("operator", (context) => {
|
|
450
|
+
context.append("(");
|
|
451
|
+
context.render(expression);
|
|
452
|
+
context.append(" IS NOT NULL)");
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function isTrue(expression) {
|
|
456
|
+
return makeSchemaExpression("operator", (context) => {
|
|
457
|
+
context.render(expression);
|
|
458
|
+
context.append(" IS TRUE");
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/query/selection.ts
|
|
463
|
+
/**
|
|
464
|
+
* Return every known source column as a named projection object.
|
|
465
|
+
*
|
|
466
|
+
* The result can be passed directly to `select()` or spread into a larger
|
|
467
|
+
* projection, such as `{ ...all(users), displayName: upper(users.name) }`.
|
|
468
|
+
*/
|
|
469
|
+
function all(source) {
|
|
470
|
+
return Object.freeze({ ...source.columns });
|
|
471
|
+
}
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/query/types.ts
|
|
474
|
+
function createQuery(queryKind, row, render) {
|
|
475
|
+
return Object.freeze({
|
|
476
|
+
queryKind,
|
|
477
|
+
row,
|
|
478
|
+
...fragment(render)
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/query/select/render.ts
|
|
483
|
+
function renderSelection(selection, context) {
|
|
484
|
+
assertNamedSelection(selection);
|
|
485
|
+
const entries = Object.entries(selection).filter(([, expression]) => expression !== omit);
|
|
486
|
+
if (entries.length === 0) throw queryValidationError({
|
|
487
|
+
code: "invalid-selection",
|
|
488
|
+
context: "select.projection",
|
|
489
|
+
path: ["selection"],
|
|
490
|
+
message: "select() requires at least one field",
|
|
491
|
+
hint: "Pass a named projection object with at least one expression."
|
|
492
|
+
});
|
|
493
|
+
entries.forEach(([name, expression], index) => {
|
|
494
|
+
if (index > 0) context.append(", ");
|
|
495
|
+
if (!isExpression(expression)) throw queryValidationError({
|
|
496
|
+
code: "invalid-selection",
|
|
497
|
+
context: "select.projection",
|
|
498
|
+
path: ["selection", name],
|
|
499
|
+
message: `Selection field "${name}" must be an expression`,
|
|
500
|
+
hint: "Use a Qubu expression such as table.column, value(), or a function."
|
|
501
|
+
});
|
|
502
|
+
const outputName = context.projectionMode === "result" ? name : snakeCaseIdentifier(name);
|
|
503
|
+
context.render(expression);
|
|
504
|
+
context.append(" AS ");
|
|
505
|
+
context.render(identifier(outputName));
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function selectionRow(selection) {
|
|
509
|
+
assertNamedSelection(selection);
|
|
510
|
+
return Object.fromEntries(Object.entries(selection).filter(([, expression]) => expression !== omit).map(([name]) => [name, void 0]));
|
|
511
|
+
}
|
|
512
|
+
function assertNamedSelection(selection) {
|
|
513
|
+
if (Array.isArray(selection)) throw queryValidationError({
|
|
514
|
+
code: "invalid-selection",
|
|
515
|
+
context: "select.projection",
|
|
516
|
+
path: ["selection"],
|
|
517
|
+
message: "Selection must be a named object projection",
|
|
518
|
+
hint: "Pass { fieldName: expression } to select()."
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/query/select/validate.ts
|
|
523
|
+
function validateClauses(clauses) {
|
|
524
|
+
const singletonKinds = /* @__PURE__ */ new Set([
|
|
525
|
+
"distinct",
|
|
526
|
+
"from",
|
|
527
|
+
"where",
|
|
528
|
+
"group-by",
|
|
529
|
+
"having",
|
|
530
|
+
"order-by",
|
|
531
|
+
"offset",
|
|
532
|
+
"fetch",
|
|
533
|
+
"row-lock"
|
|
534
|
+
]);
|
|
535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
536
|
+
for (const [index, clause] of clauses.entries()) {
|
|
537
|
+
if (!singletonKinds.has(clause.clauseKind)) continue;
|
|
538
|
+
if (seen.has(clause.clauseKind)) {
|
|
539
|
+
const hint = clause.clauseKind === "where" || clause.clauseKind === "having" ? "Compose repeated conditions with and() or or()." : clause.clauseKind === "order-by" ? "Combine terms in one orderBy() call." : clause.clauseKind === "row-lock" ? "Combine the mode and wait policy in one rowLock() call." : "Keep one clause of this kind.";
|
|
540
|
+
throw queryValidationError({
|
|
541
|
+
code: "duplicate-clause",
|
|
542
|
+
context: "select.clauses",
|
|
543
|
+
path: ["clauses", index],
|
|
544
|
+
message: `Only one ${clause.clauseKind} clause is allowed`,
|
|
545
|
+
hint
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
seen.add(clause.clauseKind);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/query/clauses/pagination.ts
|
|
553
|
+
function toPaginationPart(clause) {
|
|
554
|
+
return clause.clauseKind === "offset" ? {
|
|
555
|
+
kind: "offset",
|
|
556
|
+
rows: clause.rows
|
|
557
|
+
} : {
|
|
558
|
+
kind: "fetch",
|
|
559
|
+
rows: clause.rows,
|
|
560
|
+
direction: clause.direction
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function renderPagination(context, clauses) {
|
|
564
|
+
const parts = clauses.map(toPaginationPart);
|
|
565
|
+
if (context.dialect.pagination) {
|
|
566
|
+
context.dialect.pagination.render(context, parts);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
parts.forEach((part, index) => {
|
|
570
|
+
if (index > 0) context.append(" ");
|
|
571
|
+
if (part.kind === "offset") {
|
|
572
|
+
context.append("OFFSET ");
|
|
573
|
+
context.parameter(part.rows);
|
|
574
|
+
context.append(" ROWS");
|
|
575
|
+
} else {
|
|
576
|
+
context.append(`FETCH ${part.direction} `);
|
|
577
|
+
context.parameter(part.rows);
|
|
578
|
+
context.append(" ROWS ONLY");
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function offset(rows) {
|
|
583
|
+
if (!Number.isInteger(rows) || rows < 0) throw queryValidationError({
|
|
584
|
+
code: "invalid-pagination",
|
|
585
|
+
context: "select.pagination.offset",
|
|
586
|
+
path: ["offset"],
|
|
587
|
+
message: "offset() requires a non-negative integer",
|
|
588
|
+
hint: "Pass a non-negative integer row count."
|
|
589
|
+
});
|
|
590
|
+
return Object.assign(createClause("offset", "after-select", 90, (context) => renderPagination(context, [{
|
|
591
|
+
clauseKind: "offset",
|
|
592
|
+
rows
|
|
593
|
+
}])), {
|
|
594
|
+
clauseKind: "offset",
|
|
595
|
+
rows
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
function fetchRows(direction, rows) {
|
|
599
|
+
if (!Number.isInteger(rows) || rows < 0) throw queryValidationError({
|
|
600
|
+
code: "invalid-pagination",
|
|
601
|
+
context: `select.pagination.fetch${direction === "FIRST" ? "First" : "Next"}`,
|
|
602
|
+
path: ["fetch", direction],
|
|
603
|
+
message: "fetch rows require a non-negative integer",
|
|
604
|
+
hint: "Pass a non-negative integer row count."
|
|
605
|
+
});
|
|
606
|
+
return Object.assign(createClause("fetch", "after-select", 100, (context) => renderPagination(context, [{
|
|
607
|
+
clauseKind: "fetch",
|
|
608
|
+
direction,
|
|
609
|
+
rows
|
|
610
|
+
}])), {
|
|
611
|
+
clauseKind: "fetch",
|
|
612
|
+
direction,
|
|
613
|
+
rows
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
function fetchFirst(rows) {
|
|
617
|
+
return fetchRows("FIRST", rows);
|
|
618
|
+
}
|
|
619
|
+
function fetchNext(rows) {
|
|
620
|
+
return fetchRows("NEXT", rows);
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/query/select.ts
|
|
624
|
+
function select(selection, ...parts) {
|
|
625
|
+
const normalizedClauses = parts.filter((part) => part !== omit);
|
|
626
|
+
validateClauses(normalizedClauses);
|
|
627
|
+
const orderedClauses = normalizedClauses.map((clause, index) => ({
|
|
628
|
+
clause,
|
|
629
|
+
index
|
|
630
|
+
})).sort((left, right) => left.clause.order - right.clause.order || left.index - right.index).map(({ clause }) => clause);
|
|
631
|
+
return createQuery("select", selectionRow(selection), (context) => {
|
|
632
|
+
const beforeSelect = orderedClauses.filter((clause) => clause.placement === "before-select");
|
|
633
|
+
const afterSelect = orderedClauses.filter((clause) => clause.placement === "after-select");
|
|
634
|
+
const paginationClauses = afterSelect.filter((clause) => clause.clauseKind === "offset" || clause.clauseKind === "fetch");
|
|
635
|
+
let renderedPagination = false;
|
|
636
|
+
for (const clause of beforeSelect) {
|
|
637
|
+
if (clause.clauseKind === "correlate") continue;
|
|
638
|
+
context.render(clause);
|
|
639
|
+
context.append(" ");
|
|
640
|
+
}
|
|
641
|
+
context.append("SELECT ");
|
|
642
|
+
const distinctClause = afterSelect.find((clause) => clause.clauseKind === "distinct");
|
|
643
|
+
if (distinctClause) {
|
|
644
|
+
context.render(distinctClause);
|
|
645
|
+
context.append(" ");
|
|
646
|
+
}
|
|
647
|
+
renderSelection(selection, context);
|
|
648
|
+
for (const clause of afterSelect) {
|
|
649
|
+
if (clause.clauseKind === "correlate") continue;
|
|
650
|
+
if (clause === distinctClause) continue;
|
|
651
|
+
if (clause.clauseKind === "offset" || clause.clauseKind === "fetch") {
|
|
652
|
+
if (renderedPagination) continue;
|
|
653
|
+
renderedPagination = true;
|
|
654
|
+
context.append(" ");
|
|
655
|
+
renderPagination(context, paginationClauses);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
context.append(" ");
|
|
659
|
+
context.render(clause);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region src/query/set.ts
|
|
665
|
+
function setOperation(operator, left, right) {
|
|
666
|
+
return {
|
|
667
|
+
queryKind: "set",
|
|
668
|
+
row: left.row,
|
|
669
|
+
render: (context) => {
|
|
670
|
+
context.render(parenthesize(left));
|
|
671
|
+
context.append(` ${operator} `);
|
|
672
|
+
context.render(parenthesize(right));
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function union(left, right) {
|
|
677
|
+
return setOperation("UNION", left, right);
|
|
678
|
+
}
|
|
679
|
+
function unionAll(left, right) {
|
|
680
|
+
return setOperation("UNION ALL", left, right);
|
|
681
|
+
}
|
|
682
|
+
function intersect(left, right) {
|
|
683
|
+
return setOperation("INTERSECT", left, right);
|
|
684
|
+
}
|
|
685
|
+
function except(left, right) {
|
|
686
|
+
return setOperation("EXCEPT", left, right);
|
|
687
|
+
}
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/query/clauses/correlate.ts
|
|
690
|
+
function correlate(...sources) {
|
|
691
|
+
return Object.assign(createClause("correlate", "before-select", 0, (_context) => {}), {
|
|
692
|
+
clauseKind: "correlate",
|
|
693
|
+
sources
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/query/clauses/distinct.ts
|
|
698
|
+
function distinct() {
|
|
699
|
+
return Object.assign(createClause("distinct", "after-select", 20, (context) => {
|
|
700
|
+
context.append("DISTINCT");
|
|
701
|
+
}), { clauseKind: "distinct" });
|
|
702
|
+
}
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/query/clauses/from.ts
|
|
705
|
+
function from(...sources) {
|
|
706
|
+
return Object.assign(createClause("from", "after-select", 30, (context) => {
|
|
707
|
+
context.append("FROM ");
|
|
708
|
+
sources.forEach((source, index) => {
|
|
709
|
+
if (index > 0) context.append(", ");
|
|
710
|
+
context.render(source);
|
|
711
|
+
});
|
|
712
|
+
}), {
|
|
713
|
+
clauseKind: "from",
|
|
714
|
+
sources
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/query/clauses/group-by.ts
|
|
719
|
+
function groupBy(...expressions) {
|
|
720
|
+
return Object.assign(createClause("group-by", "after-select", 60, (context) => {
|
|
721
|
+
context.append("GROUP BY ");
|
|
722
|
+
expressions.forEach((expression, index) => {
|
|
723
|
+
if (index > 0) context.append(", ");
|
|
724
|
+
context.render(expression);
|
|
725
|
+
});
|
|
726
|
+
}), {
|
|
727
|
+
clauseKind: "group-by",
|
|
728
|
+
expressions
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/query/clauses/having.ts
|
|
733
|
+
function having(condition) {
|
|
734
|
+
if (condition === omit) return omit;
|
|
735
|
+
return Object.assign(createClause("having", "after-select", 70, (context) => {
|
|
736
|
+
context.append("HAVING ");
|
|
737
|
+
context.render(condition);
|
|
738
|
+
}), {
|
|
739
|
+
clauseKind: "having",
|
|
740
|
+
condition
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/query/clauses/joins.ts
|
|
745
|
+
function join(joinType, source, condition) {
|
|
746
|
+
return Object.assign(createClause("join", "after-select", 40, (context) => {
|
|
747
|
+
context.append(`${joinType} JOIN `);
|
|
748
|
+
context.render(source);
|
|
749
|
+
if (condition) {
|
|
750
|
+
context.append(" ON ");
|
|
751
|
+
context.render(condition);
|
|
752
|
+
}
|
|
753
|
+
}), {
|
|
754
|
+
clauseKind: "join",
|
|
755
|
+
joinType,
|
|
756
|
+
source,
|
|
757
|
+
condition
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
function innerJoin(source, condition) {
|
|
761
|
+
return join("INNER", source, condition);
|
|
762
|
+
}
|
|
763
|
+
function leftJoin(source, condition) {
|
|
764
|
+
return join("LEFT", source, condition);
|
|
765
|
+
}
|
|
766
|
+
function rightJoin(source, condition) {
|
|
767
|
+
return join("RIGHT", source, condition);
|
|
768
|
+
}
|
|
769
|
+
function fullJoin(source, condition) {
|
|
770
|
+
return join("FULL", source, condition);
|
|
771
|
+
}
|
|
772
|
+
function crossJoin(source) {
|
|
773
|
+
return join("CROSS", source);
|
|
774
|
+
}
|
|
775
|
+
function naturalJoin(source) {
|
|
776
|
+
return join("NATURAL", source);
|
|
777
|
+
}
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/query/clauses/order-by.ts
|
|
780
|
+
function orderTerm(expression, direction, nulls) {
|
|
781
|
+
const base = makeExpression("operator", (context) => {
|
|
782
|
+
context.render(expression);
|
|
783
|
+
if (direction) context.append(` ${direction}`);
|
|
784
|
+
if (nulls) context.append(` NULLS ${nulls}`);
|
|
785
|
+
});
|
|
786
|
+
return Object.freeze({
|
|
787
|
+
...base,
|
|
788
|
+
orderKind: "term",
|
|
789
|
+
expression,
|
|
790
|
+
direction,
|
|
791
|
+
nulls
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
function order(expression, direction, nulls) {
|
|
795
|
+
return orderTerm(expression, direction, nulls);
|
|
796
|
+
}
|
|
797
|
+
function asc(expression, nulls) {
|
|
798
|
+
return orderTerm(expression, "ASC", nulls);
|
|
799
|
+
}
|
|
800
|
+
function desc(expression, nulls) {
|
|
801
|
+
return orderTerm(expression, "DESC", nulls);
|
|
802
|
+
}
|
|
803
|
+
function nullsFirst(expression) {
|
|
804
|
+
return orderTerm(expression, void 0, "FIRST");
|
|
805
|
+
}
|
|
806
|
+
function nullsLast(expression) {
|
|
807
|
+
return orderTerm(expression, void 0, "LAST");
|
|
808
|
+
}
|
|
809
|
+
function orderBy(...parts) {
|
|
810
|
+
const presentParts = parts.filter((part) => part !== omit);
|
|
811
|
+
if (presentParts.length === 0) return omit;
|
|
812
|
+
const terms = presentParts.map((part) => "orderKind" in part ? part : orderTerm(part));
|
|
813
|
+
return Object.assign(createClause("order-by", "after-select", 80, (context) => {
|
|
814
|
+
context.append("ORDER BY ");
|
|
815
|
+
terms.forEach((term, index) => {
|
|
816
|
+
if (index > 0) context.append(", ");
|
|
817
|
+
context.render(term);
|
|
818
|
+
});
|
|
819
|
+
}), {
|
|
820
|
+
clauseKind: "order-by",
|
|
821
|
+
terms
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
//#endregion
|
|
825
|
+
//#region src/query/clauses/row-lock.ts
|
|
826
|
+
const rowLockModes = [
|
|
827
|
+
"update",
|
|
828
|
+
"no-key-update",
|
|
829
|
+
"share",
|
|
830
|
+
"key-share"
|
|
831
|
+
];
|
|
832
|
+
const rowLockWaitPolicies = [
|
|
833
|
+
"default",
|
|
834
|
+
"nowait",
|
|
835
|
+
"skip-locked"
|
|
836
|
+
];
|
|
837
|
+
function rowLock(modeOrOptions = "update", wait = "default") {
|
|
838
|
+
const mode = typeof modeOrOptions === "string" ? modeOrOptions : modeOrOptions.mode ?? "update";
|
|
839
|
+
const waitPolicy = typeof modeOrOptions === "string" ? wait : modeOrOptions.wait ?? modeOrOptions.waitPolicy ?? "default";
|
|
840
|
+
validateRowLockMode(mode);
|
|
841
|
+
validateRowLockWaitPolicy(waitPolicy);
|
|
842
|
+
return Object.assign(createClause("row-lock", "after-select", 110, (context) => {
|
|
843
|
+
assertDialectCapability(context.dialect, "row-locking");
|
|
844
|
+
const policy = context.dialect.rowLocking;
|
|
845
|
+
if (!policy) throw new Error(`Dialect "${context.dialect.name}" advertises the "row-locking" capability without a row-locking policy`);
|
|
846
|
+
policy.render(context, mode, waitPolicy);
|
|
847
|
+
}), {
|
|
848
|
+
clauseKind: "row-lock",
|
|
849
|
+
mode,
|
|
850
|
+
wait: waitPolicy
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
function validateRowLockMode(mode) {
|
|
854
|
+
if (rowLockModes.includes(mode)) return;
|
|
855
|
+
throw queryValidationError({
|
|
856
|
+
code: "invalid-row-lock",
|
|
857
|
+
context: "select.row-lock",
|
|
858
|
+
path: ["rowLock", "mode"],
|
|
859
|
+
message: `rowLock() does not support the "${String(mode)}" lock mode`,
|
|
860
|
+
hint: "Use update, no-key-update, share, or key-share."
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
function validateRowLockWaitPolicy(wait) {
|
|
864
|
+
if (rowLockWaitPolicies.includes(wait)) return;
|
|
865
|
+
throw queryValidationError({
|
|
866
|
+
code: "invalid-row-lock",
|
|
867
|
+
context: "select.row-lock",
|
|
868
|
+
path: ["rowLock", "wait"],
|
|
869
|
+
message: `rowLock() does not support the "${String(wait)}" wait policy`,
|
|
870
|
+
hint: "Use default, nowait, or skip-locked."
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/query/clauses/where.ts
|
|
875
|
+
function where(condition) {
|
|
876
|
+
if (condition === omit) return omit;
|
|
877
|
+
return Object.assign(createClause("where", "after-select", 50, (context) => {
|
|
878
|
+
context.append("WHERE ");
|
|
879
|
+
context.render(condition);
|
|
880
|
+
}), {
|
|
881
|
+
clauseKind: "where",
|
|
882
|
+
condition
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
//#endregion
|
|
886
|
+
//#region src/query/clauses/with.ts
|
|
887
|
+
function cte(name, query) {
|
|
888
|
+
const reference = identifier(name);
|
|
889
|
+
const source = createSource("cte", (context) => context.render(reference), reference);
|
|
890
|
+
const sqlNames = resolveSqlNames(Object.keys(query.row).map((fieldName) => ({ fieldName })));
|
|
891
|
+
const columns = Object.fromEntries(Object.keys(query.row).map((fieldName) => [fieldName, createColumnReference(sqlNames[fieldName], reference, fieldName)]));
|
|
892
|
+
Object.assign(source, {
|
|
893
|
+
cteName: name,
|
|
894
|
+
query,
|
|
895
|
+
columns
|
|
896
|
+
});
|
|
897
|
+
exposeColumns(source, columns);
|
|
898
|
+
return source;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Define a typed recursive `WITH` source from an anchor and one recursive
|
|
902
|
+
* member. The callback receives a forward reference that must be introduced
|
|
903
|
+
* through the member query's normal FROM or JOIN scope.
|
|
904
|
+
*/
|
|
905
|
+
function recursiveCte(name, anchor, member) {
|
|
906
|
+
const reference = identifier(name);
|
|
907
|
+
const source = createSource("cte", (context) => context.render(reference), reference);
|
|
908
|
+
const sqlNames = resolveSqlNames(Object.keys(anchor.row).map((fieldName) => ({ fieldName })));
|
|
909
|
+
const columns = Object.fromEntries(Object.keys(anchor.row).map((fieldName) => [fieldName, createColumnReference(sqlNames[fieldName], reference, fieldName)]));
|
|
910
|
+
Object.assign(source, {
|
|
911
|
+
cteName: name,
|
|
912
|
+
query: anchor,
|
|
913
|
+
columns
|
|
914
|
+
});
|
|
915
|
+
exposeColumns(source, columns);
|
|
916
|
+
const memberQuery = member(source);
|
|
917
|
+
const query = Object.freeze({
|
|
918
|
+
...createQuery("set", anchor.row, (context) => {
|
|
919
|
+
context.render(anchor);
|
|
920
|
+
context.append(" UNION ALL ");
|
|
921
|
+
context.render(memberQuery);
|
|
922
|
+
}),
|
|
923
|
+
recursive: true
|
|
924
|
+
});
|
|
925
|
+
Object.assign(source, { query });
|
|
926
|
+
return source;
|
|
927
|
+
}
|
|
928
|
+
function withCte(...ctes) {
|
|
929
|
+
return Object.assign(createClause("with", "before-select", 10, (context) => {
|
|
930
|
+
context.append(ctes.some((entry) => entry.query.recursive) ? "WITH RECURSIVE " : "WITH ");
|
|
931
|
+
ctes.forEach((entry, index) => {
|
|
932
|
+
if (index > 0) context.append(", ");
|
|
933
|
+
context.render(identifier(entry.cteName));
|
|
934
|
+
if (entry.query.recursive) {
|
|
935
|
+
const sqlNames = resolveSqlNames(Object.keys(entry.query.row).map((fieldName) => ({ fieldName })));
|
|
936
|
+
context.append(" (");
|
|
937
|
+
Object.keys(entry.query.row).forEach((fieldName, columnIndex) => {
|
|
938
|
+
if (columnIndex > 0) context.append(", ");
|
|
939
|
+
context.render(identifier(sqlNames[fieldName]));
|
|
940
|
+
});
|
|
941
|
+
context.append(")");
|
|
942
|
+
}
|
|
943
|
+
context.append(" AS ");
|
|
944
|
+
context.renderRelation(parenthesize(entry.query));
|
|
945
|
+
});
|
|
946
|
+
}), {
|
|
947
|
+
clauseKind: "with",
|
|
948
|
+
ctes
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
//#endregion
|
|
952
|
+
//#region src/query/mutation/types.ts
|
|
953
|
+
function createMutation(queryKind, row, render) {
|
|
954
|
+
return {
|
|
955
|
+
queryKind,
|
|
956
|
+
row,
|
|
957
|
+
render
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function allowAll() {
|
|
961
|
+
return Object.freeze({
|
|
962
|
+
clauseKind: "allow-all",
|
|
963
|
+
render: () => void 0
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
function validateMutationClauses(kind, clauses) {
|
|
967
|
+
let whereSeen = false;
|
|
968
|
+
let returningSeen = false;
|
|
969
|
+
let allowAllSeen = false;
|
|
970
|
+
for (const clause of clauses) if (clause.clauseKind === "where") {
|
|
971
|
+
if (whereSeen) throw queryValidationError({
|
|
972
|
+
code: "duplicate-clause",
|
|
973
|
+
context: `mutation.${kind.toLowerCase()}.clauses`,
|
|
974
|
+
path: ["clauses", "where"],
|
|
975
|
+
message: `${kind} accepts only one WHERE clause`,
|
|
976
|
+
hint: "Compose repeated conditions with and() or or()."
|
|
977
|
+
});
|
|
978
|
+
whereSeen = true;
|
|
979
|
+
} else if (clause.clauseKind === "returning") {
|
|
980
|
+
if (returningSeen) throw queryValidationError({
|
|
981
|
+
code: "duplicate-clause",
|
|
982
|
+
context: `mutation.${kind.toLowerCase()}.clauses`,
|
|
983
|
+
path: ["clauses", "returning"],
|
|
984
|
+
message: `${kind} accepts only one RETURNING clause`,
|
|
985
|
+
hint: "Use one returning() clause containing the complete projection."
|
|
986
|
+
});
|
|
987
|
+
returningSeen = true;
|
|
988
|
+
} else if (clause.clauseKind === "allow-all") {
|
|
989
|
+
if (allowAllSeen) throw queryValidationError({
|
|
990
|
+
code: "duplicate-clause",
|
|
991
|
+
context: `mutation.${kind.toLowerCase()}.clauses`,
|
|
992
|
+
path: ["clauses", "allowAll"],
|
|
993
|
+
message: `${kind} accepts only one allowAll() marker`,
|
|
994
|
+
hint: "Keep one allowAll() marker when the mutation is intentionally unrestricted."
|
|
995
|
+
});
|
|
996
|
+
allowAllSeen = true;
|
|
997
|
+
}
|
|
998
|
+
if (!whereSeen && !allowAllSeen) throw queryValidationError({
|
|
999
|
+
code: "unsafe-mutation",
|
|
1000
|
+
context: `mutation.${kind.toLowerCase()}.safety`,
|
|
1001
|
+
path: ["clauses"],
|
|
1002
|
+
message: `${kind} requires a WHERE clause; use allowAll() to opt into an unrestricted mutation`,
|
|
1003
|
+
hint: "Add where(...) to target rows, or add allowAll() to make the unrestricted intent explicit."
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
//#endregion
|
|
1007
|
+
//#region src/query/mutation/insert.ts
|
|
1008
|
+
function values(...rows) {
|
|
1009
|
+
return Object.freeze({
|
|
1010
|
+
insertKind: "values",
|
|
1011
|
+
rows
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function defaultValues() {
|
|
1015
|
+
return Object.freeze({ insertKind: "default-values" });
|
|
1016
|
+
}
|
|
1017
|
+
function insertSelect(query, columns) {
|
|
1018
|
+
return Object.freeze({
|
|
1019
|
+
insertKind: "select",
|
|
1020
|
+
query,
|
|
1021
|
+
columns
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
function insertInto(table, source, ...clauses) {
|
|
1025
|
+
validateInsert(table, source);
|
|
1026
|
+
const insertClauses = clauses;
|
|
1027
|
+
return createMutation("insert", insertClauses.find((clause) => clause.clauseKind === "returning")?.row ?? {}, (context) => {
|
|
1028
|
+
context.append("INSERT INTO ");
|
|
1029
|
+
context.render(table.reference);
|
|
1030
|
+
if (source.insertKind === "values") {
|
|
1031
|
+
const rows = source.rows;
|
|
1032
|
+
const columns = Object.keys(rows[0] ?? {});
|
|
1033
|
+
if (columns.length === 0) context.append(" DEFAULT VALUES");
|
|
1034
|
+
else {
|
|
1035
|
+
renderTargetColumns(context, table, columns);
|
|
1036
|
+
context.append(" VALUES ");
|
|
1037
|
+
rows.forEach((row, rowIndex) => {
|
|
1038
|
+
if (rowIndex > 0) context.append(", ");
|
|
1039
|
+
context.append("(");
|
|
1040
|
+
columns.forEach((columnName, columnIndex) => {
|
|
1041
|
+
if (columnIndex > 0) context.append(", ");
|
|
1042
|
+
renderInsertValue(context, row[columnName]);
|
|
1043
|
+
});
|
|
1044
|
+
context.append(")");
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
} else if (source.insertKind === "default-values") context.append(" DEFAULT VALUES");
|
|
1048
|
+
else {
|
|
1049
|
+
renderTargetColumns(context, table, source.columns);
|
|
1050
|
+
context.append(" ");
|
|
1051
|
+
context.renderRelation(source.query);
|
|
1052
|
+
}
|
|
1053
|
+
for (const clause of insertClauses) {
|
|
1054
|
+
context.append(" ");
|
|
1055
|
+
context.render(clause);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
function renderTargetColumns(context, table, columns) {
|
|
1060
|
+
context.append(" (");
|
|
1061
|
+
columns.forEach((columnName, index) => {
|
|
1062
|
+
if (index > 0) context.append(", ");
|
|
1063
|
+
context.render(identifier(table.sqlNames[columnName] ?? columnName));
|
|
1064
|
+
});
|
|
1065
|
+
context.append(")");
|
|
1066
|
+
}
|
|
1067
|
+
function renderInsertValue(context, input) {
|
|
1068
|
+
if (isExpression(input)) context.render(input);
|
|
1069
|
+
else context.parameter(input);
|
|
1070
|
+
}
|
|
1071
|
+
function validateInsert(table, source) {
|
|
1072
|
+
const definitions = table.definitions;
|
|
1073
|
+
if (source.insertKind === "values") {
|
|
1074
|
+
const firstColumns = Object.keys(source.rows[0] ?? {});
|
|
1075
|
+
const firstSet = new Set(firstColumns);
|
|
1076
|
+
for (const columnName of firstColumns) {
|
|
1077
|
+
if (!definitions[columnName]) throw queryValidationError({
|
|
1078
|
+
code: "invalid-insert",
|
|
1079
|
+
context: "insert.values.columns",
|
|
1080
|
+
path: ["values", columnName],
|
|
1081
|
+
message: `Unknown insert column "${columnName}"`,
|
|
1082
|
+
hint: "Use only columns declared by the insert table."
|
|
1083
|
+
});
|
|
1084
|
+
if (definitions[columnName].generated) throw queryValidationError({
|
|
1085
|
+
code: "invalid-insert",
|
|
1086
|
+
context: "insert.values.columns",
|
|
1087
|
+
path: ["values", columnName],
|
|
1088
|
+
message: `Generated column "${columnName}" cannot be inserted`,
|
|
1089
|
+
hint: "Omit generated columns from the insert values."
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
for (const row of source.rows) {
|
|
1093
|
+
const columns = Object.keys(row);
|
|
1094
|
+
if (columns.length !== firstColumns.length || columns.some((columnName) => !firstSet.has(columnName))) throw queryValidationError({
|
|
1095
|
+
code: "invalid-insert",
|
|
1096
|
+
context: "insert.values.rows",
|
|
1097
|
+
path: ["values", "rows"],
|
|
1098
|
+
message: "All INSERT values rows must use the same columns",
|
|
1099
|
+
hint: "Use one identical column set for every values row."
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
for (const [columnName, definition] of Object.entries(definitions)) {
|
|
1103
|
+
if (definition.generated || definition.hasDefault) continue;
|
|
1104
|
+
if (!firstSet.has(columnName)) throw queryValidationError({
|
|
1105
|
+
code: "invalid-insert",
|
|
1106
|
+
context: "insert.values.columns",
|
|
1107
|
+
path: ["values", columnName],
|
|
1108
|
+
message: `Required insert column "${columnName}" is missing`,
|
|
1109
|
+
hint: "Provide the column value or define a default for it."
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
} else if (source.insertKind === "default-values") {
|
|
1113
|
+
for (const [columnName, definition] of Object.entries(definitions)) if (!definition.generated && !definition.hasDefault) throw queryValidationError({
|
|
1114
|
+
code: "invalid-insert",
|
|
1115
|
+
context: "insert.default-values",
|
|
1116
|
+
path: ["defaultValues", columnName],
|
|
1117
|
+
message: `DEFAULT VALUES requires column "${columnName}" to have a default`,
|
|
1118
|
+
hint: "Provide values for required columns or define defaults for them."
|
|
1119
|
+
});
|
|
1120
|
+
} else {
|
|
1121
|
+
if (source.columns.length === 0) throw queryValidationError({
|
|
1122
|
+
code: "invalid-insert",
|
|
1123
|
+
context: "insert.select.columns",
|
|
1124
|
+
path: ["insertSelect", "columns"],
|
|
1125
|
+
message: "INSERT ... SELECT requires at least one target column",
|
|
1126
|
+
hint: "List the target columns receiving the SELECT result."
|
|
1127
|
+
});
|
|
1128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1129
|
+
for (const columnName of source.columns) {
|
|
1130
|
+
if (seen.has(columnName)) throw queryValidationError({
|
|
1131
|
+
code: "duplicate-clause",
|
|
1132
|
+
context: "insert.select.columns",
|
|
1133
|
+
path: [
|
|
1134
|
+
"insertSelect",
|
|
1135
|
+
"columns",
|
|
1136
|
+
columnName
|
|
1137
|
+
],
|
|
1138
|
+
message: `Duplicate insert column "${columnName}"`,
|
|
1139
|
+
hint: "List each insert target column once."
|
|
1140
|
+
});
|
|
1141
|
+
seen.add(columnName);
|
|
1142
|
+
if (!definitions[columnName]) throw queryValidationError({
|
|
1143
|
+
code: "invalid-insert",
|
|
1144
|
+
context: "insert.select.columns",
|
|
1145
|
+
path: [
|
|
1146
|
+
"insertSelect",
|
|
1147
|
+
"columns",
|
|
1148
|
+
columnName
|
|
1149
|
+
],
|
|
1150
|
+
message: `Unknown insert column "${columnName}"`,
|
|
1151
|
+
hint: "Use only columns declared by the insert table."
|
|
1152
|
+
});
|
|
1153
|
+
if (definitions[columnName].generated) throw queryValidationError({
|
|
1154
|
+
code: "invalid-insert",
|
|
1155
|
+
context: "insert.select.columns",
|
|
1156
|
+
path: [
|
|
1157
|
+
"insertSelect",
|
|
1158
|
+
"columns",
|
|
1159
|
+
columnName
|
|
1160
|
+
],
|
|
1161
|
+
message: `Generated column "${columnName}" cannot be inserted`,
|
|
1162
|
+
hint: "Omit generated columns from the insert target list."
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
for (const [columnName, definition] of Object.entries(definitions)) {
|
|
1166
|
+
if (definition.generated || definition.hasDefault) continue;
|
|
1167
|
+
if (!seen.has(columnName)) throw queryValidationError({
|
|
1168
|
+
code: "invalid-insert",
|
|
1169
|
+
context: "insert.select.columns",
|
|
1170
|
+
path: [
|
|
1171
|
+
"insertSelect",
|
|
1172
|
+
"columns",
|
|
1173
|
+
columnName
|
|
1174
|
+
],
|
|
1175
|
+
message: `Required insert column "${columnName}" is missing`,
|
|
1176
|
+
hint: "Include every required insert column in the target list."
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
//#endregion
|
|
1182
|
+
//#region src/query/mutation/update.ts
|
|
1183
|
+
function update(table, assignments, ...clauses) {
|
|
1184
|
+
const normalizedClauses = clauses;
|
|
1185
|
+
validateMutationClauses("UPDATE", normalizedClauses);
|
|
1186
|
+
const entries = validateUpdate(table, assignments);
|
|
1187
|
+
const whereClause = normalizedClauses.find((clause) => clause.clauseKind === "where");
|
|
1188
|
+
const returningClause = normalizedClauses.find((clause) => clause.clauseKind === "returning");
|
|
1189
|
+
return createMutation("update", returningClause?.row ?? {}, (context) => {
|
|
1190
|
+
context.append("UPDATE ");
|
|
1191
|
+
context.render(table.reference);
|
|
1192
|
+
context.append(" SET ");
|
|
1193
|
+
entries.forEach(([columnName, value], index) => {
|
|
1194
|
+
if (index > 0) context.append(", ");
|
|
1195
|
+
context.render(identifier(table.sqlNames[columnName] ?? columnName));
|
|
1196
|
+
context.append(" = ");
|
|
1197
|
+
renderAssignmentValue(context, value);
|
|
1198
|
+
});
|
|
1199
|
+
if (whereClause) {
|
|
1200
|
+
context.append(" ");
|
|
1201
|
+
context.render(whereClause);
|
|
1202
|
+
}
|
|
1203
|
+
if (returningClause) {
|
|
1204
|
+
context.append(" ");
|
|
1205
|
+
context.render(returningClause);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
function renderAssignmentValue(context, value) {
|
|
1210
|
+
if (isExpression(value)) context.render(value);
|
|
1211
|
+
else context.parameter(value);
|
|
1212
|
+
}
|
|
1213
|
+
function validateUpdate(table, assignments) {
|
|
1214
|
+
const definitions = table.definitions;
|
|
1215
|
+
const entries = Object.entries(assignments).filter(([, value]) => value !== omit);
|
|
1216
|
+
if (entries.length === 0) throw queryValidationError({
|
|
1217
|
+
code: "invalid-update",
|
|
1218
|
+
context: "update.assignments",
|
|
1219
|
+
path: ["assignments"],
|
|
1220
|
+
message: "UPDATE requires at least one assignment",
|
|
1221
|
+
hint: "Provide at least one writable column, or remove the update."
|
|
1222
|
+
});
|
|
1223
|
+
for (const [columnName] of entries) {
|
|
1224
|
+
const definition = definitions[columnName];
|
|
1225
|
+
if (!definition) throw queryValidationError({
|
|
1226
|
+
code: "invalid-update",
|
|
1227
|
+
context: "update.assignments",
|
|
1228
|
+
path: ["assignments", columnName],
|
|
1229
|
+
message: `Unknown update column "${columnName}"`,
|
|
1230
|
+
hint: "Use a column declared by the update table."
|
|
1231
|
+
});
|
|
1232
|
+
if (definition.generated) throw queryValidationError({
|
|
1233
|
+
code: "invalid-update",
|
|
1234
|
+
context: "update.assignments",
|
|
1235
|
+
path: ["assignments", columnName],
|
|
1236
|
+
message: `Generated column "${columnName}" cannot be updated`,
|
|
1237
|
+
hint: "Remove the generated column from the assignments."
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
return entries;
|
|
1241
|
+
}
|
|
1242
|
+
//#endregion
|
|
1243
|
+
//#region src/query/mutation/delete.ts
|
|
1244
|
+
function deleteFrom(table, ...clauses) {
|
|
1245
|
+
const normalizedClauses = clauses;
|
|
1246
|
+
validateMutationClauses("DELETE", normalizedClauses);
|
|
1247
|
+
const whereClause = normalizedClauses.find((clause) => clause.clauseKind === "where");
|
|
1248
|
+
const returningClause = normalizedClauses.find((clause) => clause.clauseKind === "returning");
|
|
1249
|
+
return createMutation("delete", returningClause?.row ?? {}, (context) => {
|
|
1250
|
+
context.append("DELETE FROM ");
|
|
1251
|
+
context.render(table.reference);
|
|
1252
|
+
if (whereClause) {
|
|
1253
|
+
context.append(" ");
|
|
1254
|
+
context.render(whereClause);
|
|
1255
|
+
}
|
|
1256
|
+
if (returningClause) {
|
|
1257
|
+
context.append(" ");
|
|
1258
|
+
context.render(returningClause);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
//#endregion
|
|
1263
|
+
//#region src/query/mutation/returning.ts
|
|
1264
|
+
function returning(selection) {
|
|
1265
|
+
return Object.freeze({
|
|
1266
|
+
clauseKind: "returning",
|
|
1267
|
+
selection,
|
|
1268
|
+
row: selectionRow(selection),
|
|
1269
|
+
render(context) {
|
|
1270
|
+
context.append("RETURNING ");
|
|
1271
|
+
renderSelection(selection, context);
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
//#endregion
|
|
1276
|
+
export { QueryValidationError, add, alias, all, allowAll, and, asValue, asc, avg, between, bigint, binary, boolean, call, caseWhen, cast, catalogCheck, catalogForeignKey, check, coalesce, column, concat, correlate, count, countDistinct, crossJoin, cte, date, defaultValues, deleteFrom, denseRank, desc, distinct, divide, eq, except, execute, executeRows, exists, explain, externalDefault, externalGeneratedColumn, fetchFirst, fetchNext, foreignKey, from, fullJoin, generatedColumn, groupBy, gt, gte, having, identityColumn, inList, inQuery, index, innerJoin, insertInto, insertSelect, integer, intersect, isDistinctFrom, isNotDistinctFrom, isNotNull, isNull, isTrue, json, jsonBoolean, jsonExists, jsonNumber, jsonPath, jsonText, lateral, leftJoin, like, lower, lt, lte, max, min, modulo, multiply, nativeColumn, nativeStorage, naturalJoin, ne, not, notExists, notIn, notLike, nullable, nullsFirst, nullsLast, numeric, offset, omit, or, order, orderBy, over, portableStorage, primaryKey, qubu, rank, recursiveCte, references, render, returning, rightJoin, rowLock, rowNumber, scalar, schema, schemaCall, select, sql, stream, subtract, sum, table, text, timestamp, union, unionAll, unique, uniqueConstraint, unsafeSchemaSql, update, upper, uuid, value, values, where, withCte };
|