@mikro-orm/sql 7.0.15-dev.9 → 7.0.16-dev.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/package.json +3 -3
- package/plugin/transformer.d.ts +11 -3
- package/plugin/transformer.js +138 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/sql",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.16-dev.0",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"kysely": "0.28.17"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.0.
|
|
53
|
+
"@mikro-orm/core": "^7.0.15"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.
|
|
56
|
+
"@mikro-orm/core": "7.0.16-dev.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|
package/plugin/transformer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type EntityMetadata, type EntityProperty } from '@mikro-orm/core';
|
|
2
|
-
import { type CommonTableExpressionNameNode, type DeleteQueryNode, type
|
|
1
|
+
import { type EntityMetadata, type EntityProperty, type Type } from '@mikro-orm/core';
|
|
2
|
+
import { type CommonTableExpressionNameNode, type DeleteQueryNode, type InsertQueryNode, type JoinNode, type MergeQueryNode, type OperationNode, type QueryId, type SelectQueryNode, type UpdateQueryNode, type WithNode, ColumnNode, IdentifierNode, OperationNodeTransformer, SelectionNode, TableNode, ValueNode } from 'kysely';
|
|
3
3
|
import type { MikroKyselyPluginOptions } from './index.js';
|
|
4
4
|
import type { SqlEntityManager } from '../SqlEntityManager.js';
|
|
5
5
|
export declare class MikroTransformer extends OperationNodeTransformer {
|
|
@@ -28,10 +28,18 @@ export declare class MikroTransformer extends OperationNodeTransformer {
|
|
|
28
28
|
processOnUpdateHooks(node: UpdateQueryNode, meta: EntityMetadata): UpdateQueryNode;
|
|
29
29
|
processInsertValues(node: InsertQueryNode, meta: EntityMetadata): InsertQueryNode;
|
|
30
30
|
processUpdateValues(node: UpdateQueryNode, meta: EntityMetadata): UpdateQueryNode;
|
|
31
|
+
processInputValueNode(prop: EntityProperty | undefined, fieldName: string | undefined, valueNode: ValueNode): OperationNode;
|
|
32
|
+
expandSelections(selections: readonly SelectionNode[]): readonly SelectionNode[];
|
|
33
|
+
expandSelection(sel: SelectionNode): SelectionNode[] | null;
|
|
34
|
+
expandStar(meta: EntityMetadata | undefined, table: TableNode | undefined, tableName: string | undefined): SelectionNode[] | null;
|
|
35
|
+
wrapRead(customType: Type<any>, fieldName: string, tableName: string | undefined): SelectionNode;
|
|
36
|
+
wrapWrite(prop: EntityProperty | undefined, fieldName: string | undefined, valueNode: ValueNode): OperationNode;
|
|
37
|
+
/** Resolve the customType for a specific field name (handles composite-PK FK fan-out via prop.customTypes[]). */
|
|
38
|
+
fieldType(prop: EntityProperty, fieldName: string): Type<any> | undefined;
|
|
39
|
+
findOwnerMeta(name: string | undefined): EntityMetadata | undefined;
|
|
31
40
|
mapColumnsToProperties(columns: readonly ColumnNode[], meta: EntityMetadata): (EntityProperty | undefined)[];
|
|
32
41
|
normalizeColumnName(identifier: IdentifierNode): string;
|
|
33
42
|
findProperty(meta: EntityMetadata | undefined, columnName?: string): EntityProperty | undefined;
|
|
34
|
-
shouldConvertValues(): boolean;
|
|
35
43
|
prepareInputValue(prop: EntityProperty | undefined, value: unknown, enabled: boolean): unknown;
|
|
36
44
|
/**
|
|
37
45
|
* Look up a table name/alias in the context stack.
|
package/plugin/transformer.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
|
-
import { AliasNode, ColumnNode, ColumnUpdateNode, OperationNodeTransformer, PrimitiveValueListNode, ReferenceNode, SchemableIdentifierNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
|
|
2
|
+
import { AliasNode, ColumnNode, ColumnUpdateNode, IdentifierNode, OperationNodeTransformer, PrimitiveValueListNode, RawNode, ReferenceNode, SchemableIdentifierNode, SelectAllNode, SelectionNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
|
|
3
|
+
const EXPANDABLE_KINDS = new Set([
|
|
4
|
+
ReferenceKind.SCALAR,
|
|
5
|
+
ReferenceKind.EMBEDDED,
|
|
6
|
+
ReferenceKind.MANY_TO_ONE,
|
|
7
|
+
ReferenceKind.ONE_TO_ONE,
|
|
8
|
+
]);
|
|
3
9
|
export class MikroTransformer extends OperationNodeTransformer {
|
|
4
10
|
/**
|
|
5
11
|
* Context stack to support nested queries (subqueries, CTEs)
|
|
@@ -64,7 +70,14 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
64
70
|
this.processJoinNode(join, currentContext);
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
|
-
|
|
73
|
+
const transformed = super.transformSelectQuery(node, queryId);
|
|
74
|
+
if (this.#options.convertValues && transformed.selections?.length) {
|
|
75
|
+
const selections = this.expandSelections(transformed.selections);
|
|
76
|
+
if (selections !== transformed.selections) {
|
|
77
|
+
return { ...transformed, selections };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return transformed;
|
|
68
81
|
}
|
|
69
82
|
finally {
|
|
70
83
|
// Pop the context when exiting this query scope
|
|
@@ -365,32 +378,38 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
365
378
|
return node;
|
|
366
379
|
}
|
|
367
380
|
const columnProps = this.mapColumnsToProperties(node.columns, meta);
|
|
368
|
-
const
|
|
381
|
+
const fieldNames = node.columns.map(c => this.normalizeColumnName(c.column));
|
|
382
|
+
// hasConvertToDatabaseValueSQL is set by MetadataDiscovery only when the SQL is non-trivial
|
|
383
|
+
// (i.e. it actually wraps `?`), so a no-op cast on sqlite won't force a row upgrade.
|
|
384
|
+
const needsSqlWrap = columnProps.some(p => p?.hasConvertToDatabaseValueSQL);
|
|
369
385
|
let changed = false;
|
|
370
386
|
const convertedRows = node.values.values.map(row => {
|
|
371
|
-
if (ValueListNode.is(row)) {
|
|
372
|
-
if (row.values.length !== columnProps.length) {
|
|
373
|
-
return row;
|
|
374
|
-
}
|
|
387
|
+
if (ValueListNode.is(row) && row.values.length === columnProps.length) {
|
|
375
388
|
const values = row.values.map((valueNode, idx) => {
|
|
376
389
|
if (!ValueNode.is(valueNode)) {
|
|
377
390
|
return valueNode;
|
|
378
391
|
}
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
381
|
-
|
|
392
|
+
const newNode = this.processInputValueNode(columnProps[idx], fieldNames[idx], valueNode);
|
|
393
|
+
if (newNode !== valueNode) {
|
|
394
|
+
changed = true;
|
|
382
395
|
}
|
|
383
|
-
|
|
384
|
-
return valueNode.immediate ? ValueNode.createImmediate(converted) : ValueNode.create(converted);
|
|
396
|
+
return newNode;
|
|
385
397
|
});
|
|
386
398
|
return ValueListNode.create(values);
|
|
387
399
|
}
|
|
388
|
-
if (PrimitiveValueListNode.is(row)) {
|
|
389
|
-
|
|
390
|
-
|
|
400
|
+
if (PrimitiveValueListNode.is(row) && row.values.length === columnProps.length) {
|
|
401
|
+
// upgrade to ValueListNode when any column needs SQL-side wrapping, since
|
|
402
|
+
// PrimitiveValueListNode can only hold primitives
|
|
403
|
+
if (needsSqlWrap) {
|
|
404
|
+
changed = true;
|
|
405
|
+
return ValueListNode.create(row.values.map((value, idx) => {
|
|
406
|
+
const prop = columnProps[idx];
|
|
407
|
+
const converted = this.prepareInputValue(prop, value, true);
|
|
408
|
+
return this.wrapWrite(prop, fieldNames[idx], ValueNode.create(converted));
|
|
409
|
+
}));
|
|
391
410
|
}
|
|
392
411
|
const values = row.values.map((value, idx) => {
|
|
393
|
-
const converted = this.prepareInputValue(columnProps[idx], value,
|
|
412
|
+
const converted = this.prepareInputValue(columnProps[idx], value, true);
|
|
394
413
|
if (converted !== value) {
|
|
395
414
|
changed = true;
|
|
396
415
|
}
|
|
@@ -412,7 +431,6 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
412
431
|
if (!node.updates?.length) {
|
|
413
432
|
return node;
|
|
414
433
|
}
|
|
415
|
-
const shouldConvert = this.shouldConvertValues();
|
|
416
434
|
let changed = false;
|
|
417
435
|
const updates = node.updates.map(updateNode => {
|
|
418
436
|
if (!ValueNode.is(updateNode.value)) {
|
|
@@ -422,18 +440,12 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
422
440
|
? this.normalizeColumnName(updateNode.column.column)
|
|
423
441
|
: undefined;
|
|
424
442
|
const property = this.findProperty(meta, columnName);
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
443
|
+
const newValue = this.processInputValueNode(property, columnName, updateNode.value);
|
|
444
|
+
if (newValue === updateNode.value) {
|
|
427
445
|
return updateNode;
|
|
428
446
|
}
|
|
429
447
|
changed = true;
|
|
430
|
-
|
|
431
|
-
? ValueNode.createImmediate(converted)
|
|
432
|
-
: ValueNode.create(converted);
|
|
433
|
-
return {
|
|
434
|
-
...updateNode,
|
|
435
|
-
value: newValueNode,
|
|
436
|
-
};
|
|
448
|
+
return { ...updateNode, value: newValue };
|
|
437
449
|
});
|
|
438
450
|
if (!changed) {
|
|
439
451
|
return node;
|
|
@@ -443,6 +455,106 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
443
455
|
updates,
|
|
444
456
|
};
|
|
445
457
|
}
|
|
458
|
+
processInputValueNode(prop, fieldName, valueNode) {
|
|
459
|
+
const converted = this.prepareInputValue(prop, valueNode.value, true);
|
|
460
|
+
const newValueNode = converted === valueNode.value
|
|
461
|
+
? valueNode
|
|
462
|
+
: valueNode.immediate
|
|
463
|
+
? ValueNode.createImmediate(converted)
|
|
464
|
+
: ValueNode.create(converted);
|
|
465
|
+
return this.wrapWrite(prop, fieldName, newValueNode);
|
|
466
|
+
}
|
|
467
|
+
expandSelections(selections) {
|
|
468
|
+
const out = [];
|
|
469
|
+
let changed = false;
|
|
470
|
+
for (const sel of selections) {
|
|
471
|
+
const replaced = this.expandSelection(sel);
|
|
472
|
+
if (replaced) {
|
|
473
|
+
out.push(...replaced);
|
|
474
|
+
changed = true;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
out.push(sel);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return changed ? out : selections;
|
|
481
|
+
}
|
|
482
|
+
expandSelection(sel) {
|
|
483
|
+
const inner = sel.selection;
|
|
484
|
+
if (SelectAllNode.is(inner)) {
|
|
485
|
+
return this.expandStar(this.findOwnerMeta(undefined), undefined, undefined);
|
|
486
|
+
}
|
|
487
|
+
if (!ReferenceNode.is(inner)) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const table = inner.table;
|
|
491
|
+
const tableName = table ? this.getTableName(table) : undefined;
|
|
492
|
+
const meta = this.findOwnerMeta(tableName);
|
|
493
|
+
if (!meta) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
if (SelectAllNode.is(inner.column)) {
|
|
497
|
+
return this.expandStar(meta, table, tableName);
|
|
498
|
+
}
|
|
499
|
+
const fieldName = inner.column.column.name;
|
|
500
|
+
const prop = this.findProperty(meta, fieldName);
|
|
501
|
+
const ct = prop && this.fieldType(prop, fieldName);
|
|
502
|
+
return ct?.convertToJSValueSQL ? [this.wrapRead(ct, fieldName, tableName)] : null;
|
|
503
|
+
}
|
|
504
|
+
expandStar(meta, table, tableName) {
|
|
505
|
+
if (!meta || !meta.props.some(p => p.hasConvertToJSValueSQL)) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
const out = [];
|
|
509
|
+
for (const prop of meta.props) {
|
|
510
|
+
if (prop.persist === false || !prop.fieldNames?.length || !EXPANDABLE_KINDS.has(prop.kind)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
for (const fieldName of prop.fieldNames) {
|
|
514
|
+
const ct = this.fieldType(prop, fieldName);
|
|
515
|
+
out.push(ct?.convertToJSValueSQL
|
|
516
|
+
? this.wrapRead(ct, fieldName, tableName)
|
|
517
|
+
: SelectionNode.create(table ? ReferenceNode.create(ColumnNode.create(fieldName), table) : ColumnNode.create(fieldName)));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return out;
|
|
521
|
+
}
|
|
522
|
+
wrapRead(customType, fieldName, tableName) {
|
|
523
|
+
const key = this.#platform.quoteIdentifier(tableName ? `${tableName}.${fieldName}` : fieldName);
|
|
524
|
+
const sql = customType.convertToJSValueSQL(key, this.#platform);
|
|
525
|
+
return SelectionNode.create(AliasNode.create(RawNode.createWithSql(sql), IdentifierNode.create(fieldName)));
|
|
526
|
+
}
|
|
527
|
+
wrapWrite(prop, fieldName, valueNode) {
|
|
528
|
+
if (!prop?.hasConvertToDatabaseValueSQL || !fieldName || valueNode.value == null || isRaw(valueNode.value)) {
|
|
529
|
+
return valueNode;
|
|
530
|
+
}
|
|
531
|
+
const customType = this.fieldType(prop, fieldName);
|
|
532
|
+
if (!customType?.convertToDatabaseValueSQL) {
|
|
533
|
+
return valueNode;
|
|
534
|
+
}
|
|
535
|
+
const fragments = customType.convertToDatabaseValueSQL('?', this.#platform).split('?');
|
|
536
|
+
return RawNode.create(fragments, fragments.slice(0, -1).map(() => valueNode));
|
|
537
|
+
}
|
|
538
|
+
/** Resolve the customType for a specific field name (handles composite-PK FK fan-out via prop.customTypes[]). */
|
|
539
|
+
fieldType(prop, fieldName) {
|
|
540
|
+
return prop.customType ?? prop.customTypes?.[prop.fieldNames.indexOf(fieldName)];
|
|
541
|
+
}
|
|
542
|
+
findOwnerMeta(name) {
|
|
543
|
+
if (name) {
|
|
544
|
+
return this.lookupInContextStack(name) ?? this.#subqueryAliasMap.get(name) ?? this.findEntityMetadata(name);
|
|
545
|
+
}
|
|
546
|
+
let single;
|
|
547
|
+
for (const meta of this.#contextStack[this.#contextStack.length - 1].values()) {
|
|
548
|
+
if (!meta) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (single && single !== meta) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
single = meta;
|
|
555
|
+
}
|
|
556
|
+
return single;
|
|
557
|
+
}
|
|
446
558
|
mapColumnsToProperties(columns, meta) {
|
|
447
559
|
return columns.map(column => {
|
|
448
560
|
const columnName = this.normalizeColumnName(column.column);
|
|
@@ -466,9 +578,6 @@ export class MikroTransformer extends OperationNodeTransformer {
|
|
|
466
578
|
}
|
|
467
579
|
return meta.props.find(prop => prop.fieldNames?.includes(columnName));
|
|
468
580
|
}
|
|
469
|
-
shouldConvertValues() {
|
|
470
|
-
return !!this.#options.convertValues;
|
|
471
|
-
}
|
|
472
581
|
prepareInputValue(prop, value, enabled) {
|
|
473
582
|
if (!enabled || !prop || value == null) {
|
|
474
583
|
return value;
|