uql-orm 0.80.0 → 0.82.0
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/README.md +3 -3
- package/dist/browser/querier/httpQuerier.d.ts +2 -2
- package/dist/browser/querier/httpQuerier.js +2 -1
- package/dist/browser/type/clientQuerier.d.ts +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/bunSql/bunSql.util.js +2 -1
- package/dist/dialect/abstractSqlDialect.d.ts +15 -5
- package/dist/dialect/abstractSqlDialect.js +109 -40
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/dialect/vectorSqlDialect.d.ts +2 -0
- package/dist/dialect/vectorSqlDialect.js +4 -0
- package/dist/entity/metadata/definition.d.ts +1 -2
- package/dist/entity/metadata/definition.js +37 -39
- package/dist/http/handler.js +5 -4
- package/dist/http/query.d.ts +1 -1
- package/dist/http/query.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migrate/acquireQuerierForMigrations.js +2 -1
- package/dist/migrate/assertCliConfig.js +7 -6
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +26 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +2 -2
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +3 -2
- package/dist/migrate/codegen/indexDecoratorSource.js +5 -23
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +4 -4
- package/dist/migrate/ddl/mssqlTableDdl.js +20 -14
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +2 -2
- package/dist/migrate/ddl/mysqlIndexDdl.js +12 -10
- package/dist/migrate/ddl/pgIndexDdl.d.ts +2 -1
- package/dist/migrate/ddl/pgIndexDdl.js +10 -7
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.d.ts +3 -2
- package/dist/migrate/ddl/tableDdl.js +11 -8
- package/dist/migrate/drift/driftDetector.d.ts +4 -5
- package/dist/migrate/drift/driftDetector.js +21 -21
- package/dist/migrate/generator/definitionToNode.d.ts +1 -1
- package/dist/migrate/generator/definitionToNode.js +9 -20
- package/dist/migrate/generator/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +17 -24
- package/dist/migrate/index.d.ts +2 -1
- package/dist/migrate/index.js +1 -0
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -7
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +13 -19
- package/dist/migrate/introspection/baseSqlIntrospector.js +7 -18
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +5 -4
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +15 -5
- package/dist/migrate/introspection/mysqlIntrospector.js +32 -4
- package/dist/migrate/introspection/postgresIntrospector.d.ts +29 -21
- package/dist/migrate/introspection/postgresIntrospector.js +63 -46
- package/dist/migrate/introspection/sqliteIntrospector.js +11 -9
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.d.ts +5 -0
- package/dist/migrate/migrator.js +35 -45
- package/dist/migrate/schemaChange.d.ts +18 -0
- package/dist/migrate/schemaChange.js +37 -0
- package/dist/migrate/schemaGenerator.d.ts +13 -14
- package/dist/migrate/schemaGenerator.js +88 -181
- package/dist/migrate/triggerSql.d.ts +1 -1
- package/dist/migrate/triggerSql.js +77 -61
- package/dist/mongo/mongoDialect.d.ts +1 -3
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.js +3 -7
- package/dist/querier/abstractQuerier.d.ts +9 -4
- package/dist/querier/abstractQuerier.js +26 -19
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/querier/abstractSqlQuerier.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.js +1 -1
- package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
- package/dist/querier/queryError.d.ts +2 -2
- package/dist/schema/indexDifferences.d.ts +22 -6
- package/dist/schema/indexDifferences.js +23 -8
- package/dist/schema/matchByKey.d.ts +10 -0
- package/dist/schema/matchByKey.js +18 -0
- package/dist/schema/schemaAST.d.ts +6 -2
- package/dist/schema/schemaAST.js +7 -3
- package/dist/schema/schemaASTBuilder.d.ts +2 -0
- package/dist/schema/schemaASTBuilder.js +17 -11
- package/dist/schema/schemaASTDiffer.d.ts +2 -3
- package/dist/schema/schemaASTDiffer.js +15 -36
- package/dist/schema/types.d.ts +14 -15
- package/dist/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +15 -11
- package/dist/type/logger.d.ts +2 -2
- package/dist/type/migration.d.ts +32 -50
- package/dist/type/querier.d.ts +3 -3
- package/dist/type/query.d.ts +3 -13
- package/dist/type/queryAggregate.d.ts +4 -10
- package/dist/type/queryRaw.d.ts +17 -3
- package/dist/type/queryRaw.js +2 -1
- package/dist/type/queryWhere.d.ts +7 -7
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +2 -1
- package/dist/type/vector.js +2 -1
- package/dist/util/ddlExpression.util.d.ts +5 -1
- package/dist/util/ddlExpression.util.js +6 -2
- package/dist/util/dialect.util.d.ts +13 -5
- package/dist/util/dialect.util.js +28 -20
- package/dist/util/field.util.d.ts +4 -4
- package/dist/util/field.util.js +10 -2
- package/dist/util/hook.util.d.ts +1 -1
- package/dist/util/hook.util.js +8 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/object.util.js +3 -2
- package/dist/util/raw.d.ts +6 -7
- package/dist/util/raw.js +10 -12
- package/dist/util/sqlLiteral.js +3 -2
- package/dist/util/triggerWrite.d.ts +15 -0
- package/dist/util/triggerWrite.js +20 -0
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +3 -3
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { stampEvents } from '../util/field.util.js';
|
|
2
2
|
import { definedEntries } from '../util/object.util.js';
|
|
3
|
-
import { raw,
|
|
3
|
+
import { raw, refs, rowRefs } from '../util/raw.js';
|
|
4
4
|
import { ownedName } from '../util/sql.util.js';
|
|
5
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
5
6
|
/**
|
|
6
7
|
* One trigger for `dialect`, named for its table and label and ending in a hash of its own SQL. That hash
|
|
7
8
|
* is the whole of change detection: a trigger is in place exactly when its name is installed, and an
|
|
@@ -20,7 +21,7 @@ export function renderTrigger(dialect, meta, trigger, position) {
|
|
|
20
21
|
*/
|
|
21
22
|
export function dropTrigger(dialect, meta, name) {
|
|
22
23
|
const { scope, body } = dialect.features.triggers;
|
|
23
|
-
const table = dialect.
|
|
24
|
+
const table = dialect.escapedTableName(meta);
|
|
24
25
|
return [
|
|
25
26
|
`DROP TRIGGER IF EXISTS ${triggerId(dialect, meta, name)}${scope === 'table' ? ` ON ${table}` : ''}`,
|
|
26
27
|
...(body === 'function' ? [`DROP FUNCTION IF EXISTS ${schemaObjectId(dialect, meta, name)}()`] : []),
|
|
@@ -45,17 +46,15 @@ function schemaObjectId(dialect, meta, name) {
|
|
|
45
46
|
*/
|
|
46
47
|
function triggerStatements(dialect, meta, trigger, name) {
|
|
47
48
|
const features = dialect.features.triggers;
|
|
48
|
-
|
|
49
|
-
const before =
|
|
50
|
-
const operation = trigger.on.slice(before ? 6 : 5).toUpperCase();
|
|
49
|
+
const [timing, operation] = EVENT_PARTS[trigger.on];
|
|
50
|
+
const before = timing === 'BEFORE';
|
|
51
51
|
const body = triggerBody(dialect, meta, trigger, before);
|
|
52
52
|
const id = triggerId(dialect, meta, name);
|
|
53
|
-
const table = dialect.
|
|
54
|
-
const column = (key) => dialect.escapeId(dialect.columnOf(meta, key));
|
|
53
|
+
const table = dialect.escapedTableName(meta);
|
|
55
54
|
const names = rowNames(dialect);
|
|
56
|
-
const rows = [rowRefs(names.$new), rowRefs(names.$old)];
|
|
57
|
-
const sql = dialect.compileDdl(body(...rows), meta.entity);
|
|
58
|
-
const guard = triggerGuard(dialect, meta, trigger, rows, names
|
|
55
|
+
const rows = [rowRefs(meta.entity, names.$new), rowRefs(meta.entity, names.$old)];
|
|
56
|
+
const sql = dialect.compileDdl(body(...rows), meta.entity, { rows: rowsFrom(dialect, meta, operation) });
|
|
57
|
+
const guard = triggerGuard(dialect, meta, trigger, rows, names);
|
|
59
58
|
const inBody = features.guards !== 'clause';
|
|
60
59
|
const guarded = !guard || !inBody
|
|
61
60
|
? sql
|
|
@@ -66,14 +65,14 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
66
65
|
// which rows are touched, so it runs even when the guard keeps the statements from running.
|
|
67
66
|
const opened = features.preamble ? `${features.preamble}\n${guarded}` : guarded;
|
|
68
67
|
const of = features.guards === 'clause' && operation === 'UPDATE' && trigger.of?.length
|
|
69
|
-
? ` OF ${trigger.of.map(
|
|
68
|
+
? ` OF ${trigger.of.map((key) => dialect.escapedColumnName(meta, key)).join(', ')}`
|
|
70
69
|
: '';
|
|
71
70
|
const each = features.rows === 'set' ? '' : '\nFOR EACH ROW';
|
|
72
71
|
const clause = guard && !inBody ? `\nWHEN (${guard})` : '';
|
|
73
|
-
const
|
|
72
|
+
const when = `${timing} ${operation}${of}`;
|
|
74
73
|
const header = features.layout === 'tableFirst'
|
|
75
|
-
? `CREATE TRIGGER ${id}\nON ${table} ${
|
|
76
|
-
: `CREATE TRIGGER ${id}\n${
|
|
74
|
+
? `CREATE TRIGGER ${id}\nON ${table} ${when}${each}${clause}\nAS`
|
|
75
|
+
: `CREATE TRIGGER ${id}\n${when} ON ${table}${each}${clause}`;
|
|
77
76
|
if (features.body !== 'function') {
|
|
78
77
|
return [`${header}\nBEGIN\n${opened}\nEND`];
|
|
79
78
|
}
|
|
@@ -83,6 +82,15 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
83
82
|
const fn = schemaObjectId(dialect, meta, name);
|
|
84
83
|
return [plpgsqlFunction(fn, `BEGIN\n${opened}\nRETURN ${returned};\nEND`), `${header}\nEXECUTE FUNCTION ${fn}()`];
|
|
85
84
|
}
|
|
85
|
+
/** Each event read apart: `beforeUpdate` is `BEFORE` and `UPDATE`. */
|
|
86
|
+
const EVENT_PARTS = {
|
|
87
|
+
beforeInsert: ['BEFORE', 'INSERT'],
|
|
88
|
+
afterInsert: ['AFTER', 'INSERT'],
|
|
89
|
+
beforeUpdate: ['BEFORE', 'UPDATE'],
|
|
90
|
+
afterUpdate: ['AFTER', 'UPDATE'],
|
|
91
|
+
beforeDelete: ['BEFORE', 'DELETE'],
|
|
92
|
+
afterDelete: ['AFTER', 'DELETE'],
|
|
93
|
+
};
|
|
86
94
|
/**
|
|
87
95
|
* The body for the engine in use, refusing first what the engine cannot render at all. One body serves
|
|
88
96
|
* every engine; in a map, one written for the family serves its forks: CockroachDB runs Postgres's
|
|
@@ -91,25 +99,25 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
91
99
|
function triggerBody(dialect, meta, trigger, before) {
|
|
92
100
|
const features = dialect.features.triggers;
|
|
93
101
|
if (before && !features.before) {
|
|
94
|
-
throw new
|
|
102
|
+
throw new UqlUsageError(`${dialect.dialectName} has no BEFORE trigger, only AFTER and INSTEAD OF, so '${trigger.on}' cannot be ` +
|
|
95
103
|
'rendered there. Use the matching after event, which sees the row already written.');
|
|
96
104
|
}
|
|
97
105
|
if (trigger.where && features.rows === 'set') {
|
|
98
|
-
throw new
|
|
106
|
+
throw new UqlUsageError(`${dialect.dialectName} fires a trigger once per statement, over the rows it touched, so no condition ` +
|
|
99
107
|
`can read one row: '${meta.entity.name}' cannot state a trigger 'where' there. Guard inside the body ` +
|
|
100
108
|
'instead, where `inserted` and `deleted` can be read as tables.');
|
|
101
109
|
}
|
|
102
110
|
const { run } = trigger;
|
|
103
111
|
const body = typeof run === 'function' ? run : (run[dialect.dialectName] ?? run[dialect.dialectFamily]);
|
|
104
112
|
if (!body) {
|
|
105
|
-
throw new
|
|
113
|
+
throw new UqlUsageError(`'${meta.entity.name}' has a trigger with no body for ${dialect.dialectName}, the engine in use. ` +
|
|
106
114
|
'Write one for it, or one body for every engine.');
|
|
107
115
|
}
|
|
108
116
|
return body;
|
|
109
117
|
}
|
|
110
118
|
/** The one condition both guards reduce to: any watched column that moved, and whatever `where` asks. */
|
|
111
|
-
function triggerGuard(dialect, meta, trigger, rows, names
|
|
112
|
-
const moved = movedColumns(dialect, meta, trigger.of ?? [], names
|
|
119
|
+
function triggerGuard(dialect, meta, trigger, rows, names) {
|
|
120
|
+
const moved = movedColumns(dialect, meta, trigger.of ?? [], names);
|
|
113
121
|
return [
|
|
114
122
|
...(moved ? [moved] : []),
|
|
115
123
|
...(trigger.where ? condition(dialect, meta, trigger.where, rows, names, Boolean(moved)) : []),
|
|
@@ -137,16 +145,10 @@ function condition(dialect, meta, where, rows, names, operand) {
|
|
|
137
145
|
return [operand ? `(${sql})` : sql];
|
|
138
146
|
}
|
|
139
147
|
const predicates = definedEntries(where);
|
|
140
|
-
return predicates.map(([row, predicate]) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
clause: false,
|
|
145
|
-
operand: operand || predicates.length > 1,
|
|
146
|
-
escapedPrefix,
|
|
147
|
-
});
|
|
148
|
-
return ctx.sql;
|
|
149
|
-
});
|
|
148
|
+
return predicates.map(([row, predicate]) => dialect.compileDdl(predicate, meta.entity, {
|
|
149
|
+
escapedPrefix: `${names[row]}.`,
|
|
150
|
+
operand: operand || predicates.length > 1,
|
|
151
|
+
}));
|
|
150
152
|
}
|
|
151
153
|
/**
|
|
152
154
|
* The triggers a stamp needs: one per event it names, each assigning the field's expression to its
|
|
@@ -157,8 +159,8 @@ export function stampTriggers(dialect, meta) {
|
|
|
157
159
|
const features = dialect.features.triggers;
|
|
158
160
|
// Restating the row has to wait for it to be there, so those engines stamp after the write.
|
|
159
161
|
const after = !features.assignsRow;
|
|
160
|
-
const
|
|
161
|
-
return definedEntries(
|
|
162
|
+
const fields = meta.fields;
|
|
163
|
+
return definedEntries(fields).flatMap(([key, field]) => {
|
|
162
164
|
const events = stampEvents(field);
|
|
163
165
|
const { computed } = field;
|
|
164
166
|
if (!events?.length || !computed) {
|
|
@@ -169,7 +171,7 @@ export function stampTriggers(dialect, meta) {
|
|
|
169
171
|
// The event is part of the name: a stamp on both writes installs two triggers, and one identifier
|
|
170
172
|
// between them would have the second replace the first rather than sit beside it.
|
|
171
173
|
name: `${key}_${event}`,
|
|
172
|
-
run: () => stampBody(dialect, meta, key, computed,
|
|
174
|
+
run: (newRow) => stampBody(dialect, meta, key, computed, newRow),
|
|
173
175
|
}));
|
|
174
176
|
});
|
|
175
177
|
}
|
|
@@ -178,26 +180,25 @@ const STAMP_EVENTS = {
|
|
|
178
180
|
before: { insert: 'beforeInsert', update: 'beforeUpdate' },
|
|
179
181
|
after: { insert: 'afterInsert', update: 'afterUpdate' },
|
|
180
182
|
};
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
/**
|
|
184
|
+
* The one statement a stamp runs, in whichever of the two shapes the engine leaves open: an assignment
|
|
185
|
+
* to the incoming row, or where it may not be written, the row restated after the write, as any write in
|
|
186
|
+
* a trigger's body is, keyed on its whole key and only where the stamp still differs: that `UPDATE` fires
|
|
187
|
+
* the trigger again, and with recursive triggers on it then finds nothing left to change.
|
|
188
|
+
*/
|
|
189
|
+
function stampBody(dialect, meta, key, value, newRow) {
|
|
190
|
+
const { entity } = meta;
|
|
191
|
+
if (dialect.features.triggers.assignsRow) {
|
|
192
|
+
const target = newRow[key];
|
|
193
|
+
return dialect.features.triggers.body === 'function' ? raw `${target} := ${value};` : raw `SET ${target} = ${value};`;
|
|
188
194
|
}
|
|
189
|
-
const table =
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const from = features.rows === 'set' ? ` FROM ${newName}` : '';
|
|
197
|
-
// Only where the stamp still differs: this `UPDATE` fires the trigger again, and with recursive triggers
|
|
198
|
-
// on, the restatement it runs then finds nothing left to change instead of recursing without end.
|
|
199
|
-
const differs = dialect.neExpr(`${table}.${column}`, dialect.compileDdl(value, meta.entity));
|
|
200
|
-
return raw `UPDATE ${text(table)} SET ${text(column)} = ${value}${text(from)} WHERE ${keyed} AND ${text(differs)};`;
|
|
195
|
+
const table = refs(entity);
|
|
196
|
+
const keyed = meta.ids.map((id) => raw `${table[id]} = ${newRow[id]}`);
|
|
197
|
+
// Read through the write's own qualifier, as `keyed` is: `inserted` holds the same column name.
|
|
198
|
+
const stamped = dialect.compileDdl(value, entity);
|
|
199
|
+
const differs = raw(({ ctx, escapedPrefix }) => ctx.append(dialect.neExpr(`${escapedPrefix}${dialect.escapedColumnName(meta, key)}`, stamped)));
|
|
200
|
+
const where = { $and: [...keyed, differs] };
|
|
201
|
+
return raw(({ ctx, rows }) => dialect.triggerWrite(ctx, { kind: 'update', entity, set: { [key]: value }, where }, rows));
|
|
201
202
|
}
|
|
202
203
|
/** A dollar quote the body does not contain, so no `$$` in it - a literal, a comment - ends the function early. */
|
|
203
204
|
function dollarQuote(body) {
|
|
@@ -207,23 +208,38 @@ function dollarQuote(body) {
|
|
|
207
208
|
}
|
|
208
209
|
return tag;
|
|
209
210
|
}
|
|
210
|
-
/** SQL already written out, for the identifiers a statement splices rather than binds. */
|
|
211
|
-
function text(sql) {
|
|
212
|
-
return raw((opts) => opts.ctx.append(sql));
|
|
213
|
-
}
|
|
214
211
|
/**
|
|
215
212
|
* Whether any watched column moved, null-safely, or `undefined` where none is watched: the two records
|
|
216
213
|
* compared on a row-based engine, and on a set-based one the same question over a join of its two tables.
|
|
217
214
|
*/
|
|
218
|
-
function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }
|
|
215
|
+
function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }) {
|
|
219
216
|
if (!of.length) {
|
|
220
217
|
return undefined;
|
|
221
218
|
}
|
|
222
|
-
const differs = of.map((key) =>
|
|
219
|
+
const differs = of.map((key) => {
|
|
220
|
+
const column = dialect.escapedColumnName(meta, key);
|
|
221
|
+
return dialect.neExpr(`${oldName}.${column}`, `${newName}.${column}`);
|
|
222
|
+
});
|
|
223
223
|
const moved = differs.length > 1 ? `(${differs.join(' OR ')})` : differs.join('');
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
const source = rowsFrom(dialect, meta, 'UPDATE');
|
|
225
|
+
return source ? `EXISTS (SELECT 1 ${source} WHERE ${moved})` : moved;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Where a set-based engine's body reads the rows it fires for, as the `FROM` a statement names them in:
|
|
229
|
+
* `inserted` on an insert, `deleted` on a delete, and on an update both, joined on the whole key. None on a
|
|
230
|
+
* row-based engine, whose body reads `NEW` and `OLD` bare.
|
|
231
|
+
*/
|
|
232
|
+
function rowsFrom(dialect, meta, operation) {
|
|
233
|
+
if (dialect.features.triggers.rows !== 'set') {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const { $new, $old } = rowNames(dialect);
|
|
237
|
+
if (operation !== 'UPDATE') {
|
|
238
|
+
return `FROM ${operation === 'INSERT' ? $new : $old}`;
|
|
226
239
|
}
|
|
227
|
-
const keyed = meta.ids.map((id) =>
|
|
228
|
-
|
|
240
|
+
const keyed = meta.ids.map((id) => {
|
|
241
|
+
const column = dialect.escapedColumnName(meta, id);
|
|
242
|
+
return `${$new}.${column} = ${$old}.${column}`;
|
|
243
|
+
});
|
|
244
|
+
return `FROM ${$new} JOIN ${$old} ON ${keyed.join(' AND ')}`;
|
|
229
245
|
}
|
|
@@ -90,9 +90,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
90
90
|
* apply to JSON paths.
|
|
91
91
|
*/
|
|
92
92
|
private assertKnownPathRoot;
|
|
93
|
-
/**
|
|
94
|
-
* Transform UQL operators to MongoDB operators.
|
|
95
|
-
*/
|
|
93
|
+
/** Transform UQL operators to MongoDB operators, refusing as `refusal` a key that is none. */
|
|
96
94
|
private transformOperators;
|
|
97
95
|
/**
|
|
98
96
|
* What a value holding `value` matches, as the SQL engines read it: an operator map tests it, an array
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
3
3
|
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, SUM_COUNT_ALIAS, nullsSortField, sortAggregateField, TEXT_SCORE_ALIAS, } from '../dialect/aliases.js';
|
|
4
|
-
import { GROUP_OPS, groupClauses, isGroupOp } from '../dialect/operators.js';
|
|
4
|
+
import { betweenBounds, GROUP_OPS, groupClauses, isGroupOp, whereOperators } from '../dialect/operators.js';
|
|
5
5
|
import { aggregateColumnField, groupPathField, resolveGroupJoins, relationSortTerms, resolveQueryJoins, resolveSortableJoin, } from '../dialect/queryJoins.js';
|
|
6
6
|
import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
7
7
|
import { COUNT_RESULT_KEY } from '../type/query.js';
|
|
@@ -334,12 +334,10 @@ export class MongoDialect extends AbstractDialect {
|
|
|
334
334
|
}
|
|
335
335
|
throw new UqlUsageError(`path ${key} does not exist in ${entityName(meta)}`);
|
|
336
336
|
}
|
|
337
|
-
/**
|
|
338
|
-
|
|
339
|
-
*/
|
|
340
|
-
transformOperators(ops) {
|
|
337
|
+
/** Transform UQL operators to MongoDB operators, refusing as `refusal` a key that is none. */
|
|
338
|
+
transformOperators(ops, refusal = 'unknown operator') {
|
|
341
339
|
const result = {};
|
|
342
|
-
for (const [op, val] of
|
|
340
|
+
for (const [op, val] of whereOperators(ops, refusal)) {
|
|
343
341
|
// `$elemMatch`'s value is itself a condition, so the operators inside it need the same
|
|
344
342
|
// mapping - passing it through raw sends UQL-only operators (`$startsWith`, `$between`, ...)
|
|
345
343
|
// straight to the server, which rejects them as unknown.
|
|
@@ -349,7 +347,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
349
347
|
}
|
|
350
348
|
// `$not` wraps a condition too, so a uql-only operator inside it (`$isNull`, `$startsWith`) is mapped.
|
|
351
349
|
if (op === '$not' && isOperatorObject(val)) {
|
|
352
|
-
result[op] = this.transformOperators(val);
|
|
350
|
+
result[op] = this.transformOperators(val, refusal);
|
|
353
351
|
continue;
|
|
354
352
|
}
|
|
355
353
|
// An object or an array is matched by what it holds, as the SQL engines read it, where native `$all`
|
|
@@ -374,7 +372,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
374
372
|
// Structural transforms
|
|
375
373
|
switch (op) {
|
|
376
374
|
case '$between': {
|
|
377
|
-
const [min, max] = val;
|
|
375
|
+
const [min, max] = betweenBounds(val);
|
|
378
376
|
result['$gte'] = min;
|
|
379
377
|
result['$lte'] = max;
|
|
380
378
|
break;
|
|
@@ -390,9 +388,6 @@ export class MongoDialect extends AbstractDialect {
|
|
|
390
388
|
// reads: converting a distance would mean guessing the metric, so this refuses.
|
|
391
389
|
throw new UqlUsageError('$near is not supported on MongoDB: Atlas scores by index-defined similarity, not distance. ' +
|
|
392
390
|
"Project the score with $sort's $project and filter on it instead.");
|
|
393
|
-
default:
|
|
394
|
-
result[op] = val;
|
|
395
|
-
break;
|
|
396
391
|
}
|
|
397
392
|
}
|
|
398
393
|
return result;
|
|
@@ -1072,7 +1067,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1072
1067
|
if (columnFamily(field.type) === 'string') {
|
|
1073
1068
|
return;
|
|
1074
1069
|
}
|
|
1075
|
-
throw new
|
|
1070
|
+
throw new UqlUsageError(`'${entityName(meta)}.${meta.ids[0]}' is declared '${declaredTypeName(field.type)}' and left to the ` +
|
|
1076
1071
|
'database, which MongoDB cannot do: the only key it generates is an ObjectId, read back as a string. ' +
|
|
1077
1072
|
"Declare the key as a string, or give it an 'onInsert' generator.");
|
|
1078
1073
|
}
|
|
@@ -1282,7 +1277,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1282
1277
|
// on identical input. Keeping only numbers and objects dropped a string or boolean without a
|
|
1283
1278
|
// word, handing back every group instead of the filtered ones.
|
|
1284
1279
|
if (isOperatorMap(condition)) {
|
|
1285
|
-
filter[alias] = this.transformOperators(condition);
|
|
1280
|
+
filter[alias] = this.transformOperators(condition, 'unsupported HAVING operator');
|
|
1286
1281
|
}
|
|
1287
1282
|
else {
|
|
1288
1283
|
filter[alias] = Array.isArray(condition) ? { $in: condition } : condition;
|
|
@@ -1368,6 +1363,6 @@ function sortNulls(value) {
|
|
|
1368
1363
|
function assertReadable(meta, key) {
|
|
1369
1364
|
const field = meta.fields[key];
|
|
1370
1365
|
if (field?.computed && !aggregateOf(field)) {
|
|
1371
|
-
throw new
|
|
1366
|
+
throw new UqlUsageError(`cannot read '${meta.entity.name}.${key}' on MongoDB: a 'computed' field writing SQL is not something a document engine evaluates`);
|
|
1372
1367
|
}
|
|
1373
1368
|
}
|
|
@@ -2,7 +2,7 @@ import { AGGREGATE_VALUE_ALIAS } from '../dialect/aliases.js';
|
|
|
2
2
|
import { hasRequiredJoin } from '../dialect/queryJoins.js';
|
|
3
3
|
import { fieldOf, getMeta, namesKey, soleIdOf } from '../entity/index.js';
|
|
4
4
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
5
|
-
import { clone, getKeys, getSoftDeleteValue, hasKeys, hasTriggers, populatesRelations, textSortOf, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
|
+
import { clone, getKeys, getSoftDeleteValue, hasKeys, hasTriggers, populatesRelations, textSortOf, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, whereEach, } from '../util/index.js';
|
|
6
6
|
import { UqlUsageError } from '../util/uqlError.js';
|
|
7
7
|
/**
|
|
8
8
|
* `$limit: 0` asks for no rows, the way it does on every SQL dialect - but MongoDB reads `limit(0)`
|
|
@@ -223,11 +223,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
223
223
|
return update;
|
|
224
224
|
}
|
|
225
225
|
buildConflictFilter(entity, conflictPaths, item) {
|
|
226
|
-
|
|
227
|
-
acc[key] = item[key];
|
|
228
|
-
return acc;
|
|
229
|
-
}, {});
|
|
230
|
-
return this.dialect.where(entity, where);
|
|
226
|
+
return this.dialect.where(entity, whereEach(getKeys(conflictPaths), (key) => item[key]));
|
|
231
227
|
}
|
|
232
228
|
async internalUpsertOne(entity, conflictPaths, payload) {
|
|
233
229
|
refuseTriggers(entity);
|
|
@@ -314,7 +310,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
314
310
|
/** Every read and write goes through here, which makes it where a released querier is caught. */
|
|
315
311
|
collection(entity) {
|
|
316
312
|
if (this.released) {
|
|
317
|
-
throw new
|
|
313
|
+
throw new UqlUsageError('querier already released');
|
|
318
314
|
}
|
|
319
315
|
const { name } = getMeta(entity);
|
|
320
316
|
return this.db.collection(name);
|
|
@@ -87,7 +87,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
87
87
|
* The `onInsert` values are filled here, before the write, so the after hooks and the ids read the
|
|
88
88
|
* same rows the statement wrote.
|
|
89
89
|
*/
|
|
90
|
-
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
90
|
+
insertMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
91
91
|
/** Writes `rows`, and onto each one the key the database generated for it, where it can tell. */
|
|
92
92
|
protected abstract internalInsertMany<E extends object>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
|
|
93
93
|
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -118,9 +118,14 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
118
118
|
protected abstract internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
119
119
|
restoreOneById<E extends object>(entity: Type<E>, id: EntityId<E>): Promise<number>;
|
|
120
120
|
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
121
|
+
/**
|
|
122
|
+
* An update the library writes of its own, hooked as a caller's is but carrying no version: a restore's
|
|
123
|
+
* cleared stamp, or a row pointed at the relation just inserted for it.
|
|
124
|
+
*/
|
|
125
|
+
private unversionedUpdate;
|
|
121
126
|
/** Fires `beforeUpsert`/`afterUpsert`: which branch a row takes is the database's to decide, so neither the insert's nor the update's pair fits. */
|
|
122
127
|
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
123
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
128
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: readonly EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
124
129
|
protected abstract internalUpsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
125
130
|
protected abstract internalUpsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
126
131
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -137,7 +142,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
137
142
|
* upserts on that key, so a stale id is written rather than silently missed, and an unnamed one
|
|
138
143
|
* inserts. A composite is always named. The hooks follow the statement: a named row fires the upsert pair.
|
|
139
144
|
*/
|
|
140
|
-
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
145
|
+
saveMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
141
146
|
/** Writes each inserted row's relations, one set of statements per relation whatever the number of rows. */
|
|
142
147
|
protected insertRelations<E extends object>(entity: Type<E>, rows: E[]): Promise<void>;
|
|
143
148
|
/** `EntityId` because a settled composite row is an object, which {@link childrenOf} reads each foreign key column out of. */
|
|
@@ -182,7 +187,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
182
187
|
/** Runs `task` after everything already queued, one at a time. Not re-entrant: never nest `serialize` calls. */
|
|
183
188
|
protected serialize<T>(task: () => Promise<T>): Promise<T>;
|
|
184
189
|
/** Runs `task`, logs `query` with its duration, and tags a failure with it: a method, since a decorator would lose the generics. */
|
|
185
|
-
protected timed<T>(query: string, values: unknown[] | undefined, task: () => Promise<T>): Promise<T>;
|
|
190
|
+
protected timed<T>(query: string, values: readonly unknown[] | undefined, task: () => Promise<T>): Promise<T>;
|
|
186
191
|
abstract beginTransaction(opts?: TransactionOptions): Promise<void>;
|
|
187
192
|
/** Strict: this is the check that catches a forgotten `beginTransaction`. */
|
|
188
193
|
abstract commitTransaction(): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertSoleId, getMeta, idOf, namesKey, relationOf } from '../entity/index.js';
|
|
2
|
+
import { namesRows } from '../dialect/operators.js';
|
|
2
3
|
import { parseQueryLock } from '../type/index.js';
|
|
3
|
-
import { cascadesOnDelete, childrenOf, clone, entityName, fillOnFields, filterFieldKeys, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery,
|
|
4
|
+
import { cascadesOnDelete, childrenOf, clone, entityName, fillOnFields, filterFieldKeys, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, keySet, isPagedQuery, isScalarId, LoggerWrapper, parentJoins, queryLoggerFor, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereAnyOf, whereEach, whereIds, whereWith, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
5
|
import { UqlOptimisticLockError, UqlUsageError } from '../util/uqlError.js';
|
|
5
6
|
import { enrichError } from './queryError.js';
|
|
6
7
|
/**
|
|
@@ -35,7 +36,7 @@ function soleParentColumn(relOpts) {
|
|
|
35
36
|
* every table look narrowed, which is the case this exists to catch.
|
|
36
37
|
*/
|
|
37
38
|
function assertNamesRows(entity, method, q, opts) {
|
|
38
|
-
if (opts?.unfiltered ||
|
|
39
|
+
if (opts?.unfiltered || namesRows(q?.$where) || q?.$limit !== undefined) {
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
throw new UqlUsageError(`'${method}' over '${entity.name}' names no rows, so it would address every one: pass '{ unfiltered: true }' to mean it`);
|
|
@@ -53,7 +54,7 @@ function lockVersion(meta, key, q, row) {
|
|
|
53
54
|
const next = typeof expected === 'bigint' ? expected + 1n : expected + 1;
|
|
54
55
|
// Spread, as every other added predicate here is: one flat `AND`, and a caller already filtering on
|
|
55
56
|
// the version contradicts itself into matching nothing, which is what they asked for.
|
|
56
|
-
return { expected, next, q: { ...q, $where:
|
|
57
|
+
return { expected, next, q: { ...q, $where: whereWith(key, expected, q.$where) } };
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
59
60
|
* Refuses a write that cannot carry the lock, rather than writing over whatever the row holds now.
|
|
@@ -72,8 +73,8 @@ function assertUnversioned(meta, what) {
|
|
|
72
73
|
* an `UPDATE` - reads the ids and writes them separately, putting the race back in the gap between.
|
|
73
74
|
*/
|
|
74
75
|
function assertLockableUpdate(meta, q, settles) {
|
|
75
|
-
const where = q.$where;
|
|
76
|
-
const namesOneRow = meta.ids.every((key) => where
|
|
76
|
+
const where = { ...q.$where };
|
|
77
|
+
const namesOneRow = meta.ids.every((key) => where[key] !== undefined && isScalarId(where[key]));
|
|
77
78
|
if (!namesOneRow || settles) {
|
|
78
79
|
throw new UqlUsageError(`cannot update '${entityName(meta)}' this way: a versioned row is matched and written in one statement, so it is named by its ${meta.ids.map((id) => `'${id}'`).join(', ')}, takes no '$sort', '$limit' or '$skip', writes no relation, and filters by none`);
|
|
79
80
|
}
|
|
@@ -290,9 +291,7 @@ export class AbstractQuerier {
|
|
|
290
291
|
*/
|
|
291
292
|
async throwStaleVersion(entity, key, q, expected, opts) {
|
|
292
293
|
const meta = getMeta(entity);
|
|
293
|
-
const
|
|
294
|
-
const byId = Object.fromEntries(meta.ids.map((id) => [id, where[id]]));
|
|
295
|
-
const row = await this.findOne(entity, { $select: { [key]: true }, $where: byId }, opts);
|
|
294
|
+
const row = await this.findOne(entity, { $select: keySet([key]), $where: whereIds(meta, idOf(meta, { ...q.$where })) }, opts);
|
|
296
295
|
const actual = row?.[key];
|
|
297
296
|
const message = actual === undefined
|
|
298
297
|
? `no row of '${entityName(meta)}' has that id any more: it is gone`
|
|
@@ -329,10 +328,17 @@ export class AbstractQuerier {
|
|
|
329
328
|
if (!meta.softDelete) {
|
|
330
329
|
throw new UqlUsageError(`'${entity.name}' has not enabled 'softDelete'`);
|
|
331
330
|
}
|
|
332
|
-
const $where =
|
|
331
|
+
const $where = whereWith(meta.softDelete, { $ne: null }, q.$where);
|
|
333
332
|
// No version: a restore only undoes the stamp a delete left, which takes none either, and two of
|
|
334
333
|
// them racing agree on the result anyway. A lock is for content, and a restore writes none.
|
|
335
|
-
return this.
|
|
334
|
+
return this.unversionedUpdate(entity, { ...q, $where }, { [meta.softDelete]: null }, { filters: { softDelete: false } });
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* An update the library writes of its own, hooked as a caller's is but carrying no version: a restore's
|
|
338
|
+
* cleared stamp, or a row pointed at the relation just inserted for it.
|
|
339
|
+
*/
|
|
340
|
+
unversionedUpdate(entity, q, payload, opts) {
|
|
341
|
+
return this.hooked(entity, 'Update', [payload], ([row]) => this.updateRows(entity, q, row, opts, undefined));
|
|
336
342
|
}
|
|
337
343
|
/** Fires `beforeUpsert`/`afterUpsert`: which branch a row takes is the database's to decide, so neither the insert's nor the update's pair fits. */
|
|
338
344
|
async upsertOne(entity, conflictPaths, payload) {
|
|
@@ -426,7 +432,7 @@ export class AbstractQuerier {
|
|
|
426
432
|
}
|
|
427
433
|
}
|
|
428
434
|
if (toUpsert.length) {
|
|
429
|
-
const conflictPaths =
|
|
435
|
+
const conflictPaths = keySet(meta.ids);
|
|
430
436
|
const { ids: upserted } = await this.upsertMany(entity, conflictPaths, toUpsert.map((index) => payload[index]));
|
|
431
437
|
for (let position = 0; position < toUpsert.length; position++) {
|
|
432
438
|
ids[toUpsert[position]] = upserted[position];
|
|
@@ -492,7 +498,7 @@ export class AbstractQuerier {
|
|
|
492
498
|
const savedIds = await this.saveMany(relEntity, children.map(({ row }) => row));
|
|
493
499
|
// A link needs the target's id, which a MySQL batch mixing supplied and generated keys cannot report.
|
|
494
500
|
if (savedIds.includes(undefined)) {
|
|
495
|
-
throw new
|
|
501
|
+
throw new UqlUsageError(`'${relEntity.name}' rows saved through '${holder.name}' reported no id, so they cannot be linked. ` +
|
|
496
502
|
'Insert them with their own ids, or save the relation in its own statement.');
|
|
497
503
|
}
|
|
498
504
|
const [targetColumn] = targetKeyColumns(relOpts, 1);
|
|
@@ -506,7 +512,8 @@ export class AbstractQuerier {
|
|
|
506
512
|
const pointing = writes.filter(({ value }) => value);
|
|
507
513
|
const referenceIds = await this.insertMany(relEntity, pointing.map(({ value }) => value));
|
|
508
514
|
for (const [index, { id }] of pointing.entries()) {
|
|
509
|
-
|
|
515
|
+
assertIdValue(entity, id);
|
|
516
|
+
await this.unversionedUpdate(entity, { $where: whereIds(getMeta(entity), id) }, { [localColumn]: referenceIds[index] });
|
|
510
517
|
}
|
|
511
518
|
}
|
|
512
519
|
/**
|
|
@@ -573,8 +580,8 @@ export class AbstractQuerier {
|
|
|
573
580
|
const [idKey] = meta.ids;
|
|
574
581
|
const keys = getKeys(conflictPaths);
|
|
575
582
|
const q = {
|
|
576
|
-
$select:
|
|
577
|
-
$where:
|
|
583
|
+
$select: keySet([idKey, ...keys]),
|
|
584
|
+
$where: whereAnyOf(rows.map((row) => whereEach(keys, (key) => row[key]))),
|
|
578
585
|
};
|
|
579
586
|
const found = await this.internalFindMany(entity, q, { filters: withoutSoftDeleteFilter(undefined) });
|
|
580
587
|
const byConflict = new Map();
|
|
@@ -590,12 +597,12 @@ export class AbstractQuerier {
|
|
|
590
597
|
* the write filled in - a generated key, an `onInsert` value - without it landing on the caller's.
|
|
591
598
|
*/
|
|
592
599
|
async hooked(entity, event, payloads, write) {
|
|
593
|
-
// The one place a
|
|
594
|
-
//
|
|
595
|
-
//
|
|
600
|
+
// The one place a write becomes the row the rest of the library handles. They are the same object:
|
|
601
|
+
// a write is the entity's data without the keys the database fills, which TypeScript cannot relate
|
|
602
|
+
// across an entity it has not resolved.
|
|
596
603
|
const asRows = payloads;
|
|
597
604
|
await this.emitHook(entity, `before${event}`, asRows);
|
|
598
|
-
const rows = clone(
|
|
605
|
+
const rows = asRows.map((row) => clone(row));
|
|
599
606
|
const result = await write(rows);
|
|
600
607
|
await this.emitHook(entity, `after${event}`, rows);
|
|
601
608
|
return result;
|
|
@@ -38,13 +38,13 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
|
|
|
38
38
|
aggregate<E extends object, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
39
39
|
estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
|
|
40
40
|
insertOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
41
|
-
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
41
|
+
insertMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
42
42
|
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
43
43
|
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
44
44
|
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
45
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
45
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: readonly EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
46
46
|
saveOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
47
|
-
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
47
|
+
saveMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
48
48
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|
|
49
49
|
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
50
50
|
restoreOneById<E extends object>(entity: Type<E>, id: EntityId<E>): Promise<number>;
|
|
@@ -32,8 +32,8 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
32
32
|
* caught for every SQL backend.
|
|
33
33
|
*/
|
|
34
34
|
protected lazyConnect(): Promise<void>;
|
|
35
|
-
all<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
36
|
-
run(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
|
|
35
|
+
all<T>(query: string, values?: readonly unknown[]): Promise<T[]>;
|
|
36
|
+
run(query: string, values?: readonly unknown[]): Promise<QueryUpdateResult>;
|
|
37
37
|
/** The rows of a statement the dialect builds. */
|
|
38
38
|
private query;
|
|
39
39
|
/** Runs a statement the dialect builds. */
|
|
@@ -91,7 +91,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
91
91
|
*/
|
|
92
92
|
async lazyConnect() {
|
|
93
93
|
if (this.released) {
|
|
94
|
-
throw new
|
|
94
|
+
throw new UqlUsageError('querier already released');
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
async all(query, values) {
|
|
@@ -6,6 +6,6 @@ import { AbstractQuerierPool } from './abstractQuerierPool.js';
|
|
|
6
6
|
* the connection-per-call semantics.
|
|
7
7
|
*/
|
|
8
8
|
export declare abstract class AbstractSqlQuerierPool<Q extends SqlQuerier, D extends AbstractSqlDialect> extends AbstractQuerierPool<Q, D> implements SqlQuerierPool<Q, D> {
|
|
9
|
-
all<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
10
|
-
run(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
|
|
9
|
+
all<T>(query: string, values?: readonly unknown[]): Promise<T[]>;
|
|
10
|
+
run(query: string, values?: readonly unknown[]): Promise<QueryUpdateResult>;
|
|
11
11
|
}
|
|
@@ -6,7 +6,7 @@ import { type QueryErrorKind } from '../util/uqlError.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export interface QueryError extends Error {
|
|
8
8
|
query?: string;
|
|
9
|
-
values?: unknown[];
|
|
9
|
+
values?: readonly unknown[];
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Names what `err` ran into on any engine, or `undefined` for anything else. Pure: the error is only
|
|
@@ -18,4 +18,4 @@ export declare function queryErrorKind(err: unknown): QueryErrorKind | undefined
|
|
|
18
18
|
* control flow it is. `values` are attached only when `logger?.willLogValues()`: they already surface
|
|
19
19
|
* in the logs then, so this opens no new leak.
|
|
20
20
|
*/
|
|
21
|
-
export declare function enrichError(err: unknown, logger: LoggerWrapper | undefined, query: string, values?: unknown[]): unknown;
|
|
21
|
+
export declare function enrichError(err: unknown, logger: LoggerWrapper | undefined, query: string, values?: readonly unknown[]): unknown;
|