@nest-boot/row-level-security 7.2.3 → 7.2.5
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/dist/decorators/policy.decorator.d.ts +1 -1
- package/dist/decorators/policy.decorator.js +4 -4
- package/dist/decorators/policy.decorator.js.map +1 -1
- package/dist/decorators/policy.decorator.spec.js +18 -18
- package/dist/decorators/policy.decorator.spec.js.map +1 -1
- package/dist/index.spec.js +2 -2
- package/dist/index.spec.js.map +1 -1
- package/dist/interfaces/policy-options.interface.d.ts +1 -1
- package/dist/row-level-security-driver.js +1 -1
- package/dist/row-level-security-driver.js.map +1 -1
- package/dist/row-level-security-driver.spec.js +4 -4
- package/dist/row-level-security-driver.spec.js.map +1 -1
- package/dist/row-level-security-migration-generator.js +204 -268
- package/dist/row-level-security-migration-generator.js.map +1 -1
- package/dist/row-level-security-migration-generator.spec.js +108 -38
- package/dist/row-level-security-migration-generator.spec.js.map +1 -1
- package/dist/row-level-security-migration.d.ts +1 -1
- package/dist/row-level-security-migration.js +1 -1
- package/dist/row-level-security-migration.js.map +1 -1
- package/dist/row-level-security-migration.spec.js +6 -5
- package/dist/row-level-security-migration.spec.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/create-policy-bootstrap-sql-statements.d.ts +2 -2
- package/dist/utils/create-policy-bootstrap-sql-statements.js +2 -5
- package/dist/utils/create-policy-bootstrap-sql-statements.js.map +1 -1
- package/dist/utils/create-policy-role-sql-statements.d.ts +4 -4
- package/dist/utils/create-policy-role-sql-statements.js +6 -19
- package/dist/utils/create-policy-role-sql-statements.js.map +1 -1
- package/dist/utils/normalize-postgres-type-alias.spec.d.ts +1 -0
- package/dist/utils/normalize-postgres-type-alias.spec.js +28 -0
- package/dist/utils/normalize-postgres-type-alias.spec.js.map +1 -0
- package/dist/utils/{normalize-postgres-type-alias.js → normalize-postgres-type-alias.util.js} +1 -1
- package/dist/utils/normalize-postgres-type-alias.util.js.map +1 -0
- package/dist/utils/policy-migration-sql.spec.js +19 -33
- package/dist/utils/policy-migration-sql.spec.js.map +1 -1
- package/package.json +6 -3
- package/dist/utils/normalize-postgres-type-alias.js.map +0 -1
- /package/dist/utils/{normalize-postgres-type-alias.d.ts → normalize-postgres-type-alias.util.d.ts} +0 -0
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RowLevelSecurityMigrationGenerator = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const migrations_1 = require("@mikro-orm/migrations");
|
|
6
|
+
const pgsql_ast_parser_1 = require("pgsql-ast-parser");
|
|
6
7
|
const policy_decorator_1 = require("./decorators/policy.decorator");
|
|
7
8
|
const policy_command_enum_1 = require("./enums/policy-command.enum");
|
|
8
9
|
const policy_mode_enum_1 = require("./enums/policy-mode.enum");
|
|
@@ -11,8 +12,7 @@ const create_policy_down_sql_1 = require("./utils/create-policy-down-sql");
|
|
|
11
12
|
const create_policy_privilege_down_sql_statements_1 = require("./utils/create-policy-privilege-down-sql-statements");
|
|
12
13
|
const create_policy_role_sql_statements_1 = require("./utils/create-policy-role-sql-statements");
|
|
13
14
|
const create_policy_up_sql_statements_1 = require("./utils/create-policy-up-sql-statements");
|
|
14
|
-
const
|
|
15
|
-
const normalize_postgres_type_alias_1 = require("./utils/normalize-postgres-type-alias");
|
|
15
|
+
const normalize_postgres_type_alias_util_1 = require("./utils/normalize-postgres-type-alias.util");
|
|
16
16
|
const POSTGRES_IDENTIFIER_MAX_LENGTH = 63;
|
|
17
17
|
const POLICY_IDENTIFIER_TYPE = "policy";
|
|
18
18
|
/** MikroORM TypeScript migration generator that injects generated RLS SQL. */
|
|
@@ -262,10 +262,8 @@ function hasSamePolicyDefinitionContent(left, right) {
|
|
|
262
262
|
return (normalizePolicyMode(left.mode) === normalizePolicyMode(right.mode) &&
|
|
263
263
|
normalizePolicyCommand(left.command) ===
|
|
264
264
|
normalizePolicyCommand(right.command) &&
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
normalizePolicyExpression(left.withCheck) ===
|
|
268
|
-
normalizePolicyExpression(right.withCheck) &&
|
|
265
|
+
hasSamePolicyExpression(left.using, right.using) &&
|
|
266
|
+
hasSamePolicyExpression(left.withCheck, right.withCheck) &&
|
|
269
267
|
normalizePolicyRoles(left.roles).join("\n") ===
|
|
270
268
|
normalizePolicyRoles(right.roles).join("\n"));
|
|
271
269
|
}
|
|
@@ -275,6 +273,17 @@ function normalizePolicyMode(mode) {
|
|
|
275
273
|
function normalizePolicyCommand(command) {
|
|
276
274
|
return command ?? policy_command_enum_1.PolicyCommand.ALL;
|
|
277
275
|
}
|
|
276
|
+
function hasSamePolicyExpression(left, right) {
|
|
277
|
+
const normalizedLeft = normalizePolicyExpression(left);
|
|
278
|
+
const normalizedRight = normalizePolicyExpression(right);
|
|
279
|
+
if (normalizedLeft !== undefined && normalizedRight !== undefined) {
|
|
280
|
+
return normalizedLeft === normalizedRight;
|
|
281
|
+
}
|
|
282
|
+
if (normalizedLeft === undefined && normalizedRight === undefined) {
|
|
283
|
+
return left?.trim() === right?.trim();
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
278
287
|
function normalizePolicyExpression(expression) {
|
|
279
288
|
const normalized = expression?.trim();
|
|
280
289
|
if (!normalized) {
|
|
@@ -283,270 +292,96 @@ function normalizePolicyExpression(expression) {
|
|
|
283
292
|
return normalizePostgresExpression(normalized);
|
|
284
293
|
}
|
|
285
294
|
function normalizePostgresExpression(expression) {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
function normalizePostgresExpressionSegment(segment) {
|
|
290
|
-
return normalizeSqlOperatorSpacing(normalizeNullTypeCasts(segment
|
|
291
|
-
.replace(/\s+/g, " ")
|
|
292
|
-
.replace(/\b(select|and|or|not|is|true|false|null|as)\b/gi, (match) => match.toLowerCase())
|
|
293
|
-
.replace(/\s*,\s*/g, ", ")))
|
|
294
|
-
.replace(/\(\s+/g, "(")
|
|
295
|
-
.replace(/\s+\)/g, ")");
|
|
296
|
-
}
|
|
297
|
-
function normalizeNullTypeCasts(segment) {
|
|
298
|
-
return segment
|
|
299
|
-
.replace(/\bnull::(?:character\s+varying|varchar)(?:\s*\([^)]*\))?/gi, "null::character varying")
|
|
300
|
-
.replace(/\bnull::(?:timestamp\s+with\s+time\s+zone|timestamptz)\b/gi, "null::timestamp with time zone")
|
|
301
|
-
.replace(/\bnull::([a-z_][a-z0-9_.]*)/gi, (_match, type) => `null::${(0, normalize_postgres_type_alias_1.normalizePostgresTypeAlias)(type.toLowerCase())}`);
|
|
302
|
-
}
|
|
303
|
-
function normalizeSqlOperatorSpacing(segment) {
|
|
304
|
-
const jsonOperators = [];
|
|
305
|
-
const protectedSegment = segment.replace(/\s*(#>>|#>|->>|->|@>|<@)\s*/g, (_match, operator) => {
|
|
306
|
-
const placeholder = `__rls_json_operator_${String(jsonOperators.length)}__`;
|
|
307
|
-
jsonOperators.push(` ${operator} `);
|
|
308
|
-
return placeholder;
|
|
309
|
-
});
|
|
310
|
-
return protectedSegment
|
|
311
|
-
.replace(/\s*(<>|!=|<=|>=|=|<|>)\s*/g, (_match, operator) => ` ${operator === "!=" ? "<>" : operator} `)
|
|
312
|
-
.replace(/__rls_json_operator_(\d+)__/g, (_match, index) => jsonOperators[Number(index)]);
|
|
313
|
-
}
|
|
314
|
-
function stripSelectProjectionAliases(expression) {
|
|
315
|
-
let normalized = "";
|
|
316
|
-
const selectDepths = new Set();
|
|
317
|
-
let depth = 0;
|
|
318
|
-
let index = 0;
|
|
319
|
-
while (index < expression.length) {
|
|
320
|
-
const character = expression[index];
|
|
321
|
-
if (character === "'") {
|
|
322
|
-
const literalEnd = readSqlStringLiteralEnd(expression, index);
|
|
323
|
-
normalized += expression.slice(index, literalEnd);
|
|
324
|
-
index = literalEnd;
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
if (character === '"') {
|
|
328
|
-
const identifierEnd = readQuotedIdentifierEnd(expression, index);
|
|
329
|
-
normalized += expression.slice(index, identifierEnd);
|
|
330
|
-
index = identifierEnd;
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
if (character === "(") {
|
|
334
|
-
depth += 1;
|
|
335
|
-
normalized += character;
|
|
336
|
-
index += 1;
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
if (character === ")") {
|
|
340
|
-
selectDepths.delete(depth);
|
|
341
|
-
depth -= 1;
|
|
342
|
-
normalized += character;
|
|
343
|
-
index += 1;
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
const wordEnd = readSqlWordEnd(expression, index);
|
|
347
|
-
if (wordEnd > index) {
|
|
348
|
-
const word = expression.slice(index, wordEnd);
|
|
349
|
-
const lowerWord = word.toLowerCase();
|
|
350
|
-
if (lowerWord === "select") {
|
|
351
|
-
selectDepths.add(depth);
|
|
352
|
-
}
|
|
353
|
-
if (lowerWord === "as" && selectDepths.has(depth)) {
|
|
354
|
-
const aliasEnd = readSelectProjectionAliasEnd(expression, wordEnd);
|
|
355
|
-
if (aliasEnd !== undefined) {
|
|
356
|
-
normalized = normalized.replace(/\s+$/, "");
|
|
357
|
-
index = aliasEnd;
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
normalized += word;
|
|
362
|
-
index = wordEnd;
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
normalized += character;
|
|
366
|
-
index += 1;
|
|
295
|
+
const parsed = parsePolicyExpressionAst(expression);
|
|
296
|
+
if (!parsed) {
|
|
297
|
+
return undefined;
|
|
367
298
|
}
|
|
368
|
-
return
|
|
299
|
+
return JSON.stringify(canonicalizePolicyExpressionAst(parsed.ast, parsed.sql));
|
|
369
300
|
}
|
|
370
|
-
function
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
if (aliasEnd === aliasStart) {
|
|
301
|
+
function parsePolicyExpressionAst(expression) {
|
|
302
|
+
const parsed = parsePolicyExpressionStatement(expression);
|
|
303
|
+
if (parsed?.statement.type !== "select" || !parsed.statement.where) {
|
|
374
304
|
return undefined;
|
|
375
305
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
let normalized = expression;
|
|
381
|
-
while (isWrappedInParentheses(normalized)) {
|
|
382
|
-
normalized = normalized.slice(1, -1).trim();
|
|
383
|
-
}
|
|
384
|
-
return normalized;
|
|
385
|
-
}
|
|
386
|
-
function stripRedundantJsonOperandParentheses(expression) {
|
|
387
|
-
let normalized = expression;
|
|
388
|
-
let previous;
|
|
389
|
-
do {
|
|
390
|
-
previous = normalized;
|
|
391
|
-
normalized = stripRedundantJsonOperandParenthesesOnce(normalized);
|
|
392
|
-
} while (normalized !== previous);
|
|
393
|
-
return normalized;
|
|
394
|
-
}
|
|
395
|
-
function stripRedundantJsonOperandParenthesesOnce(expression) {
|
|
396
|
-
const removals = new Set();
|
|
397
|
-
const parenthesisPairs = findParenthesisPairs(expression);
|
|
398
|
-
for (const [start, end] of parenthesisPairs) {
|
|
399
|
-
const innerExpression = expression.slice(start + 1, end);
|
|
400
|
-
if (hasTopLevelJsonOperator(innerExpression) &&
|
|
401
|
-
(hasComparisonOperatorBefore(expression, start) ||
|
|
402
|
-
hasComparisonOperatorAfter(expression, end))) {
|
|
403
|
-
removals.add(start);
|
|
404
|
-
removals.add(end);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
if (removals.size === 0) {
|
|
408
|
-
return expression;
|
|
409
|
-
}
|
|
410
|
-
return [...expression]
|
|
411
|
-
.filter((_character, index) => !removals.has(index))
|
|
412
|
-
.join("");
|
|
306
|
+
return {
|
|
307
|
+
ast: parsed.statement.where,
|
|
308
|
+
sql: parsed.sql,
|
|
309
|
+
};
|
|
413
310
|
}
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const character = expression[index];
|
|
422
|
-
if (character === "'") {
|
|
423
|
-
index = readSqlStringLiteralEnd(expression, index);
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
if (character === '"') {
|
|
427
|
-
index = readQuotedIdentifierEnd(expression, index);
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (character === "(") {
|
|
431
|
-
depth += 1;
|
|
432
|
-
}
|
|
433
|
-
else if (character === ")") {
|
|
434
|
-
depth -= 1;
|
|
435
|
-
if (depth === 0 && index < expression.length - 1) {
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
if (depth < 0) {
|
|
440
|
-
return false;
|
|
441
|
-
}
|
|
442
|
-
index += 1;
|
|
311
|
+
function parsePolicyExpressionStatement(expression) {
|
|
312
|
+
const sql = `select 1 where ${expression}`;
|
|
313
|
+
try {
|
|
314
|
+
return {
|
|
315
|
+
sql,
|
|
316
|
+
statement: (0, pgsql_ast_parser_1.parse)(sql, { locationTracking: true })[0],
|
|
317
|
+
};
|
|
443
318
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
if (character === '"') {
|
|
457
|
-
index = readQuotedIdentifierEnd(expression, index);
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
if (character === "(") {
|
|
461
|
-
stack.push(index);
|
|
462
|
-
}
|
|
463
|
-
else if (character === ")") {
|
|
464
|
-
const start = stack.pop();
|
|
465
|
-
if (start !== undefined) {
|
|
466
|
-
pairs.push([start, index]);
|
|
319
|
+
catch {
|
|
320
|
+
const parserCompatibleExpression = getPolicyExpressionForAstParser(expression);
|
|
321
|
+
if (parserCompatibleExpression !== expression) {
|
|
322
|
+
try {
|
|
323
|
+
const parserCompatibleSql = `select 1 where ${parserCompatibleExpression}`;
|
|
324
|
+
return {
|
|
325
|
+
sql: parserCompatibleSql,
|
|
326
|
+
statement: (0, pgsql_ast_parser_1.parse)(parserCompatibleSql, {
|
|
327
|
+
locationTracking: true,
|
|
328
|
+
})[0],
|
|
329
|
+
};
|
|
467
330
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
return pairs;
|
|
472
|
-
}
|
|
473
|
-
function hasTopLevelJsonOperator(expression) {
|
|
474
|
-
let depth = 0;
|
|
475
|
-
let index = 0;
|
|
476
|
-
while (index < expression.length) {
|
|
477
|
-
const character = expression[index];
|
|
478
|
-
if (character === "'") {
|
|
479
|
-
index = readSqlStringLiteralEnd(expression, index);
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
if (character === '"') {
|
|
483
|
-
index = readQuotedIdentifierEnd(expression, index);
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
if (character === "(") {
|
|
487
|
-
depth += 1;
|
|
488
|
-
index += 1;
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
if (character === ")") {
|
|
492
|
-
depth -= 1;
|
|
493
|
-
index += 1;
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
if (depth === 0) {
|
|
497
|
-
const jsonOperator = readJsonOperatorAt(expression, index);
|
|
498
|
-
if (jsonOperator) {
|
|
499
|
-
return true;
|
|
331
|
+
catch {
|
|
332
|
+
return undefined;
|
|
500
333
|
}
|
|
501
334
|
}
|
|
502
|
-
|
|
335
|
+
return undefined;
|
|
503
336
|
}
|
|
504
|
-
return false;
|
|
505
337
|
}
|
|
506
|
-
function
|
|
507
|
-
|
|
508
|
-
return
|
|
338
|
+
function getPolicyExpressionForAstParser(expression) {
|
|
339
|
+
// pgsql-ast-parser treats these multi-word casts in select projections as ambiguous.
|
|
340
|
+
return replaceSqlOutsideQuotedTokens(expression, (segment) => segment
|
|
341
|
+
.replace(/::\s*character\s+varying\b/gi, "::varchar")
|
|
342
|
+
.replace(/::\s*bit\s+varying\b/gi, "::varbit"));
|
|
509
343
|
}
|
|
510
|
-
function
|
|
511
|
-
const operatorStart = skipSqlWhitespace(expression, endIndex + 1);
|
|
512
|
-
return Boolean(readComparisonOperatorAt(expression, operatorStart));
|
|
513
|
-
}
|
|
514
|
-
function mapSqlOutsideQuotedTokens(expression, transform) {
|
|
344
|
+
function replaceSqlOutsideQuotedTokens(expression, transform) {
|
|
515
345
|
const parts = [];
|
|
516
346
|
let segmentStart = 0;
|
|
517
347
|
let index = 0;
|
|
518
348
|
while (index < expression.length) {
|
|
519
|
-
|
|
349
|
+
const tokenEnd = readSqlQuotedTokenEnd(expression, index);
|
|
350
|
+
if (tokenEnd === undefined) {
|
|
520
351
|
index += 1;
|
|
521
352
|
continue;
|
|
522
353
|
}
|
|
523
354
|
parts.push(transform(expression.slice(segmentStart, index)));
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
parts.push(expression.slice(literalStart, index));
|
|
528
|
-
}
|
|
529
|
-
else {
|
|
530
|
-
const identifierStart = index;
|
|
531
|
-
index = readQuotedIdentifierEnd(expression, index);
|
|
532
|
-
parts.push(normalizeQuotedIdentifier(expression.slice(identifierStart, index)));
|
|
533
|
-
}
|
|
534
|
-
segmentStart = index;
|
|
355
|
+
parts.push(expression.slice(index, tokenEnd));
|
|
356
|
+
segmentStart = tokenEnd;
|
|
357
|
+
index = tokenEnd;
|
|
535
358
|
}
|
|
536
359
|
parts.push(transform(expression.slice(segmentStart)));
|
|
537
360
|
return parts.join("");
|
|
538
361
|
}
|
|
539
|
-
function
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
362
|
+
function readSqlQuotedTokenEnd(expression, startIndex) {
|
|
363
|
+
if (expression[startIndex] === "'") {
|
|
364
|
+
return readSqlStringLiteralEnd(expression, startIndex, hasPostgresEscapeStringPrefix(expression, startIndex));
|
|
365
|
+
}
|
|
366
|
+
if (expression[startIndex] === '"') {
|
|
367
|
+
return readQuotedIdentifierEnd(expression, startIndex);
|
|
368
|
+
}
|
|
369
|
+
return readDollarQuotedStringEnd(expression, startIndex);
|
|
370
|
+
}
|
|
371
|
+
function hasPostgresEscapeStringPrefix(expression, quoteIndex) {
|
|
372
|
+
const prefixIndex = quoteIndex - 1;
|
|
373
|
+
if (!/[eE]/.test(expression[prefixIndex] ?? "")) {
|
|
374
|
+
return false;
|
|
544
375
|
}
|
|
545
|
-
return
|
|
376
|
+
return prefixIndex === 0 || !isSqlIdentifierPart(expression[prefixIndex - 1]);
|
|
546
377
|
}
|
|
547
|
-
function readSqlStringLiteralEnd(expression, startIndex) {
|
|
378
|
+
function readSqlStringLiteralEnd(expression, startIndex, allowBackslashEscapes = false) {
|
|
548
379
|
let index = startIndex + 1;
|
|
549
380
|
while (index < expression.length) {
|
|
381
|
+
if (allowBackslashEscapes && expression[index] === "\\") {
|
|
382
|
+
index += 2;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
550
385
|
if (expression[index] !== "'") {
|
|
551
386
|
index += 1;
|
|
552
387
|
continue;
|
|
@@ -560,6 +395,9 @@ function readSqlStringLiteralEnd(expression, startIndex) {
|
|
|
560
395
|
}
|
|
561
396
|
return index;
|
|
562
397
|
}
|
|
398
|
+
function isSqlIdentifierPart(character) {
|
|
399
|
+
return character !== undefined && /[A-Za-z0-9_$]/.test(character);
|
|
400
|
+
}
|
|
563
401
|
function readQuotedIdentifierEnd(expression, startIndex) {
|
|
564
402
|
let index = startIndex + 1;
|
|
565
403
|
while (index < expression.length) {
|
|
@@ -576,41 +414,139 @@ function readQuotedIdentifierEnd(expression, startIndex) {
|
|
|
576
414
|
}
|
|
577
415
|
return index;
|
|
578
416
|
}
|
|
579
|
-
function
|
|
580
|
-
|
|
581
|
-
|
|
417
|
+
function readDollarQuotedStringEnd(expression, startIndex) {
|
|
418
|
+
const delimiter = /^\$[a-z_][a-z0-9_]*\$|^\$\$/i.exec(expression.slice(startIndex))?.[0];
|
|
419
|
+
if (!delimiter) {
|
|
420
|
+
return undefined;
|
|
582
421
|
}
|
|
583
|
-
|
|
422
|
+
const endIndex = expression.indexOf(delimiter, startIndex + delimiter.length);
|
|
423
|
+
return endIndex === -1 ? expression.length : endIndex + delimiter.length;
|
|
584
424
|
}
|
|
585
|
-
function
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
425
|
+
function canonicalizePolicyExpressionAst(value, sourceSql) {
|
|
426
|
+
if (value === null || typeof value !== "object") {
|
|
427
|
+
return value === undefined
|
|
428
|
+
? null
|
|
429
|
+
: value;
|
|
430
|
+
}
|
|
431
|
+
if (Array.isArray(value)) {
|
|
432
|
+
return value.map((item) => canonicalizePolicyExpressionAst(item, sourceSql));
|
|
433
|
+
}
|
|
434
|
+
if (isRedundantTextCast(value)) {
|
|
435
|
+
return canonicalizePolicyExpressionAst(value.operand, sourceSql);
|
|
436
|
+
}
|
|
437
|
+
const node = value;
|
|
438
|
+
const entries = [];
|
|
439
|
+
for (const key of Object.keys(node).sort()) {
|
|
440
|
+
const property = node[key];
|
|
441
|
+
if (property === undefined || key === "_location") {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (key === "alias" && "expr" in node) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (key === "value" && isNumericPolicyExpressionAstNode(node)) {
|
|
448
|
+
entries.push([
|
|
449
|
+
key,
|
|
450
|
+
getPolicyExpressionAstNodeSource(node, sourceSql) ??
|
|
451
|
+
canonicalizePolicyExpressionAst(property, sourceSql),
|
|
452
|
+
]);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (key === "op" && typeof property === "string") {
|
|
456
|
+
entries.push([key, normalizePolicyExpressionOperator(property)]);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (key === "to" && node.type === "cast") {
|
|
460
|
+
entries.push([
|
|
461
|
+
key,
|
|
462
|
+
canonicalizePolicyExpressionDataType(property, sourceSql),
|
|
463
|
+
]);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
entries.push([key, canonicalizePolicyExpressionAst(property, sourceSql)]);
|
|
467
|
+
}
|
|
468
|
+
return Object.fromEntries(entries);
|
|
590
469
|
}
|
|
591
|
-
function
|
|
592
|
-
if (
|
|
593
|
-
return
|
|
470
|
+
function canonicalizePolicyExpressionDataType(value, sourceSql) {
|
|
471
|
+
if (value === null || typeof value !== "object") {
|
|
472
|
+
return value === undefined
|
|
473
|
+
? null
|
|
474
|
+
: value;
|
|
594
475
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
index += 1;
|
|
476
|
+
if (Array.isArray(value)) {
|
|
477
|
+
return value.map((item) => canonicalizePolicyExpressionDataType(item, sourceSql));
|
|
598
478
|
}
|
|
599
|
-
|
|
479
|
+
const node = value;
|
|
480
|
+
const entries = [];
|
|
481
|
+
const isDoubleQuoted = node.doubleQuoted === true;
|
|
482
|
+
for (const key of Object.keys(node).sort()) {
|
|
483
|
+
const property = node[key];
|
|
484
|
+
if (property === undefined || key === "_location") {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (key === "name" && typeof property === "string" && !isDoubleQuoted) {
|
|
488
|
+
entries.push([key, normalizePolicyExpressionDataTypeName(property)]);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (key === "arrayOf") {
|
|
492
|
+
entries.push([
|
|
493
|
+
key,
|
|
494
|
+
canonicalizePolicyExpressionDataType(property, sourceSql),
|
|
495
|
+
]);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
entries.push([key, canonicalizePolicyExpressionAst(property, sourceSql)]);
|
|
499
|
+
}
|
|
500
|
+
return Object.fromEntries(entries);
|
|
600
501
|
}
|
|
601
|
-
function
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
index += 1;
|
|
502
|
+
function isRedundantTextCast(value) {
|
|
503
|
+
if (!isPolicyExpressionAstNode(value, "cast") || !isTextType(value.to)) {
|
|
504
|
+
return false;
|
|
605
505
|
}
|
|
606
|
-
return
|
|
506
|
+
return (isPolicyExpressionAstNode(value.operand, "string") ||
|
|
507
|
+
isTextJsonMember(value.operand));
|
|
607
508
|
}
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
509
|
+
function isTextJsonMember(value) {
|
|
510
|
+
return isPolicyExpressionAstNode(value, "member") && value.op === "->>";
|
|
511
|
+
}
|
|
512
|
+
function isTextType(value) {
|
|
513
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
514
|
+
return false;
|
|
612
515
|
}
|
|
613
|
-
|
|
516
|
+
const node = value;
|
|
517
|
+
const typeName = node.name;
|
|
518
|
+
return (node.doubleQuoted !== true &&
|
|
519
|
+
typeof typeName === "string" &&
|
|
520
|
+
normalizePolicyExpressionDataTypeName(typeName) === "text");
|
|
521
|
+
}
|
|
522
|
+
function isNumericPolicyExpressionAstNode(value) {
|
|
523
|
+
return value.type === "integer" || value.type === "numeric";
|
|
524
|
+
}
|
|
525
|
+
function getPolicyExpressionAstNodeSource(value, sourceSql) {
|
|
526
|
+
const location = value._location;
|
|
527
|
+
if (location === null ||
|
|
528
|
+
typeof location !== "object" ||
|
|
529
|
+
Array.isArray(location)) {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
const start = location.start;
|
|
533
|
+
const end = location.end;
|
|
534
|
+
if (typeof start !== "number" || typeof end !== "number") {
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
return sourceSql.slice(start, end);
|
|
538
|
+
}
|
|
539
|
+
function isPolicyExpressionAstNode(value, type) {
|
|
540
|
+
return (value !== null &&
|
|
541
|
+
typeof value === "object" &&
|
|
542
|
+
!Array.isArray(value) &&
|
|
543
|
+
value.type === type);
|
|
544
|
+
}
|
|
545
|
+
function normalizePolicyExpressionDataTypeName(typeName) {
|
|
546
|
+
return (0, normalize_postgres_type_alias_util_1.normalizePostgresTypeAlias)(typeName.toLowerCase().replace(/\s+/g, " "));
|
|
547
|
+
}
|
|
548
|
+
function normalizePolicyExpressionOperator(operator) {
|
|
549
|
+
return operator === "!=" ? "<>" : operator;
|
|
614
550
|
}
|
|
615
551
|
function normalizePolicyRoles(roles) {
|
|
616
552
|
return [
|