@mikro-orm/core 7.3.0-dev.2 → 7.3.0-dev.20
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/EntityManager.js +131 -32
- package/drivers/DatabaseDriver.d.ts +2 -0
- package/drivers/DatabaseDriver.js +16 -3
- package/entity/EntityAssigner.js +15 -3
- package/entity/EntityFactory.d.ts +8 -0
- package/entity/EntityFactory.js +69 -3
- package/entity/EntityLoader.js +31 -15
- package/entity/WrappedEntity.js +6 -1
- package/entity/defineEntity.d.ts +32 -19
- package/entity/defineEntity.js +9 -0
- package/entity/validators.js +4 -1
- package/errors.d.ts +7 -0
- package/errors.js +15 -0
- package/hydration/ObjectHydrator.js +6 -2
- package/metadata/MetadataDiscovery.d.ts +10 -0
- package/metadata/MetadataDiscovery.js +178 -30
- package/metadata/MetadataStorage.d.ts +8 -0
- package/metadata/MetadataStorage.js +47 -3
- package/metadata/MetadataValidator.js +2 -1
- package/metadata/types.d.ts +12 -0
- package/package.json +1 -1
- package/platforms/Platform.d.ts +6 -0
- package/platforms/Platform.js +26 -0
- package/serialization/EntitySerializer.js +52 -12
- package/types/DecimalType.js +34 -1
- package/types/DoubleType.d.ts +7 -3
- package/types/DoubleType.js +8 -3
- package/typings.d.ts +26 -6
- package/typings.js +5 -2
- package/unit-of-work/ChangeSetComputer.js +22 -7
- package/unit-of-work/ChangeSetPersister.js +24 -5
- package/unit-of-work/CommitOrderCalculator.js +2 -1
- package/unit-of-work/IdentityMap.d.ts +3 -2
- package/unit-of-work/IdentityMap.js +30 -7
- package/unit-of-work/UnitOfWork.js +45 -7
- package/utils/Configuration.d.ts +12 -1
- package/utils/EntityComparator.d.ts +5 -0
- package/utils/EntityComparator.js +87 -19
- package/utils/QueryHelper.d.ts +14 -0
- package/utils/QueryHelper.js +90 -13
- package/utils/RawQueryFragment.d.ts +5 -1
- package/utils/RawQueryFragment.js +27 -19
- package/utils/TransactionManager.js +4 -0
- package/utils/Utils.d.ts +2 -0
- package/utils/Utils.js +24 -8
- package/utils/clone.js +5 -7
- package/utils/env-vars.js +2 -0
- package/utils/fs-utils.js +19 -15
- package/utils/upsert-utils.js +2 -1
|
@@ -183,10 +183,11 @@ export class MetadataDiscovery {
|
|
|
183
183
|
}
|
|
184
184
|
discovered.forEach(meta => meta.sync(true));
|
|
185
185
|
this.#metadataProvider.combineCache();
|
|
186
|
+
const referencingProperties = this.indexReferencingProperties(filtered);
|
|
186
187
|
return discovered.map(meta => {
|
|
187
188
|
meta = this.#metadata.get(meta.class);
|
|
188
189
|
meta.sync(true);
|
|
189
|
-
this.findReferencingProperties(meta,
|
|
190
|
+
this.findReferencingProperties(meta, referencingProperties);
|
|
190
191
|
if (meta.inheritanceType === 'tpt') {
|
|
191
192
|
this.computeTPTOwnProps(meta);
|
|
192
193
|
}
|
|
@@ -217,18 +218,20 @@ export class MetadataDiscovery {
|
|
|
217
218
|
.replace(/Array<(.*)>/, '$1') // unwrap array
|
|
218
219
|
.replace(/\[]$/, '') // remove array suffix
|
|
219
220
|
.replace(/\((.*)\)/, '$1'); // unwrap union types
|
|
221
|
+
const discoveredNames = new Set(this.#discovered.map(m => m.className));
|
|
220
222
|
// Names can be ambiguous when a minifier mangles two classes to the same name,
|
|
221
223
|
// so class references also need to be checked by identity.
|
|
224
|
+
const discoveredClasses = new Set(this.#discovered.map(m => m.class));
|
|
222
225
|
const discoveredByIdentity = (target) => {
|
|
223
226
|
const cls = EntitySchema.is(target) ? target.meta.class : target;
|
|
224
|
-
return typeof cls !== 'function' ||
|
|
227
|
+
return typeof cls !== 'function' || discoveredClasses.has(cls);
|
|
225
228
|
};
|
|
226
229
|
const missing = [];
|
|
227
230
|
this.#discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
228
231
|
const indirect = (prop.kind === ReferenceKind.MANY_TO_MANY ? prop.pivotEntity : prop.through);
|
|
229
232
|
if (indirect) {
|
|
230
233
|
const target = typeof indirect === 'function' && !indirect.prototype ? indirect() : indirect;
|
|
231
|
-
if (!
|
|
234
|
+
if (!discoveredNames.has(Utils.className(target)) || !discoveredByIdentity(target)) {
|
|
232
235
|
missing.push(target);
|
|
233
236
|
}
|
|
234
237
|
}
|
|
@@ -236,7 +239,7 @@ export class MetadataDiscovery {
|
|
|
236
239
|
const target = typeof prop.entity === 'function' && !prop.entity.prototype ? prop.entity() : prop.type;
|
|
237
240
|
if (!unwrap(prop.type)
|
|
238
241
|
.split(/ ?\| ?/)
|
|
239
|
-
.every(type =>
|
|
242
|
+
.every(type => discoveredNames.has(type)) ||
|
|
240
243
|
!Utils.asArray(target).every(discoveredByIdentity)) {
|
|
241
244
|
missing.push(...Utils.asArray(target));
|
|
242
245
|
}
|
|
@@ -302,7 +305,8 @@ export class MetadataDiscovery {
|
|
|
302
305
|
if (validate) {
|
|
303
306
|
this.#validator.validateDiscovered(this.#discovered, this.#config.get('discovery'));
|
|
304
307
|
}
|
|
305
|
-
|
|
308
|
+
const foundNames = new Set(found.map(m => m.name));
|
|
309
|
+
return this.#discovered.filter(meta => foundNames.has(meta.className));
|
|
306
310
|
}
|
|
307
311
|
reset(entityName) {
|
|
308
312
|
const exists = this.#discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
|
|
@@ -463,7 +467,8 @@ export class MetadataDiscovery {
|
|
|
463
467
|
}
|
|
464
468
|
}
|
|
465
469
|
initManyToOneFieldName(prop, name) {
|
|
466
|
-
|
|
470
|
+
// STI property renaming runs before `initRelation` populates `targetMeta`
|
|
471
|
+
const meta2 = prop.targetMeta ?? this.#metadata.getByClassName(prop.type);
|
|
467
472
|
const ret = [];
|
|
468
473
|
// with `targetKey` on a composite PK target, derive the FK field name from that property
|
|
469
474
|
// instead of the PKs (simple PK targets keep the PK based naming for backwards compatibility)
|
|
@@ -480,8 +485,53 @@ export class MetadataDiscovery {
|
|
|
480
485
|
const meta2 = prop.targetMeta;
|
|
481
486
|
return meta2.primaryKeys.map(() => this.#namingStrategy.propertyToColumnName(name));
|
|
482
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Walks up the TPT hierarchy to the entity whose M:N declaration this property inherits. A child redeclaring
|
|
490
|
+
* the property with a different target, pivot table, join columns or fixed order keeps its own pivot table.
|
|
491
|
+
*/
|
|
492
|
+
getPivotDeclaringMeta(meta, prop) {
|
|
493
|
+
const keys = [
|
|
494
|
+
'type',
|
|
495
|
+
'pivotTable',
|
|
496
|
+
'pivotEntity',
|
|
497
|
+
'joinColumns',
|
|
498
|
+
'inverseJoinColumns',
|
|
499
|
+
'fixedOrder',
|
|
500
|
+
'fixedOrderColumn',
|
|
501
|
+
];
|
|
502
|
+
// Rails-style polymorphic owners already share an explicit pivot table, each under its own discriminator value
|
|
503
|
+
if (prop.polymorphic && !QueryHelper.isUnionTargetPolymorphic(prop)) {
|
|
504
|
+
return meta;
|
|
505
|
+
}
|
|
506
|
+
while (meta.tptParent) {
|
|
507
|
+
const parentProp = meta.tptParent.properties[prop.name];
|
|
508
|
+
if (parentProp?.kind !== ReferenceKind.MANY_TO_MANY) {
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
// resolve the parent first so explicit options are compared against its final mapping regardless of discovery order
|
|
512
|
+
this.initManyToManyFields(meta.tptParent, parentProp);
|
|
513
|
+
if (keys.some(key => prop[key] != null && !Utils.equals(prop[key], parentProp[key]))) {
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
meta = meta.tptParent;
|
|
517
|
+
}
|
|
518
|
+
return meta;
|
|
519
|
+
}
|
|
483
520
|
initManyToManyFields(meta, prop) {
|
|
484
521
|
const meta2 = prop.targetMeta;
|
|
522
|
+
// inverse sides derive their mapping from the owner via `mappedBy`
|
|
523
|
+
const declaringMeta = prop.mappedBy ? meta : this.getPivotDeclaringMeta(meta, prop);
|
|
524
|
+
if (declaringMeta !== meta) {
|
|
525
|
+
const prop2 = declaringMeta.properties[prop.name];
|
|
526
|
+
prop.pivotTable = prop2.pivotTable;
|
|
527
|
+
prop.pivotEntity = prop2.pivotEntity;
|
|
528
|
+
prop.fixedOrder = prop2.fixedOrder;
|
|
529
|
+
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
530
|
+
prop.joinColumns = prop2.joinColumns;
|
|
531
|
+
prop.inverseJoinColumns = prop2.inverseJoinColumns;
|
|
532
|
+
prop.referencedColumnNames = prop2.referencedColumnNames;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
485
535
|
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
|
|
486
536
|
const pivotMeta = this.#metadata.find(prop.pivotEntity);
|
|
487
537
|
const props = Object.values(pivotMeta?.properties ?? {});
|
|
@@ -523,12 +573,16 @@ export class MetadataDiscovery {
|
|
|
523
573
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
524
574
|
// Union-target polymorphic M:N: owner side is fixed (real FK), target side uses discriminator-derived names.
|
|
525
575
|
const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
|
|
576
|
+
// both sides of a TPT hierarchy would collide on the root name, so name them after the entities themselves
|
|
577
|
+
const sameTPTRoot = meta.inheritanceType === 'tpt' && meta.root.class === meta2.root.class;
|
|
578
|
+
const ownerName = sameTPTRoot ? meta.className : meta.root.className;
|
|
579
|
+
const targetName = sameTPTRoot ? meta2.className : meta2.root.className;
|
|
526
580
|
if (prop.polymorphic && prop.discriminator && !isUnionTargetMN) {
|
|
527
581
|
// Rails-style: owner side is polymorphic, uses discriminator base name (e.g. taggable_id instead of post_id)
|
|
528
582
|
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
|
|
529
583
|
}
|
|
530
584
|
else {
|
|
531
|
-
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(
|
|
585
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(ownerName, referencedColumnName, meta.compositePK));
|
|
532
586
|
}
|
|
533
587
|
if (isUnionTargetMN) {
|
|
534
588
|
// Target side uses discriminator base name (e.g. attachable_id — shared across Image/Video)
|
|
@@ -536,7 +590,7 @@ export class MetadataDiscovery {
|
|
|
536
590
|
prop.inverseJoinColumns ??= targetPkCols.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, targetPkCols.length > 1));
|
|
537
591
|
}
|
|
538
592
|
else {
|
|
539
|
-
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop,
|
|
593
|
+
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, targetName);
|
|
540
594
|
}
|
|
541
595
|
}
|
|
542
596
|
isExplicitTableName(meta) {
|
|
@@ -564,7 +618,10 @@ export class MetadataDiscovery {
|
|
|
564
618
|
}
|
|
565
619
|
Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
|
|
566
620
|
if (!prop.joinColumns) {
|
|
567
|
-
|
|
621
|
+
// must match the real FK columns, which ignore `targetKey` naming for simple PK targets
|
|
622
|
+
prop.joinColumns = prop.targetKey
|
|
623
|
+
? prop.fieldNames
|
|
624
|
+
: fieldNames.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
|
|
568
625
|
}
|
|
569
626
|
if (!prop.referencedColumnNames) {
|
|
570
627
|
prop.referencedColumnNames = fieldNames;
|
|
@@ -632,8 +689,13 @@ export class MetadataDiscovery {
|
|
|
632
689
|
return Object.values(meta.properties)
|
|
633
690
|
.filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
|
|
634
691
|
.map(prop => {
|
|
635
|
-
|
|
692
|
+
// an inherited M:N lives in the pivot table of the declaring TPT entity, whose table the pivot FK references
|
|
693
|
+
const declaringMeta = this.getPivotDeclaringMeta(meta, prop);
|
|
694
|
+
const ownerProp = declaringMeta.properties[prop.name];
|
|
695
|
+
const pivotMeta = this.definePivotTableEntity(declaringMeta, ownerProp);
|
|
636
696
|
prop.pivotEntity = pivotMeta.class;
|
|
697
|
+
prop.inversedBy ??= ownerProp.inversedBy;
|
|
698
|
+
prop.fixedOrderColumn = ownerProp.fixedOrderColumn;
|
|
637
699
|
if (prop.inversedBy) {
|
|
638
700
|
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
639
701
|
const targetRoot = prop.targetMeta.root;
|
|
@@ -646,14 +708,22 @@ export class MetadataDiscovery {
|
|
|
646
708
|
// - regular inverse (Tag.posts mappedBy Post.tags) — handled by inversedBy above
|
|
647
709
|
// - union-target inverse (Image.posts mappedBy Post.attachments) — on each polymorph target
|
|
648
710
|
// - merged inverse (Tag.owners mappedBy [Post,Video].tags) — union collection on the target
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
711
|
+
// inherited inverse copies on TPT descendants of the target need the pivot as well
|
|
712
|
+
const withTPTDescendants = (m) => [
|
|
713
|
+
m,
|
|
714
|
+
...(m.tptChildren ?? []).flatMap(withTPTDescendants),
|
|
715
|
+
];
|
|
716
|
+
const targets = QueryHelper.isUnionTargetPolymorphic(prop) ? prop.polymorphTargets : [prop.targetMeta];
|
|
717
|
+
const inverseCandidates = targets.flatMap(withTPTDescendants);
|
|
652
718
|
for (const targetMeta of inverseCandidates) {
|
|
653
719
|
for (const inverseProp of Object.values(targetMeta.properties)) {
|
|
654
720
|
if (inverseProp.kind === ReferenceKind.MANY_TO_MANY &&
|
|
655
721
|
inverseProp.mappedBy === prop.name &&
|
|
656
|
-
!inverseProp.pivotEntity
|
|
722
|
+
!inverseProp.pivotEntity &&
|
|
723
|
+
// a descendant can declare its own inverse of another owner's same-named M:N
|
|
724
|
+
targets.some(target => target.properties[inverseProp.name]?.mappedBy === prop.name) &&
|
|
725
|
+
// the target itself can carry an inherited inverse of a parent M:N it redeclares
|
|
726
|
+
(inverseProp.targetMeta?.properties[prop.name]?.pivotTable ?? prop.pivotTable) === prop.pivotTable) {
|
|
657
727
|
inverseProp.pivotEntity = pivotMeta.class;
|
|
658
728
|
inverseProp.pivotTable = pivotMeta.tableName;
|
|
659
729
|
}
|
|
@@ -664,14 +734,47 @@ export class MetadataDiscovery {
|
|
|
664
734
|
}
|
|
665
735
|
return [];
|
|
666
736
|
}
|
|
667
|
-
|
|
737
|
+
/**
|
|
738
|
+
* Groups the relations of all entities by target class name in one pass, so `findReferencingProperties()`
|
|
739
|
+
* does not go over the relations of every entity for each entity. Lists keep the order of `metadata` and `relations`.
|
|
740
|
+
*/
|
|
741
|
+
indexReferencingProperties(metadata) {
|
|
742
|
+
const index = { byType: new Map(), withTargetKey: new Map() };
|
|
743
|
+
const add = (map, key, value) => {
|
|
744
|
+
const list = map.get(key);
|
|
745
|
+
if (list) {
|
|
746
|
+
list.push(value);
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
map.set(key, [value]);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
668
752
|
for (const meta2 of metadata) {
|
|
669
753
|
for (const prop2 of meta2.relations) {
|
|
670
|
-
if (prop2.kind !== ReferenceKind.SCALAR
|
|
671
|
-
|
|
754
|
+
if (prop2.kind !== ReferenceKind.SCALAR) {
|
|
755
|
+
add(index.byType, prop2.type, [meta2, prop2]);
|
|
756
|
+
}
|
|
757
|
+
if (prop2.targetKey) {
|
|
758
|
+
const targets = new Set([prop2.type, ...(prop2.polymorphTargets?.map(t => t.className) ?? [])]);
|
|
759
|
+
for (const target of targets) {
|
|
760
|
+
add(index.withTargetKey, target, prop2);
|
|
761
|
+
}
|
|
672
762
|
}
|
|
673
763
|
}
|
|
674
764
|
}
|
|
765
|
+
return index;
|
|
766
|
+
}
|
|
767
|
+
findReferencingProperties(meta, index) {
|
|
768
|
+
for (const [meta2, prop2] of index.byType.get(meta.className) ?? []) {
|
|
769
|
+
meta.referencingProperties.push({ meta: meta2, prop: prop2 });
|
|
770
|
+
}
|
|
771
|
+
for (const prop2 of index.withTargetKey.get(meta.className) ?? []) {
|
|
772
|
+
const targetKey = prop2.targetKey;
|
|
773
|
+
if (!(meta.primaryKeys.length === 1 && meta.primaryKeys[0] === targetKey) &&
|
|
774
|
+
!meta.root.targetKeys?.includes(targetKey)) {
|
|
775
|
+
(meta.root.targetKeys ??= []).push(targetKey);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
675
778
|
}
|
|
676
779
|
initFactoryField(meta, prop) {
|
|
677
780
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
@@ -776,11 +879,14 @@ export class MetadataDiscovery {
|
|
|
776
879
|
else {
|
|
777
880
|
pivotMeta2.compositePK = true;
|
|
778
881
|
}
|
|
882
|
+
// STI entities share one table, so any m:n within the hierarchy is self-referencing, named after the root
|
|
883
|
+
const stiRoot = meta.root.inheritanceType === 'sti' ? meta.root : undefined;
|
|
884
|
+
const baseMeta = stiRoot ?? meta;
|
|
779
885
|
// handle self-referenced m:n with same default field names
|
|
780
|
-
if (meta.className === targetType &&
|
|
886
|
+
if ((meta.className === targetType || stiRoot === targetMeta.root) &&
|
|
781
887
|
prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
782
888
|
// use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
|
|
783
|
-
const baseName = this.isExplicitTableName(
|
|
889
|
+
const baseName = this.isExplicitTableName(baseMeta) ? baseMeta.tableName : baseMeta.className;
|
|
784
890
|
prop.joinColumns = prop.referencedColumnNames.map(name => this.#namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
|
|
785
891
|
prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.#namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
|
|
786
892
|
if (prop.inversedBy) {
|
|
@@ -788,13 +894,23 @@ export class MetadataDiscovery {
|
|
|
788
894
|
prop2.inverseJoinColumns = prop.joinColumns;
|
|
789
895
|
prop2.joinColumns = prop.inverseJoinColumns;
|
|
790
896
|
}
|
|
791
|
-
// propagate updated joinColumns to all
|
|
792
|
-
|
|
897
|
+
// propagate updated joinColumns to all entities that inherit this property or its inverse (STI, or TPT sharing the pivot)
|
|
898
|
+
const inherits = (m) => m.root === baseMeta || (m.inheritanceType === 'tpt' && m.root === meta.root);
|
|
899
|
+
// includes `meta` itself: an STI root carries the inverse copy of an M:N declared on a child
|
|
900
|
+
for (const childMeta of this.#discovered.filter(inherits)) {
|
|
793
901
|
const childProp = childMeta.properties[prop.name];
|
|
794
|
-
|
|
902
|
+
// a TPT subclass redeclaring its own pivot table keeps its own columns
|
|
903
|
+
const sharesPivot = childMeta.inheritanceType !== 'tpt' || childProp?.pivotTable === prop.pivotTable;
|
|
904
|
+
if (childProp && sharesPivot) {
|
|
795
905
|
childProp.joinColumns = prop.joinColumns;
|
|
796
906
|
childProp.inverseJoinColumns = prop.inverseJoinColumns;
|
|
797
907
|
}
|
|
908
|
+
const childProp2 = prop.inversedBy ? childMeta.properties[prop.inversedBy] : undefined;
|
|
909
|
+
// the inherited inverse can stay on this pivot even when the child redeclares the owner side
|
|
910
|
+
if (childProp2 && (childMeta.inheritanceType !== 'tpt' || childProp2.pivotTable === prop.pivotTable)) {
|
|
911
|
+
childProp2.inverseJoinColumns = prop.joinColumns;
|
|
912
|
+
childProp2.joinColumns = prop.inverseJoinColumns;
|
|
913
|
+
}
|
|
798
914
|
}
|
|
799
915
|
}
|
|
800
916
|
// Union-target polymorphic M:N: discriminator + target FK share the pivot across multiple target types
|
|
@@ -1272,16 +1388,37 @@ export class MetadataDiscovery {
|
|
|
1272
1388
|
// the prefix of the parent cannot be a boolean; it already passed here
|
|
1273
1389
|
const prefix = this.getPrefix(embeddedProp, parentProperty);
|
|
1274
1390
|
const glue = object ? '~' : '_';
|
|
1275
|
-
|
|
1276
|
-
|
|
1391
|
+
const props = Object.values(embeddable.properties);
|
|
1392
|
+
// variants can declare a same-named property of a different kind, the virtual entity lacks it when the variants are shared with another union
|
|
1393
|
+
for (const prop of embeddable.polymorphs?.flatMap(m => Object.values(m.properties)) ?? []) {
|
|
1394
|
+
const same = props.find(p => p.name === prop.name && Utils.isSameKind(p, prop));
|
|
1395
|
+
if (!same && !prop.embedded) {
|
|
1396
|
+
props.push({ ...prop }); // a copy, the variant's metadata is shared with its other owners
|
|
1397
|
+
}
|
|
1398
|
+
else if (same && same.kind !== ReferenceKind.SCALAR && same.type !== prop.type) {
|
|
1399
|
+
// a column shared by relations to different targets cannot have a FK constraint
|
|
1400
|
+
same.createForeignKeyConstraint = false;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
for (const prop of props) {
|
|
1404
|
+
const sameName = props.filter(p => p.name === prop.name);
|
|
1405
|
+
// same-kind variants of a conflicting property share one owner property, as they share the column
|
|
1406
|
+
const first = sameName.find(p => Utils.isSameKind(p, prop));
|
|
1407
|
+
const conflict = first !== sameName[0];
|
|
1408
|
+
const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) +
|
|
1409
|
+
prop.name +
|
|
1410
|
+
(conflict ? `_${props.indexOf(first)}` : '');
|
|
1277
1411
|
meta.properties[name] = Utils.copy(prop);
|
|
1278
1412
|
meta.properties[name].name = name;
|
|
1413
|
+
// STI column tracking between the variants does not apply to the owner's columns
|
|
1414
|
+
delete meta.properties[name].stiFieldNames;
|
|
1279
1415
|
meta.properties[name].embedded = [embeddedProp.name, prop.name];
|
|
1280
1416
|
meta.propertyOrder.set(name, (order += 0.01));
|
|
1281
|
-
embeddedProp.embeddedProps[prop.name] = meta.properties[name];
|
|
1417
|
+
embeddedProp.embeddedProps[conflict ? name : prop.name] = meta.properties[name];
|
|
1282
1418
|
meta.properties[name].persist ??= embeddedProp.persist;
|
|
1283
1419
|
const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
|
|
1284
|
-
|
|
1420
|
+
// each variant fills only its own column of a conflicting property
|
|
1421
|
+
if (embeddedProp.nullable || refInArray || sameName.some(p => !Utils.isSameKind(p, prop))) {
|
|
1285
1422
|
meta.properties[name].nullable = true;
|
|
1286
1423
|
}
|
|
1287
1424
|
// polymorphic relations derive their column names from the discriminator, so prefix it too
|
|
@@ -1452,7 +1589,15 @@ export class MetadataDiscovery {
|
|
|
1452
1589
|
}
|
|
1453
1590
|
}
|
|
1454
1591
|
rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
|
|
1455
|
-
|
|
1592
|
+
// subtypes that only differ in type share the column
|
|
1593
|
+
rootProp.stiFieldNames = Utils.unique([...rootProp.stiFieldNames, ...prop.fieldNames]);
|
|
1594
|
+
// a column shared by relations to different targets cannot have a FK constraint
|
|
1595
|
+
for (const p of Object.values(meta.root.properties)) {
|
|
1596
|
+
const shared = p.type !== newProp.type && compareArrays(p.fieldNames ?? [], newProp.fieldNames);
|
|
1597
|
+
if (shared && p.kind !== ReferenceKind.SCALAR && Utils.isSameKind(p, newProp)) {
|
|
1598
|
+
p.createForeignKeyConstraint = newProp.createForeignKeyConstraint = false;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1456
1601
|
newProp.nullable = true;
|
|
1457
1602
|
newProp.name = name;
|
|
1458
1603
|
newProp.hydrate = false;
|
|
@@ -1497,6 +1642,9 @@ export class MetadataDiscovery {
|
|
|
1497
1642
|
if (isInverseSideRelation && rootProp && rootProp.type !== prop.type) {
|
|
1498
1643
|
return;
|
|
1499
1644
|
}
|
|
1645
|
+
// a sibling matching the root property must not drop the column variants of already renamed siblings
|
|
1646
|
+
newProp.stiFieldNames ??= rootProp?.stiFieldNames;
|
|
1647
|
+
newProp.stiFieldNameMap ??= rootProp?.stiFieldNameMap;
|
|
1500
1648
|
meta.root.addProperty(newProp);
|
|
1501
1649
|
});
|
|
1502
1650
|
meta.tableName = meta.root.tableName;
|
|
@@ -1617,7 +1765,7 @@ export class MetadataDiscovery {
|
|
|
1617
1765
|
if (meta.inheritanceType !== 'tpt') {
|
|
1618
1766
|
return;
|
|
1619
1767
|
}
|
|
1620
|
-
const belongsToTable = (prop) => prop.persist !== false || prop.primary;
|
|
1768
|
+
const belongsToTable = (prop) => prop.persist !== false || prop.primary || !!prop.formula;
|
|
1621
1769
|
// Use meta.properties (object) since meta.props (array) may not be populated yet
|
|
1622
1770
|
const allProps = Object.values(meta.properties);
|
|
1623
1771
|
if (!meta.tptParent) {
|
|
@@ -1863,9 +2011,9 @@ export class MetadataDiscovery {
|
|
|
1863
2011
|
// guard preserves the legacy convention where JSON defaults are passed pre-stringified.
|
|
1864
2012
|
// Array defaults are converted unconditionally (e.g. `default: []` on a JSON-backed embedded array).
|
|
1865
2013
|
if (prop.customType && (Array.isArray(prop.default) || prop.customType.compareAsType() !== 'any')) {
|
|
1866
|
-
val = prop.customType.convertToDatabaseValue(prop.default, this.#platform);
|
|
2014
|
+
val = prop.customType.convertToDatabaseValue(prop.default, this.#platform, { mode: 'discovery' });
|
|
1867
2015
|
}
|
|
1868
|
-
prop.defaultRaw = typeof val === 'string' ?
|
|
2016
|
+
prop.defaultRaw = typeof val === 'string' ? this.#platform.quoteDefaultValue(val) : '' + val;
|
|
1869
2017
|
}
|
|
1870
2018
|
inferTypeFromDefault(prop) {
|
|
1871
2019
|
if ((prop.defaultRaw == null && prop.default == null) || prop.type !== 'any') {
|
|
@@ -9,6 +9,7 @@ export declare class MetadataStorage {
|
|
|
9
9
|
/** Returns the global metadata dictionary, or a specific entry by entity name and path (keyed by the class reference when `target` is provided). */
|
|
10
10
|
static getMetadata(): Dictionary<EntityMetadata>;
|
|
11
11
|
static getMetadata<T = any>(entity: string, path: string, target?: EntityCtor): EntityMetadata<T>;
|
|
12
|
+
private static hashPath;
|
|
12
13
|
/** Checks whether an entity with the given class name exists in the global metadata. */
|
|
13
14
|
static isKnownEntity(name: string): boolean;
|
|
14
15
|
/** Clears all entries from the global metadata registry. */
|
|
@@ -32,6 +33,13 @@ export declare class MetadataStorage {
|
|
|
32
33
|
getById<T>(id: number): EntityMetadata<T>;
|
|
33
34
|
/** Returns metadata by class name, optionally throwing if not found. */
|
|
34
35
|
getByClassName<T = any, V extends boolean = true>(className: string, validate?: V): V extends true ? EntityMetadata<T> : EntityMetadata<T> | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Returns the first registered metadata with the given table name, in registration order (entities sharing
|
|
38
|
+
* a table, like STI children, resolve to the one registered first). The index is built on first use, as table
|
|
39
|
+
* names are only final once discovery has finished.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
getByTableName<T = any>(tableName: string): EntityMetadata<T> | undefined;
|
|
35
43
|
/** Returns metadata by unique name, optionally throwing if not found. */
|
|
36
44
|
getByUniqueName<T = any, V extends boolean = true>(uniqueName: string, validate?: V): V extends true ? EntityMetadata<T> : EntityMetadata<T> | undefined;
|
|
37
45
|
private validate;
|
|
@@ -13,11 +13,17 @@ export class MetadataStorage {
|
|
|
13
13
|
static PATH_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.PATH_SYMBOL');
|
|
14
14
|
static META_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.META_SYMBOL');
|
|
15
15
|
static #metadata = getGlobalStorage('metadata');
|
|
16
|
+
/** Lives in the global storage next to the registry, so it stays in sync when several copies of this module share it (e.g. after `vi.resetModules()`). */
|
|
17
|
+
static #classNames = getGlobalStorage('metadata-class-names');
|
|
18
|
+
/** Hashes of entity file paths, decorators call `getMetadata()` with the same path for every decorated member. */
|
|
19
|
+
static #pathHashes = new Map();
|
|
16
20
|
#metadataMap = new Map();
|
|
17
21
|
#idMap;
|
|
18
22
|
#classNameMap;
|
|
19
23
|
#uniqueNameMap;
|
|
20
24
|
#ambiguousNames = new Set();
|
|
25
|
+
/** Built on first use by `getByTableName()`, dropped whenever an entity is registered or removed. */
|
|
26
|
+
#tableNameMap;
|
|
21
27
|
constructor(metadata = {}) {
|
|
22
28
|
this.#idMap = {};
|
|
23
29
|
this.#uniqueNameMap = {};
|
|
@@ -29,7 +35,7 @@ export class MetadataStorage {
|
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
static getMetadata(entity, path, target) {
|
|
32
|
-
const key = entity && path ? entity + '-' +
|
|
38
|
+
const key = entity && path ? entity + '-' + MetadataStorage.hashPath(path) : null;
|
|
33
39
|
// Key the registry by the class reference when available, so two classes minified
|
|
34
40
|
// to the same mangled name don't collide on the `className-path` key.
|
|
35
41
|
if (key && target) {
|
|
@@ -41,23 +47,36 @@ export class MetadataStorage {
|
|
|
41
47
|
}
|
|
42
48
|
// Keep the name-keyed entry in sync, the class-keyed metadata survives `MetadataStorage.clear()`.
|
|
43
49
|
MetadataStorage.#metadata[key] = target[MetadataStorage.META_SYMBOL];
|
|
50
|
+
MetadataStorage.#classNames.names?.add(target[MetadataStorage.META_SYMBOL].className);
|
|
44
51
|
return target[MetadataStorage.META_SYMBOL];
|
|
45
52
|
}
|
|
46
53
|
if (key && !MetadataStorage.#metadata[key]) {
|
|
47
|
-
|
|
54
|
+
const meta = new EntityMetadata({ className: entity, path });
|
|
55
|
+
MetadataStorage.#metadata[key] = meta;
|
|
56
|
+
MetadataStorage.#classNames.names?.add(meta.className);
|
|
48
57
|
}
|
|
49
58
|
if (key) {
|
|
50
59
|
return MetadataStorage.#metadata[key];
|
|
51
60
|
}
|
|
52
61
|
return MetadataStorage.#metadata;
|
|
53
62
|
}
|
|
63
|
+
static hashPath(path) {
|
|
64
|
+
let hash = MetadataStorage.#pathHashes.get(path);
|
|
65
|
+
if (hash === undefined) {
|
|
66
|
+
hash = Utils.hash(path);
|
|
67
|
+
MetadataStorage.#pathHashes.set(path, hash);
|
|
68
|
+
}
|
|
69
|
+
return hash;
|
|
70
|
+
}
|
|
54
71
|
/** Checks whether an entity with the given class name exists in the global metadata. */
|
|
55
72
|
static isKnownEntity(name) {
|
|
56
|
-
|
|
73
|
+
this.#classNames.names ??= new Set(Object.values(this.#metadata).map(meta => meta.className));
|
|
74
|
+
return this.#classNames.names.has(name);
|
|
57
75
|
}
|
|
58
76
|
/** Clears all entries from the global metadata registry. */
|
|
59
77
|
static clear() {
|
|
60
78
|
Object.keys(this.#metadata).forEach(k => delete this.#metadata[k]);
|
|
79
|
+
delete this.#classNames.names;
|
|
61
80
|
}
|
|
62
81
|
/** Returns the map of all registered entity metadata. */
|
|
63
82
|
getAll() {
|
|
@@ -102,6 +121,7 @@ export class MetadataStorage {
|
|
|
102
121
|
/** Registers metadata for the given entity. */
|
|
103
122
|
set(entityName, meta) {
|
|
104
123
|
this.#metadataMap.set(entityName, meta);
|
|
124
|
+
this.#tableNameMap = undefined;
|
|
105
125
|
this.#idMap[meta._id] = meta;
|
|
106
126
|
this.#uniqueNameMap[meta.uniqueName] = meta;
|
|
107
127
|
const className = Utils.className(entityName);
|
|
@@ -110,6 +130,12 @@ export class MetadataStorage {
|
|
|
110
130
|
if (existing && existing !== meta && existing.class !== meta.class) {
|
|
111
131
|
this.#ambiguousNames.add(className);
|
|
112
132
|
}
|
|
133
|
+
// colliding names are numbered in storage order, which follows the discovery config, not module evaluation
|
|
134
|
+
const sameName = this.#ambiguousNames.has(className)
|
|
135
|
+
? [...new Set(this.#metadataMap.values())].filter(m => m.className === meta.className)
|
|
136
|
+
: [meta];
|
|
137
|
+
const ordinal = sameName.indexOf(meta);
|
|
138
|
+
meta.compiledName = ordinal > 0 ? `${meta.className}#${ordinal}` : meta.className;
|
|
113
139
|
this.#classNameMap[className] = meta;
|
|
114
140
|
return meta;
|
|
115
141
|
}
|
|
@@ -118,6 +144,7 @@ export class MetadataStorage {
|
|
|
118
144
|
const meta = this.find(entityName);
|
|
119
145
|
if (meta) {
|
|
120
146
|
this.#metadataMap.delete(meta.class);
|
|
147
|
+
this.#tableNameMap = undefined;
|
|
121
148
|
delete this.#idMap[meta._id];
|
|
122
149
|
delete this.#uniqueNameMap[meta.uniqueName];
|
|
123
150
|
delete this.#classNameMap[meta.className];
|
|
@@ -145,6 +172,23 @@ export class MetadataStorage {
|
|
|
145
172
|
getByClassName(className, validate = true) {
|
|
146
173
|
return this.validate(this.#classNameMap[className], className, validate);
|
|
147
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Returns the first registered metadata with the given table name, in registration order (entities sharing
|
|
177
|
+
* a table, like STI children, resolve to the one registered first). The index is built on first use, as table
|
|
178
|
+
* names are only final once discovery has finished.
|
|
179
|
+
* @internal
|
|
180
|
+
*/
|
|
181
|
+
getByTableName(tableName) {
|
|
182
|
+
if (!this.#tableNameMap) {
|
|
183
|
+
this.#tableNameMap = new Map();
|
|
184
|
+
for (const meta of this.#metadataMap.values()) {
|
|
185
|
+
if (!this.#tableNameMap.has(meta.tableName)) {
|
|
186
|
+
this.#tableNameMap.set(meta.tableName, meta);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return this.#tableNameMap.get(tableName);
|
|
191
|
+
}
|
|
148
192
|
/** Returns metadata by unique name, optionally throwing if not found. */
|
|
149
193
|
getByUniqueName(uniqueName, validate = true) {
|
|
150
194
|
return this.validate(this.#uniqueNameMap[uniqueName], uniqueName, validate);
|
|
@@ -118,12 +118,13 @@ export class MetadataValidator {
|
|
|
118
118
|
return Utils.className(p);
|
|
119
119
|
};
|
|
120
120
|
const pivotProps = new Map();
|
|
121
|
+
const discoveredNames = new Set(discovered.map(m => m.className));
|
|
121
122
|
// check for not discovered entities
|
|
122
123
|
discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
123
124
|
if (prop.kind !== ReferenceKind.SCALAR &&
|
|
124
125
|
!unwrap(prop.type)
|
|
125
126
|
.split(/ ?\| ?/)
|
|
126
|
-
.every(type =>
|
|
127
|
+
.every(type => discoveredNames.has(type))) {
|
|
127
128
|
throw MetadataError.fromUnknownEntity(prop.type, `${meta.className}.${prop.name}`);
|
|
128
129
|
}
|
|
129
130
|
if (prop.pivotEntity) {
|
package/metadata/types.d.ts
CHANGED
|
@@ -176,6 +176,12 @@ export interface PropertyOptions<Owner> {
|
|
|
176
176
|
* Explicitly specify the auto increment of the primary key.
|
|
177
177
|
*/
|
|
178
178
|
autoincrement?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Start value and increment of an autoincrement column. (SQL only)
|
|
181
|
+
* Applied only when the column is created (and by `orm.schema.clear()`), never diffed by the schema generator.
|
|
182
|
+
* `incrementBy` is not supported on MySQL, MariaDB and SQLite.
|
|
183
|
+
*/
|
|
184
|
+
sequence?: SequenceOptions;
|
|
179
185
|
/**
|
|
180
186
|
* Add the property to the `returning` clause of inserts, updates and upserts, reloading the database value into the entity.
|
|
181
187
|
*/
|
|
@@ -587,6 +593,12 @@ export interface EnumOptions<T> extends PropertyOptions<T> {
|
|
|
587
593
|
/** for postgres, by default it uses text column with check constraint */
|
|
588
594
|
nativeEnumName?: string;
|
|
589
595
|
}
|
|
596
|
+
export interface SequenceOptions {
|
|
597
|
+
/** The first value the autoincrement column generates. */
|
|
598
|
+
startWith?: number;
|
|
599
|
+
/** The step between generated values. Not supported on MySQL, MariaDB and SQLite. */
|
|
600
|
+
incrementBy?: number;
|
|
601
|
+
}
|
|
590
602
|
export interface PrimaryKeyOptions<T> extends PropertyOptions<T> {
|
|
591
603
|
}
|
|
592
604
|
export interface SerializedPrimaryKeyOptions<T> extends PropertyOptions<T> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.3.0-dev.
|
|
3
|
+
"version": "7.3.0-dev.20",
|
|
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",
|
package/platforms/Platform.d.ts
CHANGED
|
@@ -204,6 +204,8 @@ export declare abstract class Platform {
|
|
|
204
204
|
supportsUnionWhere(): boolean;
|
|
205
205
|
/** Returns the SQL type declaration used for array storage. */
|
|
206
206
|
getArrayDeclarationSQL(): string;
|
|
207
|
+
/** Whether array columns support the `&&` (overlap) operator. */
|
|
208
|
+
supportsArrayOverlap(): boolean;
|
|
207
209
|
/** Serializes a string array into its database storage format. */
|
|
208
210
|
marshallArray(values: string[]): string;
|
|
209
211
|
/** Deserializes a database-stored array string back into a string array. */
|
|
@@ -274,6 +276,8 @@ export declare abstract class Platform {
|
|
|
274
276
|
}, quote?: string): string;
|
|
275
277
|
/** Quotes a literal value for safe embedding in SQL. */
|
|
276
278
|
quoteValue(value: any): string;
|
|
279
|
+
/** Quotes a string default value for DDL; dialects that escape with backslashes override this. */
|
|
280
|
+
quoteDefaultValue(value: string): string;
|
|
277
281
|
escape(value: any): string;
|
|
278
282
|
/** Replaces `?` placeholders in SQL with quoted parameter values. */
|
|
279
283
|
formatQuery(sql: string, params: readonly any[]): string;
|
|
@@ -325,6 +329,8 @@ export declare abstract class Platform {
|
|
|
325
329
|
supportsDownMigrations(): boolean;
|
|
326
330
|
/** Whether the platform supports deferred unique constraints. */
|
|
327
331
|
supportsDeferredUniqueConstraints(): boolean;
|
|
332
|
+
/** Whether autoincrement columns support a per-column increment (`sequence.incrementBy`). */
|
|
333
|
+
supportsAutoincrementIncrement(): boolean;
|
|
328
334
|
/** Whether the platform supports row level security (PostgreSQL). */
|
|
329
335
|
supportsRowLevelSecurity(): boolean;
|
|
330
336
|
/** Whether the driver can apply the session context on every pooled connection acquire (`sessionContext: 'connection'`). */
|
package/platforms/Platform.js
CHANGED
|
@@ -360,6 +360,10 @@ export class Platform {
|
|
|
360
360
|
getArrayDeclarationSQL() {
|
|
361
361
|
return 'text';
|
|
362
362
|
}
|
|
363
|
+
/** Whether array columns support the `&&` (overlap) operator. */
|
|
364
|
+
supportsArrayOverlap() {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
363
367
|
/** Serializes a string array into its database storage format. */
|
|
364
368
|
marshallArray(values) {
|
|
365
369
|
return values.map(v => (/[,",\\]/.test(v) ? JSON.stringify(v) : v)).join(',');
|
|
@@ -573,6 +577,10 @@ export class Platform {
|
|
|
573
577
|
quoteValue(value) {
|
|
574
578
|
return value;
|
|
575
579
|
}
|
|
580
|
+
/** Quotes a string default value for DDL; dialects that escape with backslashes override this. */
|
|
581
|
+
quoteDefaultValue(value) {
|
|
582
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
583
|
+
}
|
|
576
584
|
/* v8 ignore next */
|
|
577
585
|
escape(value) {
|
|
578
586
|
return value;
|
|
@@ -738,6 +746,10 @@ export class Platform {
|
|
|
738
746
|
supportsDeferredUniqueConstraints() {
|
|
739
747
|
return true;
|
|
740
748
|
}
|
|
749
|
+
/** Whether autoincrement columns support a per-column increment (`sequence.incrementBy`). */
|
|
750
|
+
supportsAutoincrementIncrement() {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
741
753
|
/** Whether the platform supports row level security (PostgreSQL). */
|
|
742
754
|
supportsRowLevelSecurity() {
|
|
743
755
|
return false;
|
|
@@ -758,6 +770,20 @@ export class Platform {
|
|
|
758
770
|
if (meta.partitionBy && !this.supportsPartitionedTables()) {
|
|
759
771
|
throw new MetadataError(`Entity ${meta.className} uses partitionBy, but ${this.constructor.name} does not support partitioned tables`);
|
|
760
772
|
}
|
|
773
|
+
for (const prop of Object.values(meta.properties)) {
|
|
774
|
+
if (!prop.sequence) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
if (!prop.autoincrement) {
|
|
778
|
+
throw MetadataError.sequenceWithoutAutoincrement(meta, prop);
|
|
779
|
+
}
|
|
780
|
+
if (typeof prop.generated === 'string' && /identity\s*\(/i.test(prop.generated)) {
|
|
781
|
+
throw MetadataError.sequenceWithIdentityOptions(meta, prop);
|
|
782
|
+
}
|
|
783
|
+
if (prop.sequence.incrementBy != null && !this.supportsAutoincrementIncrement()) {
|
|
784
|
+
throw MetadataError.sequenceIncrementNotSupported(meta, prop, this.constructor.name);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
761
787
|
const declaresRls = meta.policies.length > 0 || !!meta.rowLevelSecurity;
|
|
762
788
|
if (declaresRls && !this.supportsRowLevelSecurity()) {
|
|
763
789
|
throw MetadataError.rowLevelSecurityNotSupportedByDriver(meta);
|