rake-db 2.34.1 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ArrayColumn, Column, CustomTypeColumn, DomainColumn, EnumColumn, PostgisGeographyPointColumn, RawSql, TimestampColumn, TimestampTZColumn, UnknownColumn, addCode, assignDbDataToColumn, backtickQuote, codeToString, colors, constraintInnerToCode, consumeColumnName, deepCompare, defaultSchemaConfig, emptyArray, emptyObject, escapeForMigration, escapeString, excludeInnerToCode, exhaustive, getColumnTypes, getImportPath, getStackTrace, indexInnerToCode, isRawSQL, logParamToLogObject, makeColumnTypes, makeColumnsByType, parseTableData, parseTableDataInput, pathToLog, primaryKeyInnerToCode, pushTableDataCode, quoteObjectKey, raw, rawSqlToCode, referencesArgsToCode, setCurrentColumnName, setDefaultLanguage, singleQuote, tableDataMethods, toArray, toCamelCase, toSnakeCase } from "pqb/internal";
1
+ import { ArrayColumn, Column, CustomTypeColumn, DomainColumn, EnumColumn, PostgisGeographyPointColumn, RawSql, TimestampColumn, TimestampTZColumn, UnknownColumn, addCode, assignDbDataToColumn, backtickQuote, codeToString, colors, constraintInnerToCode, consumeColumnName, deepCompare, defaultSchemaConfig, emptyArray, emptyObject, escapeForMigration, escapeString, excludeInnerToCode, exhaustive, getColumnTypes, getImportPath, getStackTrace, indexInnerToCode, isRawSQL, logParamToLogObject, makeColumnTypes, makeColumnsByType, parseTableData, parseTableDataInput, pathToLog, primaryKeyInnerToCode, pushTableDataCode, quoteIdentifier, quoteObjectKey, raw, rawSqlToCode, referencesArgsToCode, setCurrentColumnName, setDefaultLanguage, singleQuote, tableDataMethods, toArray, toCamelCase, toSnakeCase } from "pqb/internal";
2
2
  import path, { join } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { createDbWithAdapter } from "pqb";
@@ -1143,13 +1143,12 @@ const astToQuery$1 = (ast) => {
1143
1143
  if (options?.recursive) sql.push("RECURSIVE");
1144
1144
  sql.push(`VIEW ${sqlName}`);
1145
1145
  if (options?.columns) sql.push(`(${options.columns.map((column) => `"${column}"`).join(", ")})`);
1146
- if (options?.with) {
1147
- const list = [];
1148
- if (options.with.checkOption) list.push(`check_option = ${singleQuote(options.with.checkOption)}`);
1149
- if (options.with.securityBarrier) list.push(`security_barrier = true`);
1150
- if (options.with.securityInvoker) list.push(`security_invoker = true`);
1151
- sql.push(`WITH ( ${list.join(", ")} )`);
1152
- }
1146
+ const withOptions = options?.with;
1147
+ const list = [];
1148
+ if (withOptions?.checkOption) list.push(`check_option = ${singleQuote(withOptions.checkOption)}`);
1149
+ if (withOptions?.securityBarrier) list.push(`security_barrier = true`);
1150
+ if (withOptions?.securityInvoker !== false) list.push(`security_invoker = true`);
1151
+ if (list.length) sql.push(`WITH ( ${list.join(", ")} )`);
1153
1152
  sql.push(`AS (${ast.sql.toSQL({ values })})`);
1154
1153
  } else {
1155
1154
  sql.push("DROP VIEW");
@@ -1350,12 +1349,12 @@ const objectConfigToSql = (ast, config, action) => {
1350
1349
  const objectType = key;
1351
1350
  if (!value) continue;
1352
1351
  const { privileges, grantablePrivileges } = value;
1353
- if (privileges?.length) queries.push(buildQuery(ast, objectType, privileges, false, action));
1354
- if (grantablePrivileges?.length) queries.push(buildQuery(ast, objectType, grantablePrivileges, true, action));
1352
+ if (privileges?.length) queries.push(buildQuery$1(ast, objectType, privileges, false, action));
1353
+ if (grantablePrivileges?.length) queries.push(buildQuery$1(ast, objectType, grantablePrivileges, true, action));
1355
1354
  }
1356
1355
  return queries;
1357
1356
  };
1358
- const buildQuery = (ast, objectType, privileges, grantable, action) => {
1357
+ const buildQuery$1 = (ast, objectType, privileges, grantable, action) => {
1359
1358
  const parts = ["ALTER DEFAULT PRIVILEGES"];
1360
1359
  if (ast.owner) parts.push(`FOR ROLE "${ast.owner}"`);
1361
1360
  if (ast.schema) parts.push(`IN SCHEMA "${ast.schema}"`);
@@ -1382,6 +1381,204 @@ const enableOrDisableRls = (migration, up, tableName) => {
1382
1381
  const forceOrNoForceRls = (migration, up, tableName) => {
1383
1382
  return setRls(migration, tableName, up ? "force" : "noForce");
1384
1383
  };
1384
+ const quotedRoles = (roles) => {
1385
+ if (!roles) return;
1386
+ const arr = Array.isArray(roles) ? roles : [roles];
1387
+ if (!arr.length) return;
1388
+ return arr.map(quoteIdentifier).join(", ");
1389
+ };
1390
+ const normalizeRoles = (roles) => {
1391
+ if (!roles) return;
1392
+ return Array.isArray(roles) ? roles : [roles];
1393
+ };
1394
+ const rolesEqual = (a, b) => {
1395
+ const left = normalizeRoles(a);
1396
+ const right = normalizeRoles(b);
1397
+ return JSON.stringify(left) === JSON.stringify(right);
1398
+ };
1399
+ const rawSql = (_migration, sql, values) => sql.toSQL({ values });
1400
+ const createPolicySql = (migration, tableName, policyName, params) => {
1401
+ const [schema, table] = getSchemaAndTableFromName(migration.adapter.getSchema(), tableName);
1402
+ const values = [];
1403
+ const rolesSql = quotedRoles(params.to);
1404
+ let usingSql;
1405
+ let withCheckSql;
1406
+ if (params.for === "SELECT" || params.for === "DELETE") usingSql = rawSql(migration, params.using, values);
1407
+ else if (params.for === "INSERT") withCheckSql = rawSql(migration, params.withCheck, values);
1408
+ else {
1409
+ if (!params.withCheck) throw new Error("WITH CHECK is required for ALL and UPDATE policies");
1410
+ usingSql = rawSql(migration, params.using, values);
1411
+ withCheckSql = rawSql(migration, params.withCheck, values);
1412
+ }
1413
+ return {
1414
+ text: `CREATE POLICY ${quoteIdentifier(policyName)}
1415
+ ON ${quoteTable(schema, table)}
1416
+ AS ${params.as}
1417
+ FOR ${params.for ?? "ALL"}${rolesSql ? `
1418
+ TO ${rolesSql}` : ""}${usingSql ? `
1419
+ USING (${usingSql})` : ""}${withCheckSql ? `
1420
+ WITH CHECK (${withCheckSql})` : ""}`,
1421
+ values
1422
+ };
1423
+ };
1424
+ const dropPolicySql = (migration, tableName, policyName) => {
1425
+ const [schema, table] = getSchemaAndTableFromName(migration.adapter.getSchema(), tableName);
1426
+ return `DROP POLICY ${quoteIdentifier(policyName)} ON ${quoteTable(schema, table)}`;
1427
+ };
1428
+ const createOrDropPolicy = async (migration, up, tableName, policyName, params) => {
1429
+ if (up) {
1430
+ const { text, values } = createPolicySql(migration, tableName, policyName, params);
1431
+ await migration.adapter.arrays(text, values);
1432
+ } else await migration.adapter.arrays(dropPolicySql(migration, tableName, policyName));
1433
+ };
1434
+ const isRecreateDefinition = (value) => {
1435
+ return "as" in value || "for" in value || "table" in value;
1436
+ };
1437
+ const changePolicyInPlace = async (migration, tableName, policyName, from, to) => {
1438
+ const [schema, table] = getSchemaAndTableFromName(migration.adapter.getSchema(), tableName);
1439
+ const quotedTable = quoteTable(schema, table);
1440
+ let currentName = from.name ?? policyName;
1441
+ const targetName = to.name ?? policyName;
1442
+ if (currentName !== targetName) {
1443
+ await migration.adapter.arrays(`ALTER POLICY ${quoteIdentifier(currentName)}
1444
+ ON ${quotedTable}
1445
+ RENAME TO ${quoteIdentifier(targetName)}`);
1446
+ currentName = targetName;
1447
+ }
1448
+ if ("to" in from && "to" in to && !rolesEqual(from.to, to.to)) await migration.adapter.arrays(`ALTER POLICY ${quoteIdentifier(currentName)}
1449
+ ON ${quotedTable}
1450
+ TO ${quotedRoles(to.to) ?? "PUBLIC"}`);
1451
+ if ("using" in from && "using" in to && from.using && to.using) {
1452
+ const fromUsing = rawSql(migration, from.using, []);
1453
+ const toUsingValues = [];
1454
+ const toUsing = rawSql(migration, to.using, toUsingValues);
1455
+ if (fromUsing !== toUsing) await migration.adapter.arrays(`ALTER POLICY ${quoteIdentifier(currentName)}
1456
+ ON ${quotedTable}
1457
+ USING (${toUsing})`, toUsingValues);
1458
+ }
1459
+ if ("withCheck" in from && "withCheck" in to && from.withCheck && to.withCheck) {
1460
+ const fromWithCheck = rawSql(migration, from.withCheck, []);
1461
+ const toWithCheckValues = [];
1462
+ const toWithCheck = rawSql(migration, to.withCheck, toWithCheckValues);
1463
+ if (fromWithCheck !== toWithCheck) await migration.adapter.arrays(`ALTER POLICY ${quoteIdentifier(currentName)}
1464
+ ON ${quotedTable}
1465
+ WITH CHECK (${toWithCheck})`, toWithCheckValues);
1466
+ }
1467
+ };
1468
+ const recreatePolicy = async (migration, tableName, policyName, from, to) => {
1469
+ const fromTable = from.table ?? tableName;
1470
+ const fromName = from.name ?? policyName;
1471
+ await migration.adapter.arrays(dropPolicySql(migration, fromTable, fromName));
1472
+ const { text, values } = createPolicySql(migration, to.table ?? tableName, to.name ?? policyName, to);
1473
+ await migration.adapter.arrays(text, values);
1474
+ };
1475
+ const changePolicy = async (migration, up, tableName, policyName, params) => {
1476
+ const from = up ? params.from : params.to;
1477
+ const to = up ? params.to : params.from;
1478
+ if (isRecreateDefinition(from) || isRecreateDefinition(to)) await recreatePolicy(migration, tableName, policyName, from, to);
1479
+ else await changePolicyInPlace(migration, tableName, policyName, from, to);
1480
+ };
1481
+ const dropOrCreatePolicy = (migration, up, tableName, policyName, params) => {
1482
+ return createOrDropPolicy(migration, !up, tableName, policyName, params);
1483
+ };
1484
+ const concreteTargetKeyToSql = {
1485
+ tables: "TABLE",
1486
+ sequences: "SEQUENCE",
1487
+ routines: "ROUTINE",
1488
+ types: "TYPE",
1489
+ domains: "DOMAIN"
1490
+ };
1491
+ const schemaWideTargetKeyToSql = {
1492
+ allTablesIn: "ALL TABLES IN SCHEMA",
1493
+ allSequencesIn: "ALL SEQUENCES IN SCHEMA",
1494
+ allRoutinesIn: "ALL ROUTINES IN SCHEMA"
1495
+ };
1496
+ const schemaOrDatabaseTargetKeyToSql = {
1497
+ schemas: "SCHEMA",
1498
+ databases: "DATABASE"
1499
+ };
1500
+ const specialRoleSpecs = new Set([
1501
+ "PUBLIC",
1502
+ "CURRENT_ROLE",
1503
+ "CURRENT_USER",
1504
+ "SESSION_USER"
1505
+ ]);
1506
+ const changeGrant = async (migration, up, params) => {
1507
+ const sql = privilegeToSql(migration, {
1508
+ ...params,
1509
+ to: typeof params.to === "string" ? [params.to] : params.to,
1510
+ action: up ? "grant" : "revoke"
1511
+ });
1512
+ if (sql.length) await migration.adapter.arrays(sql.join(";\n"));
1513
+ };
1514
+ const privilegeToSql = (migration, ast) => {
1515
+ const queries = [];
1516
+ const isRevoke = ast.action === "revoke";
1517
+ const currentSchema = migration.adapter.getSchema();
1518
+ for (const key of [
1519
+ "tables",
1520
+ "sequences",
1521
+ "routines",
1522
+ "types",
1523
+ "domains"
1524
+ ]) {
1525
+ const targetNames = ast[key];
1526
+ if (!targetNames?.length) continue;
1527
+ const privileges = ast.privileges;
1528
+ const grantablePrivileges = ast.grantablePrivileges;
1529
+ const quotedTargets = targetNames.map((name) => {
1530
+ const [schema, objName] = getSchemaAndTableFromName(currentSchema, name);
1531
+ if (schema) return `"${schema}"."${objName}"`;
1532
+ return `"${objName}"`;
1533
+ });
1534
+ addTargetQueries(queries, ast, `ON ${concreteTargetKeyToSql[key]} ${quotedTargets.join(", ")}`, privileges, grantablePrivileges, isRevoke);
1535
+ }
1536
+ for (const key of [
1537
+ "allTablesIn",
1538
+ "allSequencesIn",
1539
+ "allRoutinesIn"
1540
+ ]) {
1541
+ const targetNames = ast[key];
1542
+ if (!targetNames?.length) continue;
1543
+ const targetList = targetNames.map((name) => `"${name}"`);
1544
+ const privileges = ast.privileges;
1545
+ const grantablePrivileges = ast.grantablePrivileges;
1546
+ addTargetQueries(queries, ast, `ON ${schemaWideTargetKeyToSql[key]} ${targetList.join(", ")}`, privileges, grantablePrivileges, isRevoke);
1547
+ }
1548
+ for (const key of ["schemas", "databases"]) {
1549
+ const targetNames = ast[key];
1550
+ if (!targetNames?.length) continue;
1551
+ const targetList = targetNames.map((name) => `"${name}"`);
1552
+ const privileges = ast.privileges;
1553
+ const grantablePrivileges = ast.grantablePrivileges;
1554
+ addTargetQueries(queries, ast, `ON ${schemaOrDatabaseTargetKeyToSql[key]} ${targetList.join(", ")}`, privileges, grantablePrivileges, isRevoke);
1555
+ }
1556
+ return queries;
1557
+ };
1558
+ const addTargetQueries = (queries, ast, targetSql, privileges, grantablePrivileges, isRevoke) => {
1559
+ if (privileges?.length) queries.push(buildQuery(ast, privileges, targetSql, false, isRevoke));
1560
+ if (grantablePrivileges?.length) queries.push(buildQuery(ast, grantablePrivileges, targetSql, !isRevoke, isRevoke));
1561
+ };
1562
+ const buildQuery = (ast, privileges, targetSql, grantable, isRevoke) => {
1563
+ const parts = [];
1564
+ if (isRevoke) {
1565
+ parts.push("REVOKE");
1566
+ if (grantable) parts.push("GRANT OPTION FOR");
1567
+ } else parts.push("GRANT");
1568
+ const privilegeList = privileges.map((p) => p === "ALL" ? "ALL PRIVILEGES" : p === "TEMP" ? "TEMPORARY" : p).join(", ");
1569
+ parts.push(privilegeList);
1570
+ parts.push(targetSql);
1571
+ if (isRevoke) parts.push("FROM");
1572
+ else parts.push("TO");
1573
+ parts.push(ast.to.map(formatRoleSpec).join(", "));
1574
+ if (ast.grantedBy) parts.push("GRANTED BY", `"${ast.grantedBy}"`);
1575
+ if (isRevoke && ast.revokeMode) parts.push(ast.revokeMode);
1576
+ if (!isRevoke && grantable) parts.push("WITH GRANT OPTION");
1577
+ return parts.join(" ");
1578
+ };
1579
+ const formatRoleSpec = (role) => {
1580
+ return specialRoleSpecs.has(role) ? role : `"${role}"`;
1581
+ };
1385
1582
  /**
1386
1583
  * Creates a new `db` instance that is an instance of `pqb` with mixed in migration methods from the `Migration` class.
1387
1584
  * It overrides `query` and `array` db adapter methods to intercept SQL for the logging.
@@ -2255,6 +2452,21 @@ var Migration = class {
2255
2452
  noForceRls(tableName) {
2256
2453
  return forceOrNoForceRls(this, !this.up, tableName);
2257
2454
  }
2455
+ createPolicy(tableName, policyName, params) {
2456
+ return createOrDropPolicy(this, this.up, tableName, policyName, params);
2457
+ }
2458
+ dropPolicy(tableName, policyName, params) {
2459
+ return dropOrCreatePolicy(this, this.up, tableName, policyName, params);
2460
+ }
2461
+ changePolicy(tableName, policyName, params) {
2462
+ return changePolicy(this, this.up, tableName, policyName, params);
2463
+ }
2464
+ grant(params) {
2465
+ return changeGrant(this, this.up, params);
2466
+ }
2467
+ revoke(params) {
2468
+ return changeGrant(this, !this.up, params);
2469
+ }
2258
2470
  };
2259
2471
  const wrapWithEnhancingError = async (text, values, promise) => {
2260
2472
  try {
@@ -3599,6 +3811,38 @@ const astToGenerateItem = (config, ast, currentSchema) => {
3599
3811
  deps.push(schema, `${schema}.${ast.table}`);
3600
3812
  break;
3601
3813
  }
3814
+ case "policy": {
3815
+ const defaultSchema = ast.schema ?? currentSchema;
3816
+ const [tableSchema = defaultSchema, tableName] = getSchemaAndTableFromName(defaultSchema, ast.table);
3817
+ deps.push(tableSchema, `${tableSchema}.${tableName}`);
3818
+ pushPolicyRoleDeps(deps, ast.to);
3819
+ if (ast.action === "create") add.push(`policy:${ast.name}`);
3820
+ else drop.push(`policy:${ast.name}`);
3821
+ break;
3822
+ }
3823
+ case "changePolicy": {
3824
+ const defaultSchema = ast.schema ?? currentSchema;
3825
+ const [tableSchema = defaultSchema, tableName] = getSchemaAndTableFromName(defaultSchema, ast.table);
3826
+ deps.push(tableSchema, `${tableSchema}.${tableName}`);
3827
+ const fromTable = ast.from.table ? ast.from.table : tableName;
3828
+ const fromName = ast.from.name ? ast.from.name : ast.name;
3829
+ const toTable = ast.to.table ? ast.to.table : tableName;
3830
+ const toName = ast.to.name ? ast.to.name : ast.name;
3831
+ const [fromSchema = tableSchema, fromTableName] = getSchemaAndTableFromName(tableSchema, fromTable);
3832
+ const [toSchema = tableSchema, toTableName] = getSchemaAndTableFromName(tableSchema, toTable);
3833
+ deps.push(fromSchema, `${fromSchema}.${fromTableName}`);
3834
+ deps.push(toSchema, `${toSchema}.${toTableName}`);
3835
+ if (fromName !== toName) {
3836
+ drop.push(`policy:${fromName}`);
3837
+ add.push(`policy:${toName}`);
3838
+ }
3839
+ pushPolicyRoleDeps(deps, ast.from.to);
3840
+ pushPolicyRoleDeps(deps, ast.to.to);
3841
+ break;
3842
+ }
3843
+ case "grant":
3844
+ pushGrantDeps(currentSchema, deps, ast);
3845
+ break;
3602
3846
  default: exhaustive(ast);
3603
3847
  }
3604
3848
  return {
@@ -3662,6 +3906,39 @@ const analyzeTableData = (config, currentSchema, schema, table, keys, deps, data
3662
3906
  }
3663
3907
  }
3664
3908
  };
3909
+ const pushPolicyRoleDeps = (deps, roles) => {
3910
+ if (!roles) return;
3911
+ deps.push(...roles.map((role) => `role:${role}`));
3912
+ };
3913
+ const grantConcreteTargetKeys = [
3914
+ "tables",
3915
+ "sequences",
3916
+ "routines",
3917
+ "types",
3918
+ "domains"
3919
+ ];
3920
+ const grantSchemaWideTargetKeys = [
3921
+ "allTablesIn",
3922
+ "allSequencesIn",
3923
+ "allRoutinesIn"
3924
+ ];
3925
+ const pushGrantDeps = (currentSchema, deps, ast) => {
3926
+ deps.push(...ast.to.map((role) => `role:${role}`));
3927
+ if (ast.grantedBy) deps.push(`role:${ast.grantedBy}`);
3928
+ if (ast.schemas) deps.push(...ast.schemas);
3929
+ for (const key of grantSchemaWideTargetKeys) {
3930
+ const schemas = ast[key];
3931
+ if (schemas) deps.push(...schemas);
3932
+ }
3933
+ for (const key of grantConcreteTargetKeys) {
3934
+ const targets = ast[key];
3935
+ if (!targets) continue;
3936
+ for (const target of targets) {
3937
+ const [schema = currentSchema, name] = getSchemaAndTableFromName(currentSchema, target);
3938
+ deps.push(schema, `${schema}.${name}`);
3939
+ }
3940
+ }
3941
+ };
3665
3942
  const astToMigration = (currentSchema, config, asts) => {
3666
3943
  const items = astToGenerateItems(config, asts, currentSchema);
3667
3944
  const toBeAdded = /* @__PURE__ */ new Set();
@@ -4071,8 +4348,91 @@ const astEncoders = {
4071
4348
  name: ast.table
4072
4349
  }, currentSchema);
4073
4350
  return `await db.${ast.action === "enable" ? "enableRls" : ast.action === "disable" ? "disableRls" : ast.action === "force" ? "forceRls" : "noForceRls"}(${table});`;
4351
+ },
4352
+ policy(ast, _config, currentSchema) {
4353
+ const table = quoteSchemaTable({
4354
+ schema: ast.schema,
4355
+ name: ast.table
4356
+ }, currentSchema);
4357
+ return [
4358
+ `await db.${ast.action}Policy(${table}, ${singleQuote(ast.name)}, {`,
4359
+ policyDefinitionToCode(ast),
4360
+ "});"
4361
+ ];
4362
+ },
4363
+ changePolicy(ast, _config, currentSchema) {
4364
+ return [
4365
+ `await db.changePolicy(${quoteSchemaTable({
4366
+ schema: ast.schema,
4367
+ name: ast.table
4368
+ }, currentSchema)}, ${singleQuote(ast.name)}, {`,
4369
+ [
4370
+ "from: {",
4371
+ policyChangeDefinitionToCode(ast.from),
4372
+ "},",
4373
+ "to: {",
4374
+ policyChangeDefinitionToCode(ast.to),
4375
+ "},"
4376
+ ],
4377
+ "});"
4378
+ ];
4379
+ },
4380
+ grant(ast) {
4381
+ const props = [];
4382
+ props.push(`to: [${ast.to.map(singleQuote).join(", ")}],`);
4383
+ for (const key of grantTargetKeys) {
4384
+ const values = ast[key];
4385
+ if (values?.length) props.push(`${key}: [${values.map(singleQuote).join(", ")}],`);
4386
+ }
4387
+ if (ast.privileges?.length) props.push(`privileges: [${ast.privileges.map(singleQuote).join(", ")}],`);
4388
+ if (ast.grantablePrivileges?.length) props.push(`grantablePrivileges: [${ast.grantablePrivileges.map(singleQuote).join(", ")}],`);
4389
+ if (ast.grantedBy) props.push(`grantedBy: ${singleQuote(ast.grantedBy)},`);
4390
+ if (ast.revokeMode) props.push(`revokeMode: ${singleQuote(ast.revokeMode)},`);
4391
+ return [
4392
+ `await db.${ast.action}({`,
4393
+ props,
4394
+ "});"
4395
+ ];
4074
4396
  }
4075
4397
  };
4398
+ const grantTargetKeys = [
4399
+ "schemas",
4400
+ "tables",
4401
+ "sequences",
4402
+ "routines",
4403
+ "types",
4404
+ "domains",
4405
+ "databases",
4406
+ "allTablesIn",
4407
+ "allSequencesIn",
4408
+ "allRoutinesIn"
4409
+ ];
4410
+ const policyDefinitionToCode = (policy) => {
4411
+ const code = [`as: ${singleQuote(policy.as)},`];
4412
+ if (policy.for) code.push(`for: ${singleQuote(policy.for)},`);
4413
+ if (policy.to?.length) code.push(`to: [${policy.to.map(singleQuote).join(", ")}],`);
4414
+ if (policy.using) code.push(`using: ${rawSqlStringToCode(policy.using)},`);
4415
+ if (policy.withCheck) code.push(`withCheck: ${rawSqlStringToCode(policy.withCheck)},`);
4416
+ return code;
4417
+ };
4418
+ const policyChangeDefinitionToCode = (policy) => {
4419
+ const code = [];
4420
+ if ("table" in policy && policy.table) code.push(`table: ${singleQuote(policy.table)},`);
4421
+ if (policy.name) code.push(`name: ${singleQuote(policy.name)},`);
4422
+ if (isPolicyRecreateDefinition(policy)) code.push(...policyDefinitionToCode(policy));
4423
+ else {
4424
+ if (policy.to?.length) code.push(`to: [${policy.to.map(singleQuote).join(", ")}],`);
4425
+ if (policy.using) code.push(`using: ${rawSqlStringToCode(policy.using)},`);
4426
+ if (policy.withCheck) code.push(`withCheck: ${rawSqlStringToCode(policy.withCheck)},`);
4427
+ }
4428
+ return code;
4429
+ };
4430
+ const isPolicyRecreateDefinition = (policy) => {
4431
+ return policy.as !== void 0 || policy.for !== void 0 || policy.table !== void 0;
4432
+ };
4433
+ const rawSqlStringToCode = (sql) => {
4434
+ return `db.sql${backtickQuote(sql)}`;
4435
+ };
4076
4436
  const roleParams = (name, ast, compare, to) => {
4077
4437
  const params = [];
4078
4438
  for (const key of [
@@ -4534,7 +4894,154 @@ const defaultPrivilegesSql = `SELECT COALESCE(json_agg(t.*), '[]') FROM (
4534
4894
  JOIN LATERAL aclexplode(d.defaclacl) ae ON true
4535
4895
  GROUP BY "grantor", "grantee", "schema", "object"
4536
4896
  ) t`;
4537
- const sql = (version, params) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(tablesSql(params?.rls), "tables")}, ${jsonAgg(viewsSql, "views")}, ${jsonAgg(indexesSql, "indexes")}, ${jsonAgg(constraintsSql, "constraints")}, ${jsonAgg(triggersSql, "triggers")}, ${jsonAgg(extensionsSql, "extensions")}, ${jsonAgg(enumsSql, "enums")}, ${jsonAgg(domainsSql, "domains")}, ${jsonAgg(collationsSql(version), "collations")}${params?.roles ? `, (${roleSql(params.roles)}) AS "roles"` : ""}${params?.loadDefaultPrivileges ? `, (${defaultPrivilegesSql}) AS "defaultPrivileges"` : ""}`;
4897
+ const grantsSql = `SELECT COALESCE(json_agg(t.* ORDER BY t."target", t."schema", t."name", t."grantee", t."grantor"), '[]') FROM (
4898
+ SELECT
4899
+ (ae.grantor)::regrole "grantor",
4900
+ CASE
4901
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4902
+ ELSE (ae.grantee)::regrole::text
4903
+ END "grantee",
4904
+ n.nspname "schema",
4905
+ c.relname "name",
4906
+ 'tables' "target",
4907
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4908
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4909
+ FROM pg_class c
4910
+ JOIN pg_namespace n ON n.oid = c.relnamespace
4911
+ JOIN LATERAL aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) ae ON true
4912
+ WHERE c.relkind IN ('r', 'p')
4913
+ AND ${filterSchema("n.nspname")}
4914
+ AND ae.grantee <> c.relowner
4915
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4916
+
4917
+ UNION ALL
4918
+
4919
+ SELECT
4920
+ (ae.grantor)::regrole "grantor",
4921
+ CASE
4922
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4923
+ ELSE (ae.grantee)::regrole::text
4924
+ END "grantee",
4925
+ n.nspname "schema",
4926
+ c.relname "name",
4927
+ 'sequences' "target",
4928
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4929
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4930
+ FROM pg_class c
4931
+ JOIN pg_namespace n ON n.oid = c.relnamespace
4932
+ JOIN LATERAL aclexplode(coalesce(c.relacl, acldefault('S', c.relowner))) ae ON true
4933
+ WHERE c.relkind = 'S'
4934
+ AND ${filterSchema("n.nspname")}
4935
+ AND ae.grantee <> c.relowner
4936
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4937
+
4938
+ UNION ALL
4939
+
4940
+ SELECT
4941
+ (ae.grantor)::regrole "grantor",
4942
+ CASE
4943
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4944
+ ELSE (ae.grantee)::regrole::text
4945
+ END "grantee",
4946
+ n.nspname "schema",
4947
+ p.proname "name",
4948
+ 'routines' "target",
4949
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4950
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4951
+ FROM pg_proc p
4952
+ JOIN pg_namespace n ON n.oid = p.pronamespace
4953
+ JOIN LATERAL aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) ae ON true
4954
+ WHERE ${filterSchema("n.nspname")}
4955
+ AND ae.grantee <> p.proowner
4956
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4957
+
4958
+ UNION ALL
4959
+
4960
+ SELECT
4961
+ (ae.grantor)::regrole "grantor",
4962
+ CASE
4963
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4964
+ ELSE (ae.grantee)::regrole::text
4965
+ END "grantee",
4966
+ n.nspname "schema",
4967
+ t.typname "name",
4968
+ CASE WHEN t.typtype = 'd' THEN 'domains' ELSE 'types' END "target",
4969
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4970
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4971
+ FROM pg_type t
4972
+ JOIN pg_namespace n ON n.oid = t.typnamespace
4973
+ JOIN LATERAL aclexplode(coalesce(t.typacl, acldefault('T', t.typowner))) ae ON true
4974
+ WHERE t.typtype IN ('c', 'd', 'e', 'm', 'r')
4975
+ AND ${filterSchema("n.nspname")}
4976
+ AND ae.grantee <> t.typowner
4977
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4978
+
4979
+ UNION ALL
4980
+
4981
+ SELECT
4982
+ (ae.grantor)::regrole "grantor",
4983
+ CASE
4984
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4985
+ ELSE (ae.grantee)::regrole::text
4986
+ END "grantee",
4987
+ NULL "schema",
4988
+ n.nspname "name",
4989
+ 'schemas' "target",
4990
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4991
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4992
+ FROM pg_namespace n
4993
+ JOIN LATERAL aclexplode(coalesce(n.nspacl, acldefault('n', n.nspowner))) ae ON true
4994
+ WHERE ${filterSchema("n.nspname")}
4995
+ AND ae.grantee <> n.nspowner
4996
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4997
+
4998
+ UNION ALL
4999
+
5000
+ SELECT
5001
+ (ae.grantor)::regrole "grantor",
5002
+ CASE
5003
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
5004
+ ELSE (ae.grantee)::regrole::text
5005
+ END "grantee",
5006
+ NULL "schema",
5007
+ d.datname "name",
5008
+ 'databases' "target",
5009
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
5010
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
5011
+ FROM pg_database d
5012
+ JOIN LATERAL aclexplode(coalesce(d.datacl, acldefault('d', d.datdba))) ae ON true
5013
+ WHERE NOT d.datistemplate
5014
+ AND ae.grantee <> d.datdba
5015
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
5016
+ ) t`;
5017
+ const policiesSql = `SELECT
5018
+ n.nspname AS "schemaName",
5019
+ c.relname AS "tableName",
5020
+ p.polname AS "name",
5021
+ CASE WHEN p.polpermissive
5022
+ THEN 'PERMISSIVE'
5023
+ ELSE 'RESTRICTIVE'
5024
+ END AS "mode",
5025
+ CASE p.polcmd
5026
+ WHEN '*' THEN 'ALL'
5027
+ WHEN 'r' THEN 'SELECT'
5028
+ WHEN 'a' THEN 'INSERT'
5029
+ WHEN 'w' THEN 'UPDATE'
5030
+ WHEN 'd' THEN 'DELETE'
5031
+ END AS "command",
5032
+ ARRAY(
5033
+ SELECT COALESCE(r.rolname, 'public')
5034
+ FROM unnest(p.polroles) roleOid
5035
+ LEFT JOIN pg_roles r ON r.oid = roleOid
5036
+ ORDER BY roleOid = 0 DESC, COALESCE(r.rolname, 'public')
5037
+ ) AS "roles",
5038
+ pg_get_expr(p.polqual, p.polrelid) AS "using",
5039
+ pg_get_expr(p.polwithcheck, p.polrelid) AS "withCheck"
5040
+ FROM pg_policy p
5041
+ JOIN pg_class c ON c.oid = p.polrelid
5042
+ JOIN pg_namespace n ON n.oid = c.relnamespace
5043
+ ORDER BY n.nspname, c.relname, p.polname`;
5044
+ const sql = (version, params) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(tablesSql(params?.rls), "tables")}, ${jsonAgg(viewsSql, "views")}, ${jsonAgg(indexesSql, "indexes")}, ${jsonAgg(constraintsSql, "constraints")}, ${jsonAgg(triggersSql, "triggers")}, ${jsonAgg(extensionsSql, "extensions")}, ${jsonAgg(enumsSql, "enums")}, ${jsonAgg(domainsSql, "domains")}, ${jsonAgg(collationsSql(version), "collations")}${params?.roles ? `, (${roleSql(params.roles)}) AS "roles"` : ""}${params?.loadDefaultPrivileges ? `, (${defaultPrivilegesSql}) AS "defaultPrivileges"` : ""}${params?.loadGrants ? `, (${grantsSql}) AS "grants"` : ""}${params?.rls ? `, ${jsonAgg(policiesSql, "policies")}` : ""}`;
4538
5045
  async function getDbVersion(db) {
4539
5046
  const { rows: [{ version: versionString }] } = await db.query("SELECT version()");
4540
5047
  return +versionString.match(/\d+/)[0];
@@ -4610,9 +5117,36 @@ async function introspectDbSchema(db, params) {
4610
5117
  }));
4611
5118
  }
4612
5119
  }
5120
+ if (raw.policies) {
5121
+ const policiesByTable = raw.policies.reduce((acc, policy) => {
5122
+ nullsToUndefined(policy);
5123
+ const key = `${policy.schemaName}.${policy.tableName}`;
5124
+ const existing = acc.get(key);
5125
+ const mappedPolicy = {
5126
+ schemaName: policy.schemaName,
5127
+ tableName: policy.tableName,
5128
+ name: policy.name,
5129
+ mode: policy.mode,
5130
+ command: policy.command,
5131
+ roles: policy.roles,
5132
+ using: policy.using,
5133
+ withCheck: policy.withCheck
5134
+ };
5135
+ if (existing) existing.push(mappedPolicy);
5136
+ else acc.set(key, [mappedPolicy]);
5137
+ return acc;
5138
+ }, /* @__PURE__ */ new Map());
5139
+ for (const table of raw.tables) {
5140
+ if (!table.rls) continue;
5141
+ table.rls.policies = policiesByTable.get(`${table.schemaName}.${table.name}`) ?? [];
5142
+ }
5143
+ }
5144
+ const grants = raw.grants?.map(mapRawGrant);
5145
+ const { policies: _policies, grants: _grants, ...structureWithoutPolicies } = raw;
4613
5146
  return {
4614
5147
  version,
4615
- ...raw,
5148
+ ...structureWithoutPolicies,
5149
+ grants,
4616
5150
  defaultPrivileges: raw.defaultPrivileges && [...raw.defaultPrivileges.reduce((acc, privilege) => {
4617
5151
  nullsToUndefined(privilege);
4618
5152
  const key = `${privilege.grantor}.${privilege.grantee}.${privilege.schema}`;
@@ -4637,6 +5171,20 @@ async function introspectDbSchema(db, params) {
4637
5171
  }, /* @__PURE__ */ new Map()).values()]
4638
5172
  };
4639
5173
  }
5174
+ const mapRawGrant = (raw) => {
5175
+ nullsToUndefined(raw);
5176
+ const privileges = [];
5177
+ const grantablePrivileges = [];
5178
+ for (let i = 0; i < raw.privileges.length; i++) (raw.isGrantables[i] ? grantablePrivileges : privileges).push(raw.privileges[i]);
5179
+ const grant = {
5180
+ to: [raw.grantee],
5181
+ grantedBy: raw.grantor,
5182
+ [raw.target]: raw.schema ? [`${raw.schema}.${raw.name}`] : [raw.name]
5183
+ };
5184
+ if (privileges.length) grant.privileges = privileges;
5185
+ if (grantablePrivileges.length) grant.grantablePrivileges = grantablePrivileges;
5186
+ return grant;
5187
+ };
4640
5188
  const nullsToUndefined = (obj) => {
4641
5189
  for (const key in obj) if (obj[key] === null) obj[key] = void 0;
4642
5190
  };
@@ -4661,7 +5209,7 @@ const makeStructureToAstCtx = (config, currentSchema) => ({
4661
5209
  });
4662
5210
  const structureToAst = async (ctx, adapter, config) => {
4663
5211
  const ast = [];
4664
- const data = await introspectDbSchema(adapter);
5212
+ const data = await introspectDbSchema(adapter, { rls: true });
4665
5213
  for (const name of data.schemas) {
4666
5214
  if (name === "public") continue;
4667
5215
  ast.push({
@@ -4681,6 +5229,18 @@ const structureToAst = async (ctx, adapter, config) => {
4681
5229
  for (const table of data.tables) {
4682
5230
  if (table.name === migrationsTable && table.schemaName === migrationsSchema) continue;
4683
5231
  ast.push(tableToAst(ctx, data, table, "create", domains));
5232
+ for (const policy of table.rls?.policies || []) ast.push({
5233
+ type: "policy",
5234
+ action: "create",
5235
+ schema: table.schemaName === ctx.currentSchema ? void 0 : table.schemaName,
5236
+ table: table.name,
5237
+ name: policy.name,
5238
+ as: policy.mode,
5239
+ for: policy.command,
5240
+ to: policy.roles,
5241
+ using: policy.using,
5242
+ withCheck: policy.withCheck
5243
+ });
4684
5244
  if (table.rls?.enable) ast.push({
4685
5245
  type: "tableRls",
4686
5246
  action: "enable",