@mikro-orm/sql 7.2.0-dev.2 → 7.2.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/AbstractSqlConnection.d.ts +30 -3
- package/AbstractSqlConnection.js +75 -15
- package/AbstractSqlDriver.d.ts +1 -8
- package/AbstractSqlDriver.js +124 -55
- package/AbstractSqlPlatform.d.ts +4 -2
- package/AbstractSqlPlatform.js +35 -1
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +5 -4
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +1 -1
- package/dialects/mysql/BaseMySqlPlatform.d.ts +2 -0
- package/dialects/mysql/BaseMySqlPlatform.js +4 -0
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -0
- package/dialects/mysql/MySqlSchemaHelper.js +4 -1
- package/dialects/oracledb/OracleNativeQueryBuilder.js +1 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +3 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.js +35 -2
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +8 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +22 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +181 -4
- package/dialects/sqlite/BaseSqliteConnection.d.ts +3 -0
- package/dialects/sqlite/BaseSqliteConnection.js +15 -5
- package/dialects/sqlite/SqlitePlatform.d.ts +2 -0
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +2 -2
- package/package.json +4 -4
- package/plugin/transformer.d.ts +7 -1
- package/plugin/transformer.js +60 -1
- package/query/CriteriaNodeFactory.js +4 -0
- package/query/NativeQueryBuilder.js +1 -1
- package/query/ObjectCriteriaNode.d.ts +1 -0
- package/query/ObjectCriteriaNode.js +30 -5
- package/query/QueryBuilder.d.ts +40 -7
- package/query/QueryBuilder.js +181 -37
- package/query/QueryBuilderHelper.d.ts +5 -0
- package/query/QueryBuilderHelper.js +39 -9
- package/schema/DatabaseSchema.d.ts +4 -0
- package/schema/DatabaseSchema.js +107 -1
- package/schema/DatabaseTable.d.ts +15 -1
- package/schema/DatabaseTable.js +113 -19
- package/schema/SchemaComparator.d.ts +3 -0
- package/schema/SchemaComparator.js +123 -10
- package/schema/SchemaHelper.d.ts +26 -1
- package/schema/SchemaHelper.js +67 -9
- package/schema/SqlSchemaGenerator.d.ts +4 -0
- package/schema/SqlSchemaGenerator.js +59 -22
- package/typings.d.ts +20 -2
package/query/QueryBuilder.js
CHANGED
|
@@ -397,7 +397,10 @@ export class QueryBuilder {
|
|
|
397
397
|
* @internal
|
|
398
398
|
*/
|
|
399
399
|
scheduleFilterCheck(path) {
|
|
400
|
-
|
|
400
|
+
// deduplicate so filters forming a relation cycle cannot reschedule an already visited path forever
|
|
401
|
+
if (!this.#state.autoJoinedPaths.includes(path)) {
|
|
402
|
+
this.#state.autoJoinedPaths.push(path);
|
|
403
|
+
}
|
|
401
404
|
}
|
|
402
405
|
/**
|
|
403
406
|
* @internal
|
|
@@ -433,6 +436,7 @@ export class QueryBuilder {
|
|
|
433
436
|
else {
|
|
434
437
|
join.cond = { ...cond };
|
|
435
438
|
}
|
|
439
|
+
this.nestReferencedJoins(join);
|
|
436
440
|
// For polymorphic LEFT JOIN filters, add a WHERE condition to enforce the filter
|
|
437
441
|
// only for rows matching this target's discriminator value. This ensures rows pointing
|
|
438
442
|
// to other polymorphic targets are not excluded.
|
|
@@ -456,6 +460,48 @@ export class QueryBuilder {
|
|
|
456
460
|
}
|
|
457
461
|
}
|
|
458
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* The `on` clause of `condJoin` — its explicit join condition or a filter condition merged into
|
|
465
|
+
* it — can reference the alias of any join in its subtree, both auto-joins created while
|
|
466
|
+
* processing the condition and pre-existing joined paths, all of which render after `condJoin`
|
|
467
|
+
* and would be forward alias references (issues #7681, #8090, #8099). When that happens, fold the
|
|
468
|
+
* subtree into `condJoin`, so it renders as a single parenthesized join group and every alias
|
|
469
|
+
* shares the scope of the outer `on` clause.
|
|
470
|
+
*/
|
|
471
|
+
nestReferencedJoins(condJoin) {
|
|
472
|
+
// m:n pivot joins might not have the target join entry created
|
|
473
|
+
if (!condJoin) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const subtree = this.getJoinSubtree(condJoin);
|
|
477
|
+
if (!subtree.some(j => this.condReferencesAlias(condJoin.cond, j.alias))) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
for (const j of subtree) {
|
|
481
|
+
const parent = j.ownerAlias === condJoin.alias ? condJoin : subtree.find(p => p.alias === j.ownerAlias);
|
|
482
|
+
if (!parent.nested?.has(j)) {
|
|
483
|
+
const nested = (parent.nested ??= new Set());
|
|
484
|
+
j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
485
|
+
nested.add(j);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
getJoinSubtree(join) {
|
|
490
|
+
const children = Object.values(this.#state.joins).filter(j => j !== join && j.ownerAlias === join.alias);
|
|
491
|
+
return children.flatMap(j => [j, ...this.getJoinSubtree(j)]);
|
|
492
|
+
}
|
|
493
|
+
condReferencesAlias(cond, alias) {
|
|
494
|
+
if (Array.isArray(cond)) {
|
|
495
|
+
return cond.some(c => this.condReferencesAlias(c, alias));
|
|
496
|
+
}
|
|
497
|
+
if (Utils.isPlainObject(cond)) {
|
|
498
|
+
return Object.entries(cond).some(([key, value]) => {
|
|
499
|
+
const [keyAlias, field] = this.helper.splitField(key);
|
|
500
|
+
return this.helper.getTPTAliasForProperty(field, keyAlias) === alias || this.condReferencesAlias(value, alias);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
459
505
|
withSubQuery(subQuery, alias) {
|
|
460
506
|
this.ensureNotFinalized();
|
|
461
507
|
if (isRaw(subQuery)) {
|
|
@@ -861,6 +907,8 @@ export class QueryBuilder {
|
|
|
861
907
|
if (this.type === QueryType.INSERT) {
|
|
862
908
|
const returningProps = meta.hydrateProps
|
|
863
909
|
.filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
910
|
+
// a TPT table can only return its own columns
|
|
911
|
+
.filter(prop => meta.inheritanceType !== 'tpt' || prop.primary || meta.ownProps.some(p => p.name === prop.name))
|
|
864
912
|
.filter(prop => !data || !(prop.name in data));
|
|
865
913
|
if (returningProps.length > 0) {
|
|
866
914
|
qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
@@ -950,12 +998,13 @@ export class QueryBuilder {
|
|
|
950
998
|
}
|
|
951
999
|
if (!join && options?.ignoreBranching) {
|
|
952
1000
|
join = joins.find(j => {
|
|
953
|
-
return j.path?.replace(/\[\d+]/g, '') === path.replace(/\[\d+]/g, '');
|
|
1001
|
+
return j.path?.replace(/\[\d+]/g, '') === path.replace(/\[\d+]/g, '') && !this.branchesConflict(j.path, path);
|
|
954
1002
|
});
|
|
955
1003
|
}
|
|
956
1004
|
if (!join && options?.matchPopulateJoins && options?.ignoreBranching) {
|
|
957
1005
|
join = joins.find(j => {
|
|
958
|
-
return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '')
|
|
1006
|
+
return (j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '') &&
|
|
1007
|
+
!this.branchesConflict(j.path, path));
|
|
959
1008
|
});
|
|
960
1009
|
}
|
|
961
1010
|
if (!join && options?.matchPopulateJoins) {
|
|
@@ -965,6 +1014,12 @@ export class QueryBuilder {
|
|
|
965
1014
|
}
|
|
966
1015
|
return join;
|
|
967
1016
|
}
|
|
1017
|
+
/** Branch-insensitive path matching still must not cross branches — segments with different explicit branch markers (e.g. `Mobile[0]` vs `Mobile[1]`) belong to sibling joins. */
|
|
1018
|
+
branchesConflict(path1, path2) {
|
|
1019
|
+
const markers = (path) => path.split('.').map(segment => /\[(\d+)]/.exec(segment)?.[1]);
|
|
1020
|
+
const markers2 = markers(path2);
|
|
1021
|
+
return markers(path1).some((m, idx) => m != null && markers2[idx] != null && m !== markers2[idx]);
|
|
1022
|
+
}
|
|
968
1023
|
/**
|
|
969
1024
|
* @internal
|
|
970
1025
|
*/
|
|
@@ -1010,17 +1065,26 @@ export class QueryBuilder {
|
|
|
1010
1065
|
this.limit(1);
|
|
1011
1066
|
}
|
|
1012
1067
|
const query = this.toQuery();
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1068
|
+
const cacheKey = ['qb.execute', query.sql, query.params, method];
|
|
1069
|
+
// session context (row level security) scopes cached rows per tenant/role, avoiding cross-context serves
|
|
1070
|
+
const qbSessionContext = this.em?.getSessionContext();
|
|
1071
|
+
if (qbSessionContext) {
|
|
1072
|
+
cacheKey.push(qbSessionContext);
|
|
1073
|
+
}
|
|
1074
|
+
const cached = await this.em?.tryCache(this.mainAlias.entityName, this.#state.cache, cacheKey);
|
|
1019
1075
|
if (cached?.data !== undefined) {
|
|
1020
1076
|
return cached.data;
|
|
1021
1077
|
}
|
|
1022
1078
|
const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
|
|
1023
|
-
const
|
|
1079
|
+
const conn = this.getConnection();
|
|
1080
|
+
// outside a transaction, wrap in a short implicit one so RLS `set local` session context applies (no-op when unset)
|
|
1081
|
+
const sessionContext = this.context ? undefined : this.em?.getTransactionSessionContext();
|
|
1082
|
+
const res = await (sessionContext
|
|
1083
|
+
? conn.transactional(trx => conn.execute(query.sql, query.params, method, trx, loggerContext), {
|
|
1084
|
+
sessionContext,
|
|
1085
|
+
loggerContext,
|
|
1086
|
+
})
|
|
1087
|
+
: conn.execute(query.sql, query.params, method, this.context, loggerContext));
|
|
1024
1088
|
const meta = this.mainAlias.meta;
|
|
1025
1089
|
if (!options.mapResults || !meta) {
|
|
1026
1090
|
await this.em?.storeCache(this.#state.cache, cached, res);
|
|
@@ -1070,6 +1134,11 @@ export class QueryBuilder {
|
|
|
1070
1134
|
* ```
|
|
1071
1135
|
*/
|
|
1072
1136
|
async *stream(options) {
|
|
1137
|
+
// mirror EntityManager.stream — a stream can't open the implicit session-context transaction, so under the
|
|
1138
|
+
// 'transaction' strategy fail closed instead of silently running the cursor without the staged context
|
|
1139
|
+
if (!this.context && this.em?.getTransactionSessionContext()) {
|
|
1140
|
+
throw ValidationError.sessionContextStreamRequiresTransaction();
|
|
1141
|
+
}
|
|
1073
1142
|
options ??= {};
|
|
1074
1143
|
options.mergeResults ??= true;
|
|
1075
1144
|
options.mapResults ??= true;
|
|
@@ -1104,7 +1173,9 @@ export class QueryBuilder {
|
|
|
1104
1173
|
}
|
|
1105
1174
|
if (stack.length > 0) {
|
|
1106
1175
|
const merged = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps);
|
|
1107
|
-
|
|
1176
|
+
for (const row of merged) {
|
|
1177
|
+
yield this.mapResult(row, options.mapResults);
|
|
1178
|
+
}
|
|
1108
1179
|
}
|
|
1109
1180
|
}
|
|
1110
1181
|
/**
|
|
@@ -1368,19 +1439,19 @@ export class QueryBuilder {
|
|
|
1368
1439
|
prop.targetMeta = field.mainAlias.meta;
|
|
1369
1440
|
field = field.getNativeQuery();
|
|
1370
1441
|
}
|
|
1371
|
-
if (isRaw(field)) {
|
|
1372
|
-
field = this.platform.formatQuery(field.sql, field.params);
|
|
1373
|
-
}
|
|
1374
1442
|
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1443
|
+
const join = { prop, alias, type, cond, schema, ownerAlias: this.alias };
|
|
1444
|
+
// `sql.ref('...')` is a bare table/CTE reference, join it by name instead of wrapping it as a sub-query
|
|
1445
|
+
if (isRaw(field) && field.sql === '??' && field.params.length === 1) {
|
|
1446
|
+
join.table = String(field.params[0]);
|
|
1447
|
+
}
|
|
1448
|
+
else {
|
|
1449
|
+
if (isRaw(field)) {
|
|
1450
|
+
field = this.platform.formatQuery(field.sql, field.params);
|
|
1451
|
+
}
|
|
1452
|
+
join.subquery = field.toString();
|
|
1453
|
+
}
|
|
1454
|
+
this.#state.joins[key] = join;
|
|
1384
1455
|
return { prop, key };
|
|
1385
1456
|
}
|
|
1386
1457
|
if (!subquery && type.includes('lateral')) {
|
|
@@ -1412,7 +1483,6 @@ export class QueryBuilder {
|
|
|
1412
1483
|
aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
|
|
1413
1484
|
});
|
|
1414
1485
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
|
|
1415
|
-
const joinCountBefore = Object.keys(this.#state.joins).length;
|
|
1416
1486
|
cond = criteriaNode.process(this, { ignoreBranching: true, alias });
|
|
1417
1487
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1418
1488
|
path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
|
|
@@ -1442,22 +1512,31 @@ export class QueryBuilder {
|
|
|
1442
1512
|
this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
|
|
1443
1513
|
this.#state.joins[aliasedName].path ??= path;
|
|
1444
1514
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
// fold them into the new join so both aliases share scope in the outer ON clause (issue #7681)
|
|
1448
|
-
const condJoin = this.#state.joins[aliasedName];
|
|
1449
|
-
const joinKeys = Object.keys(this.#state.joins);
|
|
1450
|
-
for (let i = joinCountBefore; i < joinKeys.length; i++) {
|
|
1451
|
-
const j = this.#state.joins[joinKeys[i]];
|
|
1452
|
-
if (j === condJoin || j.ownerAlias !== alias) {
|
|
1453
|
-
continue;
|
|
1454
|
-
}
|
|
1455
|
-
const nested = (condJoin.nested ??= new Set());
|
|
1456
|
-
j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
1457
|
-
nested.add(j);
|
|
1515
|
+
if (prop.targetMeta.inheritanceType === 'tpt' && prop.targetMeta.tptParent) {
|
|
1516
|
+
this.addTPTParentJoins(prop.targetMeta, alias, path);
|
|
1458
1517
|
}
|
|
1518
|
+
this.nestReferencedJoins(this.#state.joins[aliasedName]);
|
|
1459
1519
|
return { prop, key: aliasedName };
|
|
1460
1520
|
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Walks the TPT inheritance chain of `leafMeta` and INNER JOINs each parent table.
|
|
1523
|
+
* Registers the parent aliases in `state.tptAlias` so column resolution finds them
|
|
1524
|
+
* when conditions reference parent-table columns.
|
|
1525
|
+
* @internal
|
|
1526
|
+
*/
|
|
1527
|
+
addTPTParentJoins(leafMeta, leafAlias, basePath) {
|
|
1528
|
+
let childAlias = leafAlias;
|
|
1529
|
+
let childMeta = leafMeta;
|
|
1530
|
+
while (childMeta.tptParent) {
|
|
1531
|
+
const parentMeta = childMeta.tptParent;
|
|
1532
|
+
const parentAlias = this.getNextAlias(parentMeta.className);
|
|
1533
|
+
this.createAlias(parentMeta.class, parentAlias);
|
|
1534
|
+
this.#state.tptAlias[`${leafAlias}:${parentMeta.className}`] = parentAlias;
|
|
1535
|
+
this.addPropertyJoin(childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `${basePath}.[tpt]${childMeta.className}`);
|
|
1536
|
+
childAlias = parentAlias;
|
|
1537
|
+
childMeta = parentMeta;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1461
1540
|
prepareFields(fields, type = 'where', schema) {
|
|
1462
1541
|
const ret = [];
|
|
1463
1542
|
const getFieldName = (name, customAlias) => {
|
|
@@ -2010,6 +2089,13 @@ export class QueryBuilder {
|
|
|
2010
2089
|
for (const k of Object.keys(cond)) {
|
|
2011
2090
|
if (Utils.isOperator(k)) {
|
|
2012
2091
|
if (Array.isArray(cond[k])) {
|
|
2092
|
+
if (k === '$or' && !this.canDistributeOrBranches(cond[k], joins)) {
|
|
2093
|
+
// entity filters have no other sink, so their `$or` is kept intact on the outermost targeted join instead
|
|
2094
|
+
if (filter) {
|
|
2095
|
+
this.mergeFilterOrCondition(cond[k], joins);
|
|
2096
|
+
}
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2013
2099
|
cond[k].forEach((c) => this.mergeOnConditions(joins, c, filter, k));
|
|
2014
2100
|
}
|
|
2015
2101
|
/* v8 ignore next */
|
|
@@ -2040,8 +2126,66 @@ export class QueryBuilder {
|
|
|
2040
2126
|
else {
|
|
2041
2127
|
join.cond = { ...join.cond, [k]: cond[k] };
|
|
2042
2128
|
}
|
|
2129
|
+
this.nestReferencedJoins(join);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* `$or` branches can be moved to a join's `on` clause only when they all target that same join
|
|
2135
|
+
* and stay flat — a partial `$or` in the `on` clause would drop rows matching a sibling branch,
|
|
2136
|
+
* and nested operators cannot be preserved inside a distributed disjunction, as `mergeOnConditions`
|
|
2137
|
+
* would flatten them into `and` conjuncts.
|
|
2138
|
+
*/
|
|
2139
|
+
canDistributeOrBranches(branches, joins) {
|
|
2140
|
+
const aliases = this.getOrBranchAliases(branches);
|
|
2141
|
+
const targeted = joins.filter(j => aliases.has(j.alias));
|
|
2142
|
+
const flat = branches.every(branch => Object.keys(branch).every(k => !Utils.isOperator(k)));
|
|
2143
|
+
return aliases.size === 1 && targeted.length === 1 && flat;
|
|
2144
|
+
}
|
|
2145
|
+
getOrBranchAliases(branches) {
|
|
2146
|
+
const aliases = new Set();
|
|
2147
|
+
const collectAliases = (cond) => {
|
|
2148
|
+
for (const k of Object.keys(cond)) {
|
|
2149
|
+
if (Utils.isOperator(k)) {
|
|
2150
|
+
Utils.asArray(cond[k]).forEach((c) => collectAliases(c));
|
|
2151
|
+
}
|
|
2152
|
+
else {
|
|
2153
|
+
aliases.add(this.helper.splitField(k)[0]);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
};
|
|
2157
|
+
branches.forEach(collectAliases);
|
|
2158
|
+
return aliases;
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* An entity filter's `$or` that cannot be distributed is applied intact to the `on` clause of the
|
|
2162
|
+
* outermost join common to all targeted joins, nesting the targeted joins under it so the clause
|
|
2163
|
+
* can reference their aliases.
|
|
2164
|
+
*/
|
|
2165
|
+
mergeFilterOrCondition(branches, joins) {
|
|
2166
|
+
const aliases = this.getOrBranchAliases(branches);
|
|
2167
|
+
const chainOf = (join) => {
|
|
2168
|
+
const parent = joins.find(j => j.alias === join.ownerAlias);
|
|
2169
|
+
return parent ? [join, ...chainOf(parent)] : [join];
|
|
2170
|
+
};
|
|
2171
|
+
// chains go from each targeted join up to its root, so the first join present in all of them is the outermost common one
|
|
2172
|
+
const chains = joins.filter(j => aliases.has(j.alias)).map(chainOf);
|
|
2173
|
+
const anchor = chains[0]?.find(a => chains.every(chain => chain.includes(a)));
|
|
2174
|
+
/* v8 ignore next 3 */
|
|
2175
|
+
if (!anchor) {
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
for (const chain of chains) {
|
|
2179
|
+
// nest the chain below the anchor, so the anchor's `on` clause can reference the nested aliases
|
|
2180
|
+
for (let i = 0; chain[i] !== anchor; i++) {
|
|
2181
|
+
const nested = (chain[i + 1].nested ??= new Set());
|
|
2182
|
+
if (!nested.has(chain[i])) {
|
|
2183
|
+
chain[i].type = chain[i].type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
|
|
2184
|
+
nested.add(chain[i]);
|
|
2185
|
+
}
|
|
2043
2186
|
}
|
|
2044
2187
|
}
|
|
2188
|
+
anchor.cond = anchor.cond.$or ? { $and: [anchor.cond, { $or: branches }] } : { ...anchor.cond, $or: branches };
|
|
2045
2189
|
}
|
|
2046
2190
|
/**
|
|
2047
2191
|
* When adding an inner join on a left joined relation, we need to nest them,
|
|
@@ -14,6 +14,11 @@ export declare class QueryBuilderHelper {
|
|
|
14
14
|
* Returns the main alias if not a TPT property or if the property belongs to the main entity.
|
|
15
15
|
*/
|
|
16
16
|
getTPTAliasForProperty(propName: string, defaultAlias: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Replaces `ALIAS_REPLACEMENT` placeholders, resolving column-qualified ones via
|
|
19
|
+
* `getTPTAliasForProperty()` so TPT inherited columns map to their owning table.
|
|
20
|
+
*/
|
|
21
|
+
replaceAliases(sql: string, alias?: string): string;
|
|
17
22
|
mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType): string;
|
|
18
23
|
mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType, value?: any, alias?: string | null, schema?: string): string;
|
|
19
24
|
processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, JsonType, inspect, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, Raw, QueryHelper, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
|
|
2
2
|
import { EMBEDDABLE_ARRAY_OPS, JoinType, QueryType } from './enums.js';
|
|
3
|
+
/** Captures the column name that follows the alias placeholder. */
|
|
4
|
+
const QUALIFYING_ALIAS_RE = new RegExp(ALIAS_REPLACEMENT_RE + '(?=["\'`\\]]*\\.["\'`\\[]?([\\w$]+))', 'g');
|
|
3
5
|
/**
|
|
4
6
|
* @internal
|
|
5
7
|
*/
|
|
@@ -49,6 +51,15 @@ export class QueryBuilderHelper {
|
|
|
49
51
|
// Property not found in hierarchy, return default alias
|
|
50
52
|
return defaultAlias;
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Replaces `ALIAS_REPLACEMENT` placeholders, resolving column-qualified ones via
|
|
56
|
+
* `getTPTAliasForProperty()` so TPT inherited columns map to their owning table.
|
|
57
|
+
*/
|
|
58
|
+
replaceAliases(sql, alias = this.#alias) {
|
|
59
|
+
return sql
|
|
60
|
+
.replace(QUALIFYING_ALIAS_RE, (_, column) => this.getTPTAliasForProperty(column, alias))
|
|
61
|
+
.replaceAll(ALIAS_REPLACEMENT, alias);
|
|
62
|
+
}
|
|
52
63
|
mapper(field, type = QueryType.SELECT, value, alias, schema) {
|
|
53
64
|
if (isRaw(field)) {
|
|
54
65
|
return raw(field.sql, field.params);
|
|
@@ -97,7 +108,6 @@ export class QueryBuilderHelper {
|
|
|
97
108
|
// Only apply TPT resolution when `a` is an actual table alias (in aliasMap),
|
|
98
109
|
// not when it's an embedded property name like 'profile1.identity.links'
|
|
99
110
|
const isTableAlias = !!this.#aliasMap[a];
|
|
100
|
-
const baseAlias = isTableAlias ? a : this.#alias;
|
|
101
111
|
const resolvedAlias = isTableAlias ? this.getTPTAliasForProperty(prop?.name ?? f, a) : this.#alias;
|
|
102
112
|
const aliasPrefix = isTableNameAliasRequired ? resolvedAlias + '.' : '';
|
|
103
113
|
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
@@ -249,7 +259,13 @@ export class QueryBuilderHelper {
|
|
|
249
259
|
}[join.type] ?? join.type;
|
|
250
260
|
const conditions = [];
|
|
251
261
|
const params = [];
|
|
252
|
-
|
|
262
|
+
if (join.prop.name === '__subquery__') {
|
|
263
|
+
// bare table/CTE references (`sql.ref()` joins) keep their literal name, only an explicit schema applies
|
|
264
|
+
schema = join.schema === '*' ? undefined : join.schema;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
schema = join.schema === '*' ? schema : (join.schema ?? schemaOverride);
|
|
268
|
+
}
|
|
253
269
|
if (schema && schema !== this.#platform.getDefaultSchemaName()) {
|
|
254
270
|
table = `${schema}.${table}`;
|
|
255
271
|
}
|
|
@@ -414,7 +430,8 @@ export class QueryBuilderHelper {
|
|
|
414
430
|
}
|
|
415
431
|
if (k === '$not') {
|
|
416
432
|
const res = this._appendQueryCondition(type, cond[k]);
|
|
417
|
-
|
|
433
|
+
// negating a vacuously true condition (e.g. an empty `$and`) matches nothing
|
|
434
|
+
parts.push(res.sql ? `not (${res.sql})` : '1 = 0');
|
|
418
435
|
res.params.forEach(p => params.push(p));
|
|
419
436
|
continue;
|
|
420
437
|
}
|
|
@@ -464,7 +481,7 @@ export class QueryBuilderHelper {
|
|
|
464
481
|
const op = cond[key] === null ? 'is' : '=';
|
|
465
482
|
if (Raw.isKnownFragmentSymbol(key)) {
|
|
466
483
|
const raw = Raw.getKnownFragment(key);
|
|
467
|
-
const sql = raw.sql
|
|
484
|
+
const sql = this.replaceAliases(raw.sql);
|
|
468
485
|
const value = Utils.asArray(cond[key]);
|
|
469
486
|
params.push(...raw.params);
|
|
470
487
|
if (value.length > 0) {
|
|
@@ -516,6 +533,11 @@ export class QueryBuilderHelper {
|
|
|
516
533
|
const replacement = this.getOperatorReplacement(op, value);
|
|
517
534
|
const rawField = Raw.isKnownFragmentSymbol(key);
|
|
518
535
|
const fields = rawField ? [key] : Utils.splitPrimaryKeys(key);
|
|
536
|
+
// a raw key's arity is opaque, so the row-value form is detected from the payload instead
|
|
537
|
+
const rowValues = rawField &&
|
|
538
|
+
['$in', '$nin'].includes(op) &&
|
|
539
|
+
Array.isArray(value[op]) &&
|
|
540
|
+
value[op].every((v) => Array.isArray(v));
|
|
519
541
|
if (fields.length > 1 && Array.isArray(value[op])) {
|
|
520
542
|
const singleTuple = !value[op].every((v) => Array.isArray(v));
|
|
521
543
|
if (!this.#platform.allowsComparingTuples()) {
|
|
@@ -575,7 +597,7 @@ export class QueryBuilderHelper {
|
|
|
575
597
|
if (opValueIsRaw || typeof opValue?.toRaw === 'function') {
|
|
576
598
|
const query = opValueIsRaw ? opValue : opValue.toRaw();
|
|
577
599
|
const mappedKey = this.mapper(key, type, query, null);
|
|
578
|
-
let sql = query.sql
|
|
600
|
+
let sql = this.replaceAliases(query.sql);
|
|
579
601
|
if (['$in', '$nin'].includes(op)) {
|
|
580
602
|
sql = `(${sql})`;
|
|
581
603
|
}
|
|
@@ -584,15 +606,15 @@ export class QueryBuilderHelper {
|
|
|
584
606
|
}
|
|
585
607
|
else {
|
|
586
608
|
const mappedKey = this.mapper(key, type, opValue, null);
|
|
587
|
-
const val = this.getValueReplacement(fields, opValue, params, op, prop);
|
|
609
|
+
const val = this.getValueReplacement(fields, opValue, params, op, prop, rowValues);
|
|
588
610
|
parts.push(`${this.#platform.quoteIdentifier(mappedKey)} ${replacement} ${val}`);
|
|
589
611
|
}
|
|
590
612
|
}
|
|
591
613
|
return { sql: parts.join(' and '), params };
|
|
592
614
|
}
|
|
593
|
-
getValueReplacement(fields, value, params, key, prop) {
|
|
615
|
+
getValueReplacement(fields, value, params, key, prop, rowValues = false) {
|
|
594
616
|
if (Array.isArray(value)) {
|
|
595
|
-
if (fields.length > 1) {
|
|
617
|
+
if (fields.length > 1 || rowValues) {
|
|
596
618
|
const tmp = [];
|
|
597
619
|
for (const field of value) {
|
|
598
620
|
tmp.push(`(${field.map(() => '?').join(', ')})`);
|
|
@@ -621,6 +643,10 @@ export class QueryBuilderHelper {
|
|
|
621
643
|
return '?';
|
|
622
644
|
}
|
|
623
645
|
getOperatorReplacement(op, value) {
|
|
646
|
+
// collection properties expand `$all` into `$some` sub-queries in `ObjectCriteriaNode`, nothing else has a SQL equivalent
|
|
647
|
+
if (op === '$all') {
|
|
648
|
+
throw new Error('The `$all` operator is supported only on collection properties in SQL drivers, use `$contains` for array columns instead.');
|
|
649
|
+
}
|
|
624
650
|
let replacement = QueryOperator[op];
|
|
625
651
|
if (op === '$exists') {
|
|
626
652
|
replacement = value[op] ? 'is not' : 'is';
|
|
@@ -741,7 +767,7 @@ export class QueryBuilderHelper {
|
|
|
741
767
|
}
|
|
742
768
|
updateVersionProperty(qb, data) {
|
|
743
769
|
const meta = this.#metadata.find(this.#entityName);
|
|
744
|
-
if (!meta?.
|
|
770
|
+
if (!meta?.ownsVersionProperty() || meta.versionProperty in data) {
|
|
745
771
|
return;
|
|
746
772
|
}
|
|
747
773
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
@@ -785,6 +811,10 @@ export class QueryBuilderHelper {
|
|
|
785
811
|
appendGroupCondition(type, operator, subCondition) {
|
|
786
812
|
const parts = [];
|
|
787
813
|
const params = [];
|
|
814
|
+
// an empty disjunction is false, same as `$in: []`, while an empty conjunction is vacuously true
|
|
815
|
+
if (operator === '$or' && subCondition.length === 0) {
|
|
816
|
+
return { sql: '1 = 0', params };
|
|
817
|
+
}
|
|
788
818
|
// single sub-condition can be ignored to reduce nesting of parens
|
|
789
819
|
if (subCondition.length === 1 || operator === '$and') {
|
|
790
820
|
for (const sub of subCondition) {
|
|
@@ -48,6 +48,10 @@ export declare class DatabaseSchema {
|
|
|
48
48
|
/** Separate from `create()` so the comparator only pays for routine introspection when the user actually defined routines. SQLite/libSQL helpers return []. */
|
|
49
49
|
loadRoutines(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, schemas?: string[]): Promise<void>;
|
|
50
50
|
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, em?: any): DatabaseSchema;
|
|
51
|
+
/** Compiles an `rls`-flagged filter's condition into a resolved policy backed by `current_setting()` lookups. */
|
|
52
|
+
private static compileRlsFilterPolicy;
|
|
53
|
+
/** Truncates the base first so the collision suffix survives the identifier limit. */
|
|
54
|
+
private static uniquePolicyName;
|
|
51
55
|
/** Separate from {@link fromMetadata} so the comparator only walks routines when the user defined any. */
|
|
52
56
|
addRoutinesFromMetadata(routines: readonly Routine[], platform: AbstractSqlPlatform, em?: any): void;
|
|
53
57
|
/**
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
1
|
+
import { ReferenceKind, MetadataError, QueryHelper, Utils, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
import { normalizeViewDefinition } from './SchemaHelper.js';
|
|
4
4
|
import { getTablePartitioning } from './partitioning.js';
|
|
@@ -262,9 +262,115 @@ export class DatabaseSchema {
|
|
|
262
262
|
expression: trigger.expression,
|
|
263
263
|
});
|
|
264
264
|
}
|
|
265
|
+
// non-empty policies imply RLS; `rowLevelSecurity: 'force'` enforces it for the table owner too, but an
|
|
266
|
+
// explicit `rowLevelSecurity: false` keeps RLS disabled even when policies are staged (they stay dormant)
|
|
267
|
+
table.rlsEnabled = meta.rowLevelSecurity !== false && (meta.policies.length > 0 || !!meta.rowLevelSecurity);
|
|
268
|
+
table.rlsForced = meta.rowLevelSecurity === 'force';
|
|
269
|
+
const usedPolicyNames = new Set();
|
|
270
|
+
const resolve = (raw) => {
|
|
271
|
+
if (raw == null) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
return isRaw(raw) ? platform.formatQuery(raw.sql, raw.params) : raw;
|
|
275
|
+
};
|
|
276
|
+
for (const policy of meta.policies) {
|
|
277
|
+
const command = policy.command ?? 'all';
|
|
278
|
+
// deterministic default name derived from table + command + collision index, truncated the
|
|
279
|
+
// same way check names are, so the introspected (engine-truncated) name matches metadata
|
|
280
|
+
const max = platform.getMaxIdentifierLength();
|
|
281
|
+
let name = policy.name;
|
|
282
|
+
if (!name) {
|
|
283
|
+
name = this.uniquePolicyName(`${meta.collection}_${command}_policy`, platform, usedPolicyNames);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
name = name.substring(0, max);
|
|
287
|
+
// explicit names skip the collision suffixing, so a duplicate would otherwise die with a raw pg error
|
|
288
|
+
// on create or be silently swallowed by the name-keyed diff dictionaries — reject it up front
|
|
289
|
+
if (usedPolicyNames.has(name)) {
|
|
290
|
+
throw MetadataError.duplicatePolicyName(meta, name);
|
|
291
|
+
}
|
|
292
|
+
usedPolicyNames.add(name);
|
|
293
|
+
}
|
|
294
|
+
table.addPolicy({
|
|
295
|
+
name,
|
|
296
|
+
command,
|
|
297
|
+
type: policy.type ?? 'permissive',
|
|
298
|
+
roles: policy.roles ?? [],
|
|
299
|
+
using: resolve(policy.using),
|
|
300
|
+
check: resolve(policy.check),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
// rls-flagged filters materialize as additional permissive policies (app-level WHERE + DB-level policy);
|
|
304
|
+
// filters inherited from a TPT parent are skipped — the policy already lives on the parent table, and the
|
|
305
|
+
// child table may not even have the referenced columns
|
|
306
|
+
const rlsFilters = Object.values(meta.filters).filter(filter => filter.rls && !(meta.tptParent && Object.values(meta.tptParent.filters).includes(filter)));
|
|
307
|
+
if (rlsFilters.length > 0) {
|
|
308
|
+
// an explicit `rowLevelSecurity: false` still stages the filter's policy but keeps RLS off (dormant)
|
|
309
|
+
table.rlsEnabled = meta.rowLevelSecurity !== false;
|
|
310
|
+
for (const filter of rlsFilters) {
|
|
311
|
+
table.addPolicy(this.compileRlsFilterPolicy(meta, filter, table, platform, usedPolicyNames));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
265
314
|
}
|
|
266
315
|
return schema;
|
|
267
316
|
}
|
|
317
|
+
/** Compiles an `rls`-flagged filter's condition into a resolved policy backed by `current_setting()` lookups. */
|
|
318
|
+
static compileRlsFilterPolicy(meta, filter, table, platform, usedPolicyNames) {
|
|
319
|
+
const accessed = new Set();
|
|
320
|
+
const cond = QueryHelper.resolveRlsFilterCond(filter, accessed, meta.className);
|
|
321
|
+
const setting = typeof filter.rls === 'object' ? filter.rls.setting : undefined;
|
|
322
|
+
if (setting && accessed.size > 1) {
|
|
323
|
+
throw MetadataError.rlsFilterMultiArgSetting(filter.name, [...accessed]);
|
|
324
|
+
}
|
|
325
|
+
// the config-bound driver is always an `AbstractSqlDriver` here, like in `DatabaseTable.processIndexWhere`
|
|
326
|
+
const driver = platform.getConfig().getDriver();
|
|
327
|
+
let sql = driver.renderPartialIndexWhere(meta.class, cond);
|
|
328
|
+
const prefix = QueryHelper.RLS_SENTINEL_PREFIX;
|
|
329
|
+
const suffix = QueryHelper.RLS_SENTINEL_SUFFIX;
|
|
330
|
+
// match `"<column>" <op> '<sentinel>'` — the LHS is always a quoted column emitted from this entity's own
|
|
331
|
+
// where; group 1 keeps the column + operator so only the sentinel literal is swapped for the session lookup
|
|
332
|
+
const re = new RegExp(`("([^"]+)"\\s*(?:!=|>=|<=|=|>|<)\\s*)'${prefix}(\\w+)${suffix}'`, 'g');
|
|
333
|
+
sql = sql.replace(re, (_whole, lhs, column, arg) => {
|
|
334
|
+
const col = table.getColumn(column);
|
|
335
|
+
// the condition can reference a property that renders a field name without a managed column
|
|
336
|
+
// (`persist: false`, `skipColumns`) — fail with a descriptive error instead of a crash
|
|
337
|
+
if (!col) {
|
|
338
|
+
throw MetadataError.rlsFilterUnmanagedColumn(filter.name, column);
|
|
339
|
+
}
|
|
340
|
+
// native enum columns compare against the enum type itself, `current_setting()` text won't coerce implicitly
|
|
341
|
+
const cast = col.nativeEnumName
|
|
342
|
+
? `::${platform.quoteIdentifier(col.nativeEnumName)}`
|
|
343
|
+
: platform.getCurrentSettingCast(col.mappedType);
|
|
344
|
+
if (cast === null) {
|
|
345
|
+
throw MetadataError.rlsFilterUncastableType(filter.name, col.type);
|
|
346
|
+
}
|
|
347
|
+
// a sentinel implies the arg was accessed, and multi-arg custom settings were already rejected above
|
|
348
|
+
const name = setting || Utils.getRlsSettingName(filter.name, arg);
|
|
349
|
+
return `${lhs}current_setting(${platform.quoteValue(name)})${cast}`;
|
|
350
|
+
});
|
|
351
|
+
// a leftover sentinel means an argument appeared somewhere other than a direct comparison, which we can't compile
|
|
352
|
+
if (sql.includes(prefix)) {
|
|
353
|
+
throw MetadataError.rlsFilterUnsupportedCond(filter.name);
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
name: this.uniquePolicyName(`${meta.collection}_${filter.name}_policy`, platform, usedPolicyNames),
|
|
357
|
+
command: 'all',
|
|
358
|
+
type: 'permissive',
|
|
359
|
+
roles: [],
|
|
360
|
+
using: sql,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/** Truncates the base first so the collision suffix survives the identifier limit. */
|
|
364
|
+
static uniquePolicyName(base, platform, used) {
|
|
365
|
+
const max = platform.getMaxIdentifierLength();
|
|
366
|
+
let name = base.substring(0, max);
|
|
367
|
+
for (let i = 2; used.has(name); i++) {
|
|
368
|
+
const suffix = `_${i}`;
|
|
369
|
+
name = base.substring(0, max - suffix.length) + suffix;
|
|
370
|
+
}
|
|
371
|
+
used.add(name);
|
|
372
|
+
return name;
|
|
373
|
+
}
|
|
268
374
|
/** Separate from {@link fromMetadata} so the comparator only walks routines when the user defined any. */
|
|
269
375
|
addRoutinesFromMetadata(routines, platform, em) {
|
|
270
376
|
const resolveBody = (raw) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type IndexCallback, type NamingStrategy } from '@mikro-orm/core';
|
|
2
2
|
import type { SchemaHelper } from './SchemaHelper.js';
|
|
3
|
-
import type { CheckDef, Column, ForeignKey, IndexDef, TablePartitioning, SqlTriggerDef } from '../typings.js';
|
|
3
|
+
import type { CheckDef, Column, ForeignKey, IndexDef, TablePartitioning, SqlPolicyDef, SqlTriggerDef } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
5
5
|
/**
|
|
6
6
|
* @internal
|
|
@@ -15,6 +15,10 @@ export declare class DatabaseTable {
|
|
|
15
15
|
items: string[];
|
|
16
16
|
}>;
|
|
17
17
|
comment?: string;
|
|
18
|
+
/** Whether row level security is enabled on the table (postgres only). */
|
|
19
|
+
rlsEnabled: boolean;
|
|
20
|
+
/** Whether row level security is also enforced for the table owner (postgres `force`). */
|
|
21
|
+
rlsForced: boolean;
|
|
18
22
|
partitioning?: TablePartitioning;
|
|
19
23
|
/**
|
|
20
24
|
* Effective collation the column defaults to when no explicit `COLLATE` is set on a column.
|
|
@@ -34,6 +38,11 @@ export declare class DatabaseTable {
|
|
|
34
38
|
/** @internal */
|
|
35
39
|
setPartitioning(partitioning?: TablePartitioning): void;
|
|
36
40
|
getTriggers(): SqlTriggerDef[];
|
|
41
|
+
getPolicies(): SqlPolicyDef[];
|
|
42
|
+
/** `[]` and `['public']` both mean PUBLIC — a single predicate so metadata, introspection and codegen agree. */
|
|
43
|
+
static isDefaultPolicyRoles(roles: string[]): boolean;
|
|
44
|
+
/** @internal */
|
|
45
|
+
setPolicies(policies: SqlPolicyDef[]): void;
|
|
37
46
|
/** @internal */
|
|
38
47
|
setIndexes(indexes: IndexDef[]): void;
|
|
39
48
|
/** @internal */
|
|
@@ -49,6 +58,8 @@ export declare class DatabaseTable {
|
|
|
49
58
|
getEntityDeclaration(namingStrategy: NamingStrategy, schemaHelper: SchemaHelper, scalarPropertiesForRelations: 'always' | 'never' | 'smart'): EntityMetadata;
|
|
50
59
|
private foreignKeysToProps;
|
|
51
60
|
private findFkIndex;
|
|
61
|
+
/** Advanced options require an entity-level declaration, as the property-level `index`/`unique` cannot carry them. */
|
|
62
|
+
private hasAdvancedIndexOptions;
|
|
52
63
|
private getIndexProperties;
|
|
53
64
|
private getSafeBaseNameForFkProp;
|
|
54
65
|
/**
|
|
@@ -63,6 +74,8 @@ export declare class DatabaseTable {
|
|
|
63
74
|
hasCheck(checkName: string): boolean;
|
|
64
75
|
getTrigger(triggerName: string): SqlTriggerDef | undefined;
|
|
65
76
|
hasTrigger(triggerName: string): boolean;
|
|
77
|
+
getPolicy(policyName: string): SqlPolicyDef | undefined;
|
|
78
|
+
hasPolicy(policyName: string): boolean;
|
|
66
79
|
getPrimaryKey(): IndexDef | undefined;
|
|
67
80
|
hasPrimaryKey(): boolean;
|
|
68
81
|
private getForeignKeyDeclaration;
|
|
@@ -97,5 +110,6 @@ export declare class DatabaseTable {
|
|
|
97
110
|
private processIndexWhere;
|
|
98
111
|
addCheck(check: CheckDef): void;
|
|
99
112
|
addTrigger(trigger: SqlTriggerDef): void;
|
|
113
|
+
addPolicy(policy: SqlPolicyDef): void;
|
|
100
114
|
toJSON(): Dictionary;
|
|
101
115
|
}
|