turbine-orm 0.40.1 → 0.41.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 +22 -4
- package/dist/cjs/cli/config.js +3 -0
- package/dist/cjs/cli/index.js +179 -0
- package/dist/cjs/cli/prisma-report.js +216 -0
- package/dist/cjs/cli/prisma-resolve.js +335 -0
- package/dist/cjs/cli/prisma-schema.js +484 -0
- package/dist/cjs/client.js +1 -0
- package/dist/cjs/generate.js +279 -22
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/introspect.js +203 -26
- package/dist/cjs/mssql.js +9 -10
- package/dist/cjs/mysql.js +3 -9
- package/dist/cjs/powdb-introspect.js +5 -10
- package/dist/cjs/powql.js +13 -0
- package/dist/cjs/prisma-compat.js +1147 -0
- package/dist/cjs/query/aggregates.js +67 -7
- package/dist/cjs/query/builder.js +388 -17
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/relations.js +7 -5
- package/dist/cjs/query/warn-registry.js +98 -0
- package/dist/cjs/query/writes.js +13 -5
- package/dist/cjs/schema.js +47 -0
- package/dist/cjs/sqlite.js +4 -9
- package/dist/cli/config.d.ts +26 -0
- package/dist/cli/config.js +3 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +180 -1
- package/dist/cli/prisma-report.d.ts +19 -0
- package/dist/cli/prisma-report.js +211 -0
- package/dist/cli/prisma-resolve.d.ts +87 -0
- package/dist/cli/prisma-resolve.js +330 -0
- package/dist/cli/prisma-schema.d.ts +116 -0
- package/dist/cli/prisma-schema.js +479 -0
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +18 -2
- package/dist/client.js +1 -0
- package/dist/generate.d.ts +80 -1
- package/dist/generate.js +277 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +92 -2
- package/dist/introspect.js +198 -26
- package/dist/mssql.js +10 -11
- package/dist/mysql.js +4 -10
- package/dist/powdb-introspect.js +5 -10
- package/dist/powql.js +13 -0
- package/dist/prisma-compat.d.ts +281 -0
- package/dist/prisma-compat.js +1143 -0
- package/dist/query/aggregates.js +67 -7
- package/dist/query/builder.d.ts +77 -4
- package/dist/query/builder.js +390 -19
- package/dist/query/compound-unique.d.ts +49 -0
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +18 -0
- package/dist/query/relations.js +7 -5
- package/dist/query/types.d.ts +70 -9
- package/dist/query/warn-registry.d.ts +57 -0
- package/dist/query/warn-registry.js +92 -0
- package/dist/query/writes.js +13 -5
- package/dist/schema.d.ts +75 -0
- package/dist/schema.js +46 -0
- package/dist/sqlite.js +5 -10
- package/package.json +6 -1
package/dist/cjs/introspect.js
CHANGED
|
@@ -12,12 +12,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DEFAULT_EXCLUDED_TABLES = void 0;
|
|
15
16
|
exports.pgConfActionToReferential = pgConfActionToReferential;
|
|
17
|
+
exports.applyTableFilters = applyTableFilters;
|
|
18
|
+
exports.defaultExcludedTablesPresent = defaultExcludedTablesPresent;
|
|
16
19
|
exports.introspect = introspect;
|
|
17
20
|
exports.introspectPostgresCatalog = introspectPostgresCatalog;
|
|
18
21
|
exports.stripCheckWrapper = stripCheckWrapper;
|
|
19
22
|
exports.relationNameFromColumn = relationNameFromColumn;
|
|
20
23
|
exports.isUnknownTsType = isUnknownTsType;
|
|
24
|
+
exports.parsePlainUniqueIndexColumns = parsePlainUniqueIndexColumns;
|
|
25
|
+
exports.detectUniqueForeignKeySets = detectUniqueForeignKeySets;
|
|
21
26
|
exports.buildRelationsFromForeignKeys = buildRelationsFromForeignKeys;
|
|
22
27
|
exports.addAutoManyToManyRelations = addAutoManyToManyRelations;
|
|
23
28
|
exports.deriveEngineRelations = deriveEngineRelations;
|
|
@@ -190,6 +195,61 @@ const SQL_ENUMS = `
|
|
|
190
195
|
ORDER BY t.typname, e.enumsortorder
|
|
191
196
|
`;
|
|
192
197
|
// ---------------------------------------------------------------------------
|
|
198
|
+
// Default table exclusions (F12)
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
/**
|
|
201
|
+
* Migration-bookkeeping tables that introspection drops by default: Turbine's
|
|
202
|
+
* own `_turbine_migrations` / `_turbine_metrics` and Prisma's
|
|
203
|
+
* `_prisma_migrations`. These are almost never meant to be surfaced as typed
|
|
204
|
+
* accessors, and a fresh migrate-from-Prisma introspection would otherwise emit
|
|
205
|
+
* a `PrismaMigrations` entity plus stray FK-derived relations on neighbours.
|
|
206
|
+
*
|
|
207
|
+
* A table named here is dropped UNLESS it is explicitly listed in
|
|
208
|
+
* `options.include` (`include` is the escape hatch, no separate flag), and
|
|
209
|
+
* naming a default-excluded table restores its old generated output byte for
|
|
210
|
+
* byte. The list is deliberately tight (exactly these three); leading-
|
|
211
|
+
* underscore tables are legitimate user tables and are never blanket-excluded.
|
|
212
|
+
*/
|
|
213
|
+
exports.DEFAULT_EXCLUDED_TABLES = ['_turbine_migrations', '_prisma_migrations', '_turbine_metrics'];
|
|
214
|
+
/**
|
|
215
|
+
* The single authority for turning a raw list of candidate table names into the
|
|
216
|
+
* introspected set, shared by the Postgres catalog reader and every engine
|
|
217
|
+
* introspector (SQLite / MySQL / MSSQL / PowDB) so all surfaces agree.
|
|
218
|
+
*
|
|
219
|
+
* Order of operations:
|
|
220
|
+
* 1. `include` filter: when non-empty, keep only the named tables.
|
|
221
|
+
* 2. user `exclude`: drop anything the caller listed.
|
|
222
|
+
* 3. {@link DEFAULT_EXCLUDED_TABLES}: drop migration bookkeeping tables,
|
|
223
|
+
* EXCEPT any that the caller explicitly named in `include` (the escape
|
|
224
|
+
* hatch that restores the pre-0.41 output for those tables).
|
|
225
|
+
*/
|
|
226
|
+
function applyTableFilters(names, options = {}) {
|
|
227
|
+
let result = names;
|
|
228
|
+
const includeSet = options.include?.length ? new Set(options.include) : null;
|
|
229
|
+
if (includeSet) {
|
|
230
|
+
result = result.filter((t) => includeSet.has(t));
|
|
231
|
+
}
|
|
232
|
+
if (options.exclude?.length) {
|
|
233
|
+
const excludeSet = new Set(options.exclude);
|
|
234
|
+
result = result.filter((t) => !excludeSet.has(t));
|
|
235
|
+
}
|
|
236
|
+
// Default exclusions never override an explicit include.
|
|
237
|
+
const defaults = new Set(exports.DEFAULT_EXCLUDED_TABLES);
|
|
238
|
+
result = result.filter((t) => !defaults.has(t) || (includeSet?.has(t) ?? false));
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The subset of {@link DEFAULT_EXCLUDED_TABLES} that were present in `names` but
|
|
243
|
+
* dropped by {@link applyTableFilters} (i.e. not re-added via `include`). Pure
|
|
244
|
+
* helper so the CLI can report "skipped internal table X" without re-deriving
|
|
245
|
+
* the filtering rule.
|
|
246
|
+
*/
|
|
247
|
+
function defaultExcludedTablesPresent(names, options = {}) {
|
|
248
|
+
const includeSet = options.include?.length ? new Set(options.include) : null;
|
|
249
|
+
const present = new Set(names);
|
|
250
|
+
return exports.DEFAULT_EXCLUDED_TABLES.filter((t) => present.has(t) && !(includeSet?.has(t) ?? false));
|
|
251
|
+
}
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
193
253
|
// Main introspection function
|
|
194
254
|
// ---------------------------------------------------------------------------
|
|
195
255
|
/**
|
|
@@ -257,16 +317,18 @@ async function introspectPostgresCatalog(options) {
|
|
|
257
317
|
onUpdate: pgConfActionToReferential(row.confupdtype),
|
|
258
318
|
});
|
|
259
319
|
}
|
|
260
|
-
// Filter tables by include/exclude
|
|
261
|
-
// candidates so
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
320
|
+
// Filter tables by include/exclude + default bookkeeping-table exclusions
|
|
321
|
+
// (F12). Views/matviews join the base tables as candidates so the filters
|
|
322
|
+
// apply uniformly.
|
|
323
|
+
const candidateTables = [
|
|
324
|
+
...tablesResult.rows.map((r) => r.table_name),
|
|
325
|
+
...viewNameSet,
|
|
326
|
+
];
|
|
327
|
+
const tableNames = applyTableFilters(candidateTables, options);
|
|
328
|
+
if (options.onDefaultTableExclusion) {
|
|
329
|
+
const skipped = defaultExcludedTablesPresent(candidateTables, options);
|
|
330
|
+
if (skipped.length > 0)
|
|
331
|
+
options.onDefaultTableExclusion(skipped);
|
|
270
332
|
}
|
|
271
333
|
const tableSet = new Set(tableNames);
|
|
272
334
|
// ----- Group columns by table -----
|
|
@@ -421,7 +483,13 @@ async function introspectPostgresCatalog(options) {
|
|
|
421
483
|
// main, so only genuine json/jsonb columns qualify as historical shadows.
|
|
422
484
|
unknownTypedFieldsByTable.set(tbl, new Set(cols.filter((c) => isUnknownTsType(c.tsType) && !Object.hasOwn(enums, c.pgType)).map((c) => c.field)));
|
|
423
485
|
}
|
|
424
|
-
|
|
486
|
+
// F2: unless the caller opts out, detect child FK column sets that a unique
|
|
487
|
+
// constraint / plain unique index exactly covers, so the reverse relation is
|
|
488
|
+
// emitted as a one-to-one (`hasOne`) instead of `hasMany`.
|
|
489
|
+
const uniqueSetsByTable = options.legacyToManyUniques
|
|
490
|
+
? undefined
|
|
491
|
+
: detectUniqueForeignKeySets(pkByTable, uniqueByTable, indexesByTable);
|
|
492
|
+
const relationsByTable = buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActions, unknownTypedFieldsByTable, uniqueSetsByTable);
|
|
425
493
|
// ----- Conservative many-to-many auto-detection (PURELY ADDITIVE) -----
|
|
426
494
|
//
|
|
427
495
|
// Auto-detecting m2m is a footgun: any table with two FKs *looks* like a
|
|
@@ -551,6 +619,92 @@ function upperFirst(s) {
|
|
|
551
619
|
function isUnknownTsType(tsType) {
|
|
552
620
|
return tsType === 'unknown' || tsType === 'unknown | null';
|
|
553
621
|
}
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
// Unique-foreign-key detection for one-to-one relations (F2)
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
/** True when two column lists cover the same set (order-insensitive, no dupes). */
|
|
626
|
+
function columnSetsEqual(a, b) {
|
|
627
|
+
if (a.length !== b.length)
|
|
628
|
+
return false;
|
|
629
|
+
const bs = new Set(b);
|
|
630
|
+
return a.every((c) => bs.has(c));
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Parse the column list of a PLAIN unique index from its `pg_indexes.indexdef`,
|
|
634
|
+
* returning `null` for anything that does NOT guarantee at-most-one child row:
|
|
635
|
+
*
|
|
636
|
+
* - a PARTIAL index (has a `WHERE` clause): only unique within the predicate;
|
|
637
|
+
* - an EXPRESSION index (`lower(email)`, `(a || b)`): the uniqueness is on the
|
|
638
|
+
* expression, not the raw FK column set.
|
|
639
|
+
*
|
|
640
|
+
* Anchors on the `USING <method> (` clause the same way
|
|
641
|
+
* {@link describeIndexDefMismatch} does, so a partial index's `WHERE (...)`
|
|
642
|
+
* parentheses are never mistaken for the column list. Every column token must be
|
|
643
|
+
* a bare or double-quoted identifier; anything else (a function call, an
|
|
644
|
+
* operator expression) fails the check and yields `null`.
|
|
645
|
+
*/
|
|
646
|
+
function parsePlainUniqueIndexColumns(indexdef) {
|
|
647
|
+
// Partial index: uniqueness is scoped to the WHERE predicate.
|
|
648
|
+
if (/\bWHERE\b/i.test(indexdef))
|
|
649
|
+
return null;
|
|
650
|
+
const paren = indexdef.match(/USING\s+\w+\s*\(([^)]*)\)/i);
|
|
651
|
+
if (!paren)
|
|
652
|
+
return null;
|
|
653
|
+
const tokens = paren[1].split(',').map((c) => c
|
|
654
|
+
.trim()
|
|
655
|
+
.replace(/\s+(ASC|DESC|NULLS\s+(FIRST|LAST))\b/gi, '')
|
|
656
|
+
.trim());
|
|
657
|
+
const columns = [];
|
|
658
|
+
for (const token of tokens) {
|
|
659
|
+
if (token.length === 0)
|
|
660
|
+
return null;
|
|
661
|
+
if (/^"(?:[^"]|"")*"$/.test(token)) {
|
|
662
|
+
// Quoted identifier: unquote and unescape doubled quotes.
|
|
663
|
+
columns.push(token.slice(1, -1).replace(/""/g, '"'));
|
|
664
|
+
}
|
|
665
|
+
else if (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)) {
|
|
666
|
+
columns.push(token);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
// Expression column (function call, operator, cast, and the like): never a plain FK.
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return columns.length > 0 ? columns : null;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Assemble, per table, every column set that EXACTLY guarantees at-most-one row:
|
|
677
|
+
* the primary key, every UNIQUE constraint, and every PLAIN (non-partial,
|
|
678
|
+
* non-expression) UNIQUE index. Consumed by
|
|
679
|
+
* {@link buildRelationsFromForeignKeys} to flip a child relation whose FK column
|
|
680
|
+
* set matches one of these sets from `hasMany` to `hasOne` (F2, Prisma
|
|
681
|
+
* one-to-one parity).
|
|
682
|
+
*/
|
|
683
|
+
function detectUniqueForeignKeySets(pkByTable, uniqueByTable, indexesByTable) {
|
|
684
|
+
const result = new Map();
|
|
685
|
+
const add = (table, cols) => {
|
|
686
|
+
if (cols.length === 0)
|
|
687
|
+
return;
|
|
688
|
+
if (!result.has(table))
|
|
689
|
+
result.set(table, []);
|
|
690
|
+
result.get(table).push(cols);
|
|
691
|
+
};
|
|
692
|
+
for (const [table, pk] of pkByTable)
|
|
693
|
+
add(table, pk);
|
|
694
|
+
for (const [table, sets] of uniqueByTable)
|
|
695
|
+
for (const cols of sets)
|
|
696
|
+
add(table, cols);
|
|
697
|
+
for (const [table, indexes] of indexesByTable) {
|
|
698
|
+
for (const idx of indexes) {
|
|
699
|
+
if (!idx.unique)
|
|
700
|
+
continue;
|
|
701
|
+
const cols = parsePlainUniqueIndexColumns(idx.definition);
|
|
702
|
+
if (cols)
|
|
703
|
+
add(table, cols);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return result;
|
|
707
|
+
}
|
|
554
708
|
/**
|
|
555
709
|
* Resolve a derived relation name against the names already taken on the
|
|
556
710
|
* table (scalar column fields + previously assigned relations). On collision,
|
|
@@ -597,8 +751,16 @@ function resolveRelationNameCollision(candidate, taken, table, source) {
|
|
|
597
751
|
* guarantee relations never shadow concrete-typed scalar columns.
|
|
598
752
|
* @param unknownTypedFieldsByTable subset of the column fields whose tsType is
|
|
599
753
|
* `unknown` (json/jsonb) — legacy shadows of these are preserved (rule 2).
|
|
754
|
+
* @param uniqueSetsByTable when provided (F2), the child-table column sets that
|
|
755
|
+
* guarantee at-most-one row (PK + unique constraints + plain unique indexes,
|
|
756
|
+
* from {@link detectUniqueForeignKeySets}). A reverse relation whose FK column
|
|
757
|
+
* set EXACTLY matches one of the child's unique sets is emitted as `hasOne`
|
|
758
|
+
* (to-one) instead of `hasMany`, and named with the SINGULAR of the child
|
|
759
|
+
* table (falling back to the legacy plural name on collision). Omit it (the
|
|
760
|
+
* engine introspectors and `defineSchema` path do) to keep every reverse
|
|
761
|
+
* relation `hasMany`.
|
|
600
762
|
*/
|
|
601
|
-
function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActions, unknownTypedFieldsByTable) {
|
|
763
|
+
function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActions, unknownTypedFieldsByTable, uniqueSetsByTable) {
|
|
602
764
|
// Count FKs per (source, target) pair for disambiguation.
|
|
603
765
|
const fkCounts = new Map();
|
|
604
766
|
for (const fk of foreignKeys) {
|
|
@@ -687,26 +849,41 @@ function buildRelationsFromForeignKeys(foreignKeys, columnFieldsByTable, fkActio
|
|
|
687
849
|
referenceKey,
|
|
688
850
|
...actionFields,
|
|
689
851
|
};
|
|
690
|
-
// ---
|
|
691
|
-
// e.g. posts.user_id → users.id creates users.posts (hasMany)
|
|
692
|
-
|
|
852
|
+
// --- reverse relation on the target (parent) table ---
|
|
853
|
+
// e.g. posts.user_id → users.id creates users.posts (hasMany), UNLESS the
|
|
854
|
+
// child's FK column set is exactly covered by a unique constraint / plain
|
|
855
|
+
// unique index (F2), then it is a one-to-one, emitted as `hasOne` and
|
|
856
|
+
// named with the SINGULAR of the child table.
|
|
857
|
+
const isUniqueFk = (uniqueSetsByTable?.get(fk.sourceTable) ?? []).some((set) => columnSetsEqual(set, fk.sourceColumns));
|
|
858
|
+
const disambSuffix = needsDisambiguation
|
|
859
|
+
? singleColumn
|
|
860
|
+
? `By${upperFirst(relationNameFromColumn(fk.sourceColumns[0]))}`
|
|
861
|
+
: `By${upperFirst((0, schema_js_1.snakeToCamel)(constraintBase))}`
|
|
862
|
+
: '';
|
|
863
|
+
const legacyReverse = needsDisambiguation
|
|
693
864
|
? singleColumn
|
|
694
865
|
? (0, schema_js_1.snakeToCamel)(`${fk.sourceTable}_by_${fk.sourceColumns[0].replace(/_id$/, '')}`)
|
|
695
866
|
: (0, schema_js_1.snakeToCamel)(`${fk.sourceTable}_by_${constraintBase}`)
|
|
696
867
|
: (0, schema_js_1.snakeToCamel)(fk.sourceTable);
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
868
|
+
const modernReverse = needsDisambiguation ? `${(0, schema_js_1.snakeToCamel)(fk.sourceTable)}${disambSuffix}` : null;
|
|
869
|
+
let reverseName;
|
|
870
|
+
const reverseType = isUniqueFk ? 'hasOne' : 'hasMany';
|
|
871
|
+
if (isUniqueFk) {
|
|
872
|
+
// Prefer the singular child-table name; fall back to the legacy plural
|
|
873
|
+
// (which stays byte-stable for any app that was on the pre-flip shape).
|
|
874
|
+
const singularReverse = `${(0, schema_js_1.singularize)((0, schema_js_1.snakeToCamel)(fk.sourceTable))}${disambSuffix}`;
|
|
875
|
+
reverseName = resolveName(singularReverse, legacyReverse, fk.targetTable, `FK ${fk.constraintName}`);
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
reverseName = resolveName(legacyReverse, modernReverse, fk.targetTable, `FK ${fk.constraintName}`);
|
|
879
|
+
}
|
|
880
|
+
takenFor(fk.targetTable).add(reverseName);
|
|
881
|
+
assignedFor(fk.targetTable).add(reverseName);
|
|
705
882
|
if (!relationsByTable.has(fk.targetTable))
|
|
706
883
|
relationsByTable.set(fk.targetTable, {});
|
|
707
|
-
relationsByTable.get(fk.targetTable)[
|
|
708
|
-
type:
|
|
709
|
-
name:
|
|
884
|
+
relationsByTable.get(fk.targetTable)[reverseName] = {
|
|
885
|
+
type: reverseType,
|
|
886
|
+
name: reverseName,
|
|
710
887
|
from: fk.targetTable,
|
|
711
888
|
to: fk.sourceTable,
|
|
712
889
|
foreignKey,
|
package/dist/cjs/mssql.js
CHANGED
|
@@ -560,7 +560,12 @@ exports.mssqlDialect = {
|
|
|
560
560
|
.map((row) => `(${row.map(() => this.paramPlaceholder(++n)).join(', ')})`)
|
|
561
561
|
.join(', ');
|
|
562
562
|
const out = mssqlOutput(input.returning, 'INSERTED');
|
|
563
|
-
//
|
|
563
|
+
// SQL Server has no single-statement skip-duplicates form (no ON CONFLICT /
|
|
564
|
+
// INSERT IGNORE), so silently dropping the flag would let duplicate rows
|
|
565
|
+
// through against the caller's intent. Refuse loudly instead.
|
|
566
|
+
if (input.skipDuplicates) {
|
|
567
|
+
throw new errors_js_1.UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'mssql', 'SQL Server has no ON CONFLICT DO NOTHING equivalent, pre-filter conflicting rows or use a MERGE.');
|
|
568
|
+
}
|
|
564
569
|
return {
|
|
565
570
|
sql: `INSERT INTO ${input.table} (${input.columns.join(', ')})${out} VALUES ${placeholders}`,
|
|
566
571
|
params: input.rowValues.flat(),
|
|
@@ -934,15 +939,9 @@ function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, colum
|
|
|
934
939
|
*/
|
|
935
940
|
async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
|
|
936
941
|
// ----- Tables -----
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
tableNames = tableNames.filter((t) => inc.has(t));
|
|
941
|
-
}
|
|
942
|
-
if (options.exclude?.length) {
|
|
943
|
-
const exc = new Set(options.exclude);
|
|
944
|
-
tableNames = tableNames.filter((t) => !exc.has(t));
|
|
945
|
-
}
|
|
942
|
+
const candidateTables = (await exec("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @p1 AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME", [schema])).map((r) => String(r.TABLE_NAME));
|
|
943
|
+
// include / exclude + default bookkeeping-table exclusions (F12).
|
|
944
|
+
const tableNames = (0, introspect_js_1.applyTableFilters)(candidateTables, options);
|
|
946
945
|
const tableSet = new Set(tableNames);
|
|
947
946
|
// ----- Identity columns (mark hasDefault) -----
|
|
948
947
|
const identityRows = await exec(`SELECT t.name AS TABLE_NAME, c.name AS COLUMN_NAME
|
package/dist/cjs/mysql.js
CHANGED
|
@@ -596,15 +596,9 @@ function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, colum
|
|
|
596
596
|
*/
|
|
597
597
|
async function introspectMysqlWith(exec, schema, options = {}) {
|
|
598
598
|
// ----- Tables -----
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
tableNames = tableNames.filter((t) => inc.has(t));
|
|
603
|
-
}
|
|
604
|
-
if (options.exclude?.length) {
|
|
605
|
-
const exc = new Set(options.exclude);
|
|
606
|
-
tableNames = tableNames.filter((t) => !exc.has(t));
|
|
607
|
-
}
|
|
599
|
+
const candidateTables = (await exec("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = :p1 AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME", [schema])).map((r) => String(r.TABLE_NAME));
|
|
600
|
+
// include / exclude + default bookkeeping-table exclusions (F12).
|
|
601
|
+
const tableNames = (0, introspect_js_1.applyTableFilters)(candidateTables, options);
|
|
608
602
|
const tableSet = new Set(tableNames);
|
|
609
603
|
// ----- Columns -----
|
|
610
604
|
const columnRows = (await exec(`SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
58
|
exports.introspectPowdbDatabase = introspectPowdbDatabase;
|
|
59
59
|
const errors_js_1 = require("./errors.js");
|
|
60
|
+
const introspect_js_1 = require("./introspect.js");
|
|
60
61
|
const powdb_js_1 = require("./powdb.js");
|
|
61
62
|
const schema_js_1 = require("./schema.js");
|
|
62
63
|
/** Coerce a wire cell to string (legacy wire cells are strings; native cells may be typed). */
|
|
@@ -110,24 +111,18 @@ async function introspectPowdbDatabase(exec, options = {}) {
|
|
|
110
111
|
}
|
|
111
112
|
// ----- Types (one row per table, columns `name`, `columns`) -----
|
|
112
113
|
const schemaRows = (await exec('schema')).rows;
|
|
113
|
-
|
|
114
|
+
const candidateTables = schemaRows.map((r) => asString(r.name)).filter((n) => n.length > 0);
|
|
114
115
|
// A mis-shaped `exec` (e.g. the raw client's positional `string[][]` rows
|
|
115
116
|
// passed straight through) yields rows whose `name` cell is `undefined`, so
|
|
116
117
|
// every table filters out and the schema comes back silently empty. Refuse
|
|
117
118
|
// that instead of losing data: real rows must carry a `name`.
|
|
118
|
-
if (schemaRows.length > 0 &&
|
|
119
|
+
if (schemaRows.length > 0 && candidateTables.length === 0) {
|
|
119
120
|
throw new errors_js_1.ValidationError(`[turbine] PowDB introspection: the \`schema\` statement returned ${schemaRows.length} row(s) but none carried a ` +
|
|
120
121
|
'`name` cell. The `exec` you supplied likely returns POSITIONAL rows (string[][]) rather than records keyed by ' +
|
|
121
122
|
'column name; zip `columns` with each row (see introspectPowdbDatabase docs).');
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
tableNames = tableNames.filter((t) => inc.has(t));
|
|
126
|
-
}
|
|
127
|
-
if (options.exclude?.length) {
|
|
128
|
-
const exc = new Set(options.exclude);
|
|
129
|
-
tableNames = tableNames.filter((t) => !exc.has(t));
|
|
130
|
-
}
|
|
124
|
+
// include / exclude + default bookkeeping-table exclusions (F12).
|
|
125
|
+
const tableNames = (0, introspect_js_1.applyTableFilters)(candidateTables, options);
|
|
131
126
|
const tables = {};
|
|
132
127
|
for (const tableName of tableNames) {
|
|
133
128
|
// `describe` needs the table name in bare-identifier position → quote it so
|
package/dist/cjs/powql.js
CHANGED
|
@@ -74,6 +74,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
74
74
|
const errors_js_1 = require("./errors.js");
|
|
75
75
|
const nested_write_js_1 = require("./nested-write.js");
|
|
76
76
|
const powdb_js_1 = require("./powdb.js");
|
|
77
|
+
const compound_unique_js_1 = require("./query/compound-unique.js");
|
|
77
78
|
const filters_js_1 = require("./query/filters.js");
|
|
78
79
|
const utils_js_1 = require("./query/utils.js");
|
|
79
80
|
const schema_js_1 = require("./schema.js");
|
|
@@ -1043,6 +1044,13 @@ class PowqlInterface {
|
|
|
1043
1044
|
.filter((line) => line.length > 0);
|
|
1044
1045
|
}
|
|
1045
1046
|
async findUnique(args) {
|
|
1047
|
+
// Prisma compound-unique selector → column conjunction (engine parity with
|
|
1048
|
+
// the SQL findUnique family; pure metadata, so this is a one-line adoption).
|
|
1049
|
+
if (args.where) {
|
|
1050
|
+
const expanded = (0, compound_unique_js_1.expandCompoundUniqueWhere)(this.meta, args.where);
|
|
1051
|
+
if (expanded !== args.where)
|
|
1052
|
+
args = { ...args, where: expanded };
|
|
1053
|
+
}
|
|
1046
1054
|
return this.withMiddleware('findUnique', args, async () => {
|
|
1047
1055
|
const { rows, native, nestedPlans, residualWith } = await this.runFind({ ...args, limit: 1 }, 'findUnique');
|
|
1048
1056
|
if (!rows.length)
|
|
@@ -1772,6 +1780,11 @@ class PowqlInterface {
|
|
|
1772
1780
|
}
|
|
1773
1781
|
async createMany(args) {
|
|
1774
1782
|
return this.withMiddleware('createMany', args, async () => {
|
|
1783
|
+
if (args.skipDuplicates) {
|
|
1784
|
+
// PowQL's `insert … returning` has no conflict clause, so there is no
|
|
1785
|
+
// faithful skip-duplicates form; refuse rather than silently insert.
|
|
1786
|
+
throw new errors_js_1.UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'powdb', 'PowQL insert has no ON CONFLICT DO NOTHING equivalent, filter duplicates before inserting.');
|
|
1787
|
+
}
|
|
1775
1788
|
const inputs = args.data.map((d) => this.applyPkDefault(d));
|
|
1776
1789
|
if (!inputs.length)
|
|
1777
1790
|
return [];
|