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.js CHANGED
@@ -1166,13 +1166,12 @@ const astToQuery$1 = (ast) => {
1166
1166
  if (options?.recursive) sql.push("RECURSIVE");
1167
1167
  sql.push(`VIEW ${sqlName}`);
1168
1168
  if (options?.columns) sql.push(`(${options.columns.map((column) => `"${column}"`).join(", ")})`);
1169
- if (options?.with) {
1170
- const list = [];
1171
- if (options.with.checkOption) list.push(`check_option = ${(0, pqb_internal.singleQuote)(options.with.checkOption)}`);
1172
- if (options.with.securityBarrier) list.push(`security_barrier = true`);
1173
- if (options.with.securityInvoker) list.push(`security_invoker = true`);
1174
- sql.push(`WITH ( ${list.join(", ")} )`);
1175
- }
1169
+ const withOptions = options?.with;
1170
+ const list = [];
1171
+ if (withOptions?.checkOption) list.push(`check_option = ${(0, pqb_internal.singleQuote)(withOptions.checkOption)}`);
1172
+ if (withOptions?.securityBarrier) list.push(`security_barrier = true`);
1173
+ if (withOptions?.securityInvoker !== false) list.push(`security_invoker = true`);
1174
+ if (list.length) sql.push(`WITH ( ${list.join(", ")} )`);
1176
1175
  sql.push(`AS (${ast.sql.toSQL({ values })})`);
1177
1176
  } else {
1178
1177
  sql.push("DROP VIEW");
@@ -1373,12 +1372,12 @@ const objectConfigToSql = (ast, config, action) => {
1373
1372
  const objectType = key;
1374
1373
  if (!value) continue;
1375
1374
  const { privileges, grantablePrivileges } = value;
1376
- if (privileges?.length) queries.push(buildQuery(ast, objectType, privileges, false, action));
1377
- if (grantablePrivileges?.length) queries.push(buildQuery(ast, objectType, grantablePrivileges, true, action));
1375
+ if (privileges?.length) queries.push(buildQuery$1(ast, objectType, privileges, false, action));
1376
+ if (grantablePrivileges?.length) queries.push(buildQuery$1(ast, objectType, grantablePrivileges, true, action));
1378
1377
  }
1379
1378
  return queries;
1380
1379
  };
1381
- const buildQuery = (ast, objectType, privileges, grantable, action) => {
1380
+ const buildQuery$1 = (ast, objectType, privileges, grantable, action) => {
1382
1381
  const parts = ["ALTER DEFAULT PRIVILEGES"];
1383
1382
  if (ast.owner) parts.push(`FOR ROLE "${ast.owner}"`);
1384
1383
  if (ast.schema) parts.push(`IN SCHEMA "${ast.schema}"`);
@@ -1405,6 +1404,204 @@ const enableOrDisableRls = (migration, up, tableName) => {
1405
1404
  const forceOrNoForceRls = (migration, up, tableName) => {
1406
1405
  return setRls(migration, tableName, up ? "force" : "noForce");
1407
1406
  };
1407
+ const quotedRoles = (roles) => {
1408
+ if (!roles) return;
1409
+ const arr = Array.isArray(roles) ? roles : [roles];
1410
+ if (!arr.length) return;
1411
+ return arr.map(pqb_internal.quoteIdentifier).join(", ");
1412
+ };
1413
+ const normalizeRoles = (roles) => {
1414
+ if (!roles) return;
1415
+ return Array.isArray(roles) ? roles : [roles];
1416
+ };
1417
+ const rolesEqual = (a, b) => {
1418
+ const left = normalizeRoles(a);
1419
+ const right = normalizeRoles(b);
1420
+ return JSON.stringify(left) === JSON.stringify(right);
1421
+ };
1422
+ const rawSql = (_migration, sql, values) => sql.toSQL({ values });
1423
+ const createPolicySql = (migration, tableName, policyName, params) => {
1424
+ const [schema, table] = getSchemaAndTableFromName(migration.adapter.getSchema(), tableName);
1425
+ const values = [];
1426
+ const rolesSql = quotedRoles(params.to);
1427
+ let usingSql;
1428
+ let withCheckSql;
1429
+ if (params.for === "SELECT" || params.for === "DELETE") usingSql = rawSql(migration, params.using, values);
1430
+ else if (params.for === "INSERT") withCheckSql = rawSql(migration, params.withCheck, values);
1431
+ else {
1432
+ if (!params.withCheck) throw new Error("WITH CHECK is required for ALL and UPDATE policies");
1433
+ usingSql = rawSql(migration, params.using, values);
1434
+ withCheckSql = rawSql(migration, params.withCheck, values);
1435
+ }
1436
+ return {
1437
+ text: `CREATE POLICY ${(0, pqb_internal.quoteIdentifier)(policyName)}
1438
+ ON ${quoteTable(schema, table)}
1439
+ AS ${params.as}
1440
+ FOR ${params.for ?? "ALL"}${rolesSql ? `
1441
+ TO ${rolesSql}` : ""}${usingSql ? `
1442
+ USING (${usingSql})` : ""}${withCheckSql ? `
1443
+ WITH CHECK (${withCheckSql})` : ""}`,
1444
+ values
1445
+ };
1446
+ };
1447
+ const dropPolicySql = (migration, tableName, policyName) => {
1448
+ const [schema, table] = getSchemaAndTableFromName(migration.adapter.getSchema(), tableName);
1449
+ return `DROP POLICY ${(0, pqb_internal.quoteIdentifier)(policyName)} ON ${quoteTable(schema, table)}`;
1450
+ };
1451
+ const createOrDropPolicy = async (migration, up, tableName, policyName, params) => {
1452
+ if (up) {
1453
+ const { text, values } = createPolicySql(migration, tableName, policyName, params);
1454
+ await migration.adapter.arrays(text, values);
1455
+ } else await migration.adapter.arrays(dropPolicySql(migration, tableName, policyName));
1456
+ };
1457
+ const isRecreateDefinition = (value) => {
1458
+ return "as" in value || "for" in value || "table" in value;
1459
+ };
1460
+ const changePolicyInPlace = async (migration, tableName, policyName, from, to) => {
1461
+ const [schema, table] = getSchemaAndTableFromName(migration.adapter.getSchema(), tableName);
1462
+ const quotedTable = quoteTable(schema, table);
1463
+ let currentName = from.name ?? policyName;
1464
+ const targetName = to.name ?? policyName;
1465
+ if (currentName !== targetName) {
1466
+ await migration.adapter.arrays(`ALTER POLICY ${(0, pqb_internal.quoteIdentifier)(currentName)}
1467
+ ON ${quotedTable}
1468
+ RENAME TO ${(0, pqb_internal.quoteIdentifier)(targetName)}`);
1469
+ currentName = targetName;
1470
+ }
1471
+ if ("to" in from && "to" in to && !rolesEqual(from.to, to.to)) await migration.adapter.arrays(`ALTER POLICY ${(0, pqb_internal.quoteIdentifier)(currentName)}
1472
+ ON ${quotedTable}
1473
+ TO ${quotedRoles(to.to) ?? "PUBLIC"}`);
1474
+ if ("using" in from && "using" in to && from.using && to.using) {
1475
+ const fromUsing = rawSql(migration, from.using, []);
1476
+ const toUsingValues = [];
1477
+ const toUsing = rawSql(migration, to.using, toUsingValues);
1478
+ if (fromUsing !== toUsing) await migration.adapter.arrays(`ALTER POLICY ${(0, pqb_internal.quoteIdentifier)(currentName)}
1479
+ ON ${quotedTable}
1480
+ USING (${toUsing})`, toUsingValues);
1481
+ }
1482
+ if ("withCheck" in from && "withCheck" in to && from.withCheck && to.withCheck) {
1483
+ const fromWithCheck = rawSql(migration, from.withCheck, []);
1484
+ const toWithCheckValues = [];
1485
+ const toWithCheck = rawSql(migration, to.withCheck, toWithCheckValues);
1486
+ if (fromWithCheck !== toWithCheck) await migration.adapter.arrays(`ALTER POLICY ${(0, pqb_internal.quoteIdentifier)(currentName)}
1487
+ ON ${quotedTable}
1488
+ WITH CHECK (${toWithCheck})`, toWithCheckValues);
1489
+ }
1490
+ };
1491
+ const recreatePolicy = async (migration, tableName, policyName, from, to) => {
1492
+ const fromTable = from.table ?? tableName;
1493
+ const fromName = from.name ?? policyName;
1494
+ await migration.adapter.arrays(dropPolicySql(migration, fromTable, fromName));
1495
+ const { text, values } = createPolicySql(migration, to.table ?? tableName, to.name ?? policyName, to);
1496
+ await migration.adapter.arrays(text, values);
1497
+ };
1498
+ const changePolicy = async (migration, up, tableName, policyName, params) => {
1499
+ const from = up ? params.from : params.to;
1500
+ const to = up ? params.to : params.from;
1501
+ if (isRecreateDefinition(from) || isRecreateDefinition(to)) await recreatePolicy(migration, tableName, policyName, from, to);
1502
+ else await changePolicyInPlace(migration, tableName, policyName, from, to);
1503
+ };
1504
+ const dropOrCreatePolicy = (migration, up, tableName, policyName, params) => {
1505
+ return createOrDropPolicy(migration, !up, tableName, policyName, params);
1506
+ };
1507
+ const concreteTargetKeyToSql = {
1508
+ tables: "TABLE",
1509
+ sequences: "SEQUENCE",
1510
+ routines: "ROUTINE",
1511
+ types: "TYPE",
1512
+ domains: "DOMAIN"
1513
+ };
1514
+ const schemaWideTargetKeyToSql = {
1515
+ allTablesIn: "ALL TABLES IN SCHEMA",
1516
+ allSequencesIn: "ALL SEQUENCES IN SCHEMA",
1517
+ allRoutinesIn: "ALL ROUTINES IN SCHEMA"
1518
+ };
1519
+ const schemaOrDatabaseTargetKeyToSql = {
1520
+ schemas: "SCHEMA",
1521
+ databases: "DATABASE"
1522
+ };
1523
+ const specialRoleSpecs = new Set([
1524
+ "PUBLIC",
1525
+ "CURRENT_ROLE",
1526
+ "CURRENT_USER",
1527
+ "SESSION_USER"
1528
+ ]);
1529
+ const changeGrant = async (migration, up, params) => {
1530
+ const sql = privilegeToSql(migration, {
1531
+ ...params,
1532
+ to: typeof params.to === "string" ? [params.to] : params.to,
1533
+ action: up ? "grant" : "revoke"
1534
+ });
1535
+ if (sql.length) await migration.adapter.arrays(sql.join(";\n"));
1536
+ };
1537
+ const privilegeToSql = (migration, ast) => {
1538
+ const queries = [];
1539
+ const isRevoke = ast.action === "revoke";
1540
+ const currentSchema = migration.adapter.getSchema();
1541
+ for (const key of [
1542
+ "tables",
1543
+ "sequences",
1544
+ "routines",
1545
+ "types",
1546
+ "domains"
1547
+ ]) {
1548
+ const targetNames = ast[key];
1549
+ if (!targetNames?.length) continue;
1550
+ const privileges = ast.privileges;
1551
+ const grantablePrivileges = ast.grantablePrivileges;
1552
+ const quotedTargets = targetNames.map((name) => {
1553
+ const [schema, objName] = getSchemaAndTableFromName(currentSchema, name);
1554
+ if (schema) return `"${schema}"."${objName}"`;
1555
+ return `"${objName}"`;
1556
+ });
1557
+ addTargetQueries(queries, ast, `ON ${concreteTargetKeyToSql[key]} ${quotedTargets.join(", ")}`, privileges, grantablePrivileges, isRevoke);
1558
+ }
1559
+ for (const key of [
1560
+ "allTablesIn",
1561
+ "allSequencesIn",
1562
+ "allRoutinesIn"
1563
+ ]) {
1564
+ const targetNames = ast[key];
1565
+ if (!targetNames?.length) continue;
1566
+ const targetList = targetNames.map((name) => `"${name}"`);
1567
+ const privileges = ast.privileges;
1568
+ const grantablePrivileges = ast.grantablePrivileges;
1569
+ addTargetQueries(queries, ast, `ON ${schemaWideTargetKeyToSql[key]} ${targetList.join(", ")}`, privileges, grantablePrivileges, isRevoke);
1570
+ }
1571
+ for (const key of ["schemas", "databases"]) {
1572
+ const targetNames = ast[key];
1573
+ if (!targetNames?.length) continue;
1574
+ const targetList = targetNames.map((name) => `"${name}"`);
1575
+ const privileges = ast.privileges;
1576
+ const grantablePrivileges = ast.grantablePrivileges;
1577
+ addTargetQueries(queries, ast, `ON ${schemaOrDatabaseTargetKeyToSql[key]} ${targetList.join(", ")}`, privileges, grantablePrivileges, isRevoke);
1578
+ }
1579
+ return queries;
1580
+ };
1581
+ const addTargetQueries = (queries, ast, targetSql, privileges, grantablePrivileges, isRevoke) => {
1582
+ if (privileges?.length) queries.push(buildQuery(ast, privileges, targetSql, false, isRevoke));
1583
+ if (grantablePrivileges?.length) queries.push(buildQuery(ast, grantablePrivileges, targetSql, !isRevoke, isRevoke));
1584
+ };
1585
+ const buildQuery = (ast, privileges, targetSql, grantable, isRevoke) => {
1586
+ const parts = [];
1587
+ if (isRevoke) {
1588
+ parts.push("REVOKE");
1589
+ if (grantable) parts.push("GRANT OPTION FOR");
1590
+ } else parts.push("GRANT");
1591
+ const privilegeList = privileges.map((p) => p === "ALL" ? "ALL PRIVILEGES" : p === "TEMP" ? "TEMPORARY" : p).join(", ");
1592
+ parts.push(privilegeList);
1593
+ parts.push(targetSql);
1594
+ if (isRevoke) parts.push("FROM");
1595
+ else parts.push("TO");
1596
+ parts.push(ast.to.map(formatRoleSpec).join(", "));
1597
+ if (ast.grantedBy) parts.push("GRANTED BY", `"${ast.grantedBy}"`);
1598
+ if (isRevoke && ast.revokeMode) parts.push(ast.revokeMode);
1599
+ if (!isRevoke && grantable) parts.push("WITH GRANT OPTION");
1600
+ return parts.join(" ");
1601
+ };
1602
+ const formatRoleSpec = (role) => {
1603
+ return specialRoleSpecs.has(role) ? role : `"${role}"`;
1604
+ };
1408
1605
  /**
1409
1606
  * Creates a new `db` instance that is an instance of `pqb` with mixed in migration methods from the `Migration` class.
1410
1607
  * It overrides `query` and `array` db adapter methods to intercept SQL for the logging.
@@ -2278,6 +2475,21 @@ var Migration = class {
2278
2475
  noForceRls(tableName) {
2279
2476
  return forceOrNoForceRls(this, !this.up, tableName);
2280
2477
  }
2478
+ createPolicy(tableName, policyName, params) {
2479
+ return createOrDropPolicy(this, this.up, tableName, policyName, params);
2480
+ }
2481
+ dropPolicy(tableName, policyName, params) {
2482
+ return dropOrCreatePolicy(this, this.up, tableName, policyName, params);
2483
+ }
2484
+ changePolicy(tableName, policyName, params) {
2485
+ return changePolicy(this, this.up, tableName, policyName, params);
2486
+ }
2487
+ grant(params) {
2488
+ return changeGrant(this, this.up, params);
2489
+ }
2490
+ revoke(params) {
2491
+ return changeGrant(this, !this.up, params);
2492
+ }
2281
2493
  };
2282
2494
  const wrapWithEnhancingError = async (text, values, promise) => {
2283
2495
  try {
@@ -3622,6 +3834,38 @@ const astToGenerateItem = (config, ast, currentSchema) => {
3622
3834
  deps.push(schema, `${schema}.${ast.table}`);
3623
3835
  break;
3624
3836
  }
3837
+ case "policy": {
3838
+ const defaultSchema = ast.schema ?? currentSchema;
3839
+ const [tableSchema = defaultSchema, tableName] = getSchemaAndTableFromName(defaultSchema, ast.table);
3840
+ deps.push(tableSchema, `${tableSchema}.${tableName}`);
3841
+ pushPolicyRoleDeps(deps, ast.to);
3842
+ if (ast.action === "create") add.push(`policy:${ast.name}`);
3843
+ else drop.push(`policy:${ast.name}`);
3844
+ break;
3845
+ }
3846
+ case "changePolicy": {
3847
+ const defaultSchema = ast.schema ?? currentSchema;
3848
+ const [tableSchema = defaultSchema, tableName] = getSchemaAndTableFromName(defaultSchema, ast.table);
3849
+ deps.push(tableSchema, `${tableSchema}.${tableName}`);
3850
+ const fromTable = ast.from.table ? ast.from.table : tableName;
3851
+ const fromName = ast.from.name ? ast.from.name : ast.name;
3852
+ const toTable = ast.to.table ? ast.to.table : tableName;
3853
+ const toName = ast.to.name ? ast.to.name : ast.name;
3854
+ const [fromSchema = tableSchema, fromTableName] = getSchemaAndTableFromName(tableSchema, fromTable);
3855
+ const [toSchema = tableSchema, toTableName] = getSchemaAndTableFromName(tableSchema, toTable);
3856
+ deps.push(fromSchema, `${fromSchema}.${fromTableName}`);
3857
+ deps.push(toSchema, `${toSchema}.${toTableName}`);
3858
+ if (fromName !== toName) {
3859
+ drop.push(`policy:${fromName}`);
3860
+ add.push(`policy:${toName}`);
3861
+ }
3862
+ pushPolicyRoleDeps(deps, ast.from.to);
3863
+ pushPolicyRoleDeps(deps, ast.to.to);
3864
+ break;
3865
+ }
3866
+ case "grant":
3867
+ pushGrantDeps(currentSchema, deps, ast);
3868
+ break;
3625
3869
  default: (0, pqb_internal.exhaustive)(ast);
3626
3870
  }
3627
3871
  return {
@@ -3685,6 +3929,39 @@ const analyzeTableData = (config, currentSchema, schema, table, keys, deps, data
3685
3929
  }
3686
3930
  }
3687
3931
  };
3932
+ const pushPolicyRoleDeps = (deps, roles) => {
3933
+ if (!roles) return;
3934
+ deps.push(...roles.map((role) => `role:${role}`));
3935
+ };
3936
+ const grantConcreteTargetKeys = [
3937
+ "tables",
3938
+ "sequences",
3939
+ "routines",
3940
+ "types",
3941
+ "domains"
3942
+ ];
3943
+ const grantSchemaWideTargetKeys = [
3944
+ "allTablesIn",
3945
+ "allSequencesIn",
3946
+ "allRoutinesIn"
3947
+ ];
3948
+ const pushGrantDeps = (currentSchema, deps, ast) => {
3949
+ deps.push(...ast.to.map((role) => `role:${role}`));
3950
+ if (ast.grantedBy) deps.push(`role:${ast.grantedBy}`);
3951
+ if (ast.schemas) deps.push(...ast.schemas);
3952
+ for (const key of grantSchemaWideTargetKeys) {
3953
+ const schemas = ast[key];
3954
+ if (schemas) deps.push(...schemas);
3955
+ }
3956
+ for (const key of grantConcreteTargetKeys) {
3957
+ const targets = ast[key];
3958
+ if (!targets) continue;
3959
+ for (const target of targets) {
3960
+ const [schema = currentSchema, name] = getSchemaAndTableFromName(currentSchema, target);
3961
+ deps.push(schema, `${schema}.${name}`);
3962
+ }
3963
+ }
3964
+ };
3688
3965
  const astToMigration = (currentSchema, config, asts) => {
3689
3966
  const items = astToGenerateItems(config, asts, currentSchema);
3690
3967
  const toBeAdded = /* @__PURE__ */ new Set();
@@ -4094,8 +4371,91 @@ const astEncoders = {
4094
4371
  name: ast.table
4095
4372
  }, currentSchema);
4096
4373
  return `await db.${ast.action === "enable" ? "enableRls" : ast.action === "disable" ? "disableRls" : ast.action === "force" ? "forceRls" : "noForceRls"}(${table});`;
4374
+ },
4375
+ policy(ast, _config, currentSchema) {
4376
+ const table = quoteSchemaTable({
4377
+ schema: ast.schema,
4378
+ name: ast.table
4379
+ }, currentSchema);
4380
+ return [
4381
+ `await db.${ast.action}Policy(${table}, ${(0, pqb_internal.singleQuote)(ast.name)}, {`,
4382
+ policyDefinitionToCode(ast),
4383
+ "});"
4384
+ ];
4385
+ },
4386
+ changePolicy(ast, _config, currentSchema) {
4387
+ return [
4388
+ `await db.changePolicy(${quoteSchemaTable({
4389
+ schema: ast.schema,
4390
+ name: ast.table
4391
+ }, currentSchema)}, ${(0, pqb_internal.singleQuote)(ast.name)}, {`,
4392
+ [
4393
+ "from: {",
4394
+ policyChangeDefinitionToCode(ast.from),
4395
+ "},",
4396
+ "to: {",
4397
+ policyChangeDefinitionToCode(ast.to),
4398
+ "},"
4399
+ ],
4400
+ "});"
4401
+ ];
4402
+ },
4403
+ grant(ast) {
4404
+ const props = [];
4405
+ props.push(`to: [${ast.to.map(pqb_internal.singleQuote).join(", ")}],`);
4406
+ for (const key of grantTargetKeys) {
4407
+ const values = ast[key];
4408
+ if (values?.length) props.push(`${key}: [${values.map(pqb_internal.singleQuote).join(", ")}],`);
4409
+ }
4410
+ if (ast.privileges?.length) props.push(`privileges: [${ast.privileges.map(pqb_internal.singleQuote).join(", ")}],`);
4411
+ if (ast.grantablePrivileges?.length) props.push(`grantablePrivileges: [${ast.grantablePrivileges.map(pqb_internal.singleQuote).join(", ")}],`);
4412
+ if (ast.grantedBy) props.push(`grantedBy: ${(0, pqb_internal.singleQuote)(ast.grantedBy)},`);
4413
+ if (ast.revokeMode) props.push(`revokeMode: ${(0, pqb_internal.singleQuote)(ast.revokeMode)},`);
4414
+ return [
4415
+ `await db.${ast.action}({`,
4416
+ props,
4417
+ "});"
4418
+ ];
4097
4419
  }
4098
4420
  };
4421
+ const grantTargetKeys = [
4422
+ "schemas",
4423
+ "tables",
4424
+ "sequences",
4425
+ "routines",
4426
+ "types",
4427
+ "domains",
4428
+ "databases",
4429
+ "allTablesIn",
4430
+ "allSequencesIn",
4431
+ "allRoutinesIn"
4432
+ ];
4433
+ const policyDefinitionToCode = (policy) => {
4434
+ const code = [`as: ${(0, pqb_internal.singleQuote)(policy.as)},`];
4435
+ if (policy.for) code.push(`for: ${(0, pqb_internal.singleQuote)(policy.for)},`);
4436
+ if (policy.to?.length) code.push(`to: [${policy.to.map(pqb_internal.singleQuote).join(", ")}],`);
4437
+ if (policy.using) code.push(`using: ${rawSqlStringToCode(policy.using)},`);
4438
+ if (policy.withCheck) code.push(`withCheck: ${rawSqlStringToCode(policy.withCheck)},`);
4439
+ return code;
4440
+ };
4441
+ const policyChangeDefinitionToCode = (policy) => {
4442
+ const code = [];
4443
+ if ("table" in policy && policy.table) code.push(`table: ${(0, pqb_internal.singleQuote)(policy.table)},`);
4444
+ if (policy.name) code.push(`name: ${(0, pqb_internal.singleQuote)(policy.name)},`);
4445
+ if (isPolicyRecreateDefinition(policy)) code.push(...policyDefinitionToCode(policy));
4446
+ else {
4447
+ if (policy.to?.length) code.push(`to: [${policy.to.map(pqb_internal.singleQuote).join(", ")}],`);
4448
+ if (policy.using) code.push(`using: ${rawSqlStringToCode(policy.using)},`);
4449
+ if (policy.withCheck) code.push(`withCheck: ${rawSqlStringToCode(policy.withCheck)},`);
4450
+ }
4451
+ return code;
4452
+ };
4453
+ const isPolicyRecreateDefinition = (policy) => {
4454
+ return policy.as !== void 0 || policy.for !== void 0 || policy.table !== void 0;
4455
+ };
4456
+ const rawSqlStringToCode = (sql) => {
4457
+ return `db.sql${(0, pqb_internal.backtickQuote)(sql)}`;
4458
+ };
4099
4459
  const roleParams = (name, ast, compare, to) => {
4100
4460
  const params = [];
4101
4461
  for (const key of [
@@ -4557,7 +4917,154 @@ const defaultPrivilegesSql = `SELECT COALESCE(json_agg(t.*), '[]') FROM (
4557
4917
  JOIN LATERAL aclexplode(d.defaclacl) ae ON true
4558
4918
  GROUP BY "grantor", "grantee", "schema", "object"
4559
4919
  ) t`;
4560
- 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"` : ""}`;
4920
+ const grantsSql = `SELECT COALESCE(json_agg(t.* ORDER BY t."target", t."schema", t."name", t."grantee", t."grantor"), '[]') FROM (
4921
+ SELECT
4922
+ (ae.grantor)::regrole "grantor",
4923
+ CASE
4924
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4925
+ ELSE (ae.grantee)::regrole::text
4926
+ END "grantee",
4927
+ n.nspname "schema",
4928
+ c.relname "name",
4929
+ 'tables' "target",
4930
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4931
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4932
+ FROM pg_class c
4933
+ JOIN pg_namespace n ON n.oid = c.relnamespace
4934
+ JOIN LATERAL aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) ae ON true
4935
+ WHERE c.relkind IN ('r', 'p')
4936
+ AND ${filterSchema("n.nspname")}
4937
+ AND ae.grantee <> c.relowner
4938
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4939
+
4940
+ UNION ALL
4941
+
4942
+ SELECT
4943
+ (ae.grantor)::regrole "grantor",
4944
+ CASE
4945
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4946
+ ELSE (ae.grantee)::regrole::text
4947
+ END "grantee",
4948
+ n.nspname "schema",
4949
+ c.relname "name",
4950
+ 'sequences' "target",
4951
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4952
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4953
+ FROM pg_class c
4954
+ JOIN pg_namespace n ON n.oid = c.relnamespace
4955
+ JOIN LATERAL aclexplode(coalesce(c.relacl, acldefault('S', c.relowner))) ae ON true
4956
+ WHERE c.relkind = 'S'
4957
+ AND ${filterSchema("n.nspname")}
4958
+ AND ae.grantee <> c.relowner
4959
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4960
+
4961
+ UNION ALL
4962
+
4963
+ SELECT
4964
+ (ae.grantor)::regrole "grantor",
4965
+ CASE
4966
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4967
+ ELSE (ae.grantee)::regrole::text
4968
+ END "grantee",
4969
+ n.nspname "schema",
4970
+ p.proname "name",
4971
+ 'routines' "target",
4972
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4973
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4974
+ FROM pg_proc p
4975
+ JOIN pg_namespace n ON n.oid = p.pronamespace
4976
+ JOIN LATERAL aclexplode(coalesce(p.proacl, acldefault('f', p.proowner))) ae ON true
4977
+ WHERE ${filterSchema("n.nspname")}
4978
+ AND ae.grantee <> p.proowner
4979
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
4980
+
4981
+ UNION ALL
4982
+
4983
+ SELECT
4984
+ (ae.grantor)::regrole "grantor",
4985
+ CASE
4986
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
4987
+ ELSE (ae.grantee)::regrole::text
4988
+ END "grantee",
4989
+ n.nspname "schema",
4990
+ t.typname "name",
4991
+ CASE WHEN t.typtype = 'd' THEN 'domains' ELSE 'types' END "target",
4992
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
4993
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
4994
+ FROM pg_type t
4995
+ JOIN pg_namespace n ON n.oid = t.typnamespace
4996
+ JOIN LATERAL aclexplode(coalesce(t.typacl, acldefault('T', t.typowner))) ae ON true
4997
+ WHERE t.typtype IN ('c', 'd', 'e', 'm', 'r')
4998
+ AND ${filterSchema("n.nspname")}
4999
+ AND ae.grantee <> t.typowner
5000
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
5001
+
5002
+ UNION ALL
5003
+
5004
+ SELECT
5005
+ (ae.grantor)::regrole "grantor",
5006
+ CASE
5007
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
5008
+ ELSE (ae.grantee)::regrole::text
5009
+ END "grantee",
5010
+ NULL "schema",
5011
+ n.nspname "name",
5012
+ 'schemas' "target",
5013
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
5014
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
5015
+ FROM pg_namespace n
5016
+ JOIN LATERAL aclexplode(coalesce(n.nspacl, acldefault('n', n.nspowner))) ae ON true
5017
+ WHERE ${filterSchema("n.nspname")}
5018
+ AND ae.grantee <> n.nspowner
5019
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
5020
+
5021
+ UNION ALL
5022
+
5023
+ SELECT
5024
+ (ae.grantor)::regrole "grantor",
5025
+ CASE
5026
+ WHEN ae.grantee = 0 THEN 'PUBLIC'
5027
+ ELSE (ae.grantee)::regrole::text
5028
+ END "grantee",
5029
+ NULL "schema",
5030
+ d.datname "name",
5031
+ 'databases' "target",
5032
+ array_agg(ae.privilege_type ORDER BY ae.privilege_type) "privileges",
5033
+ array_agg(ae.is_grantable ORDER BY ae.privilege_type) "isGrantables"
5034
+ FROM pg_database d
5035
+ JOIN LATERAL aclexplode(coalesce(d.datacl, acldefault('d', d.datdba))) ae ON true
5036
+ WHERE NOT d.datistemplate
5037
+ AND ae.grantee <> d.datdba
5038
+ GROUP BY "grantor", "grantee", "schema", "name", "target"
5039
+ ) t`;
5040
+ const policiesSql = `SELECT
5041
+ n.nspname AS "schemaName",
5042
+ c.relname AS "tableName",
5043
+ p.polname AS "name",
5044
+ CASE WHEN p.polpermissive
5045
+ THEN 'PERMISSIVE'
5046
+ ELSE 'RESTRICTIVE'
5047
+ END AS "mode",
5048
+ CASE p.polcmd
5049
+ WHEN '*' THEN 'ALL'
5050
+ WHEN 'r' THEN 'SELECT'
5051
+ WHEN 'a' THEN 'INSERT'
5052
+ WHEN 'w' THEN 'UPDATE'
5053
+ WHEN 'd' THEN 'DELETE'
5054
+ END AS "command",
5055
+ ARRAY(
5056
+ SELECT COALESCE(r.rolname, 'public')
5057
+ FROM unnest(p.polroles) roleOid
5058
+ LEFT JOIN pg_roles r ON r.oid = roleOid
5059
+ ORDER BY roleOid = 0 DESC, COALESCE(r.rolname, 'public')
5060
+ ) AS "roles",
5061
+ pg_get_expr(p.polqual, p.polrelid) AS "using",
5062
+ pg_get_expr(p.polwithcheck, p.polrelid) AS "withCheck"
5063
+ FROM pg_policy p
5064
+ JOIN pg_class c ON c.oid = p.polrelid
5065
+ JOIN pg_namespace n ON n.oid = c.relnamespace
5066
+ ORDER BY n.nspname, c.relname, p.polname`;
5067
+ 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")}` : ""}`;
4561
5068
  async function getDbVersion(db) {
4562
5069
  const { rows: [{ version: versionString }] } = await db.query("SELECT version()");
4563
5070
  return +versionString.match(/\d+/)[0];
@@ -4633,9 +5140,36 @@ async function introspectDbSchema(db, params) {
4633
5140
  }));
4634
5141
  }
4635
5142
  }
5143
+ if (raw.policies) {
5144
+ const policiesByTable = raw.policies.reduce((acc, policy) => {
5145
+ nullsToUndefined(policy);
5146
+ const key = `${policy.schemaName}.${policy.tableName}`;
5147
+ const existing = acc.get(key);
5148
+ const mappedPolicy = {
5149
+ schemaName: policy.schemaName,
5150
+ tableName: policy.tableName,
5151
+ name: policy.name,
5152
+ mode: policy.mode,
5153
+ command: policy.command,
5154
+ roles: policy.roles,
5155
+ using: policy.using,
5156
+ withCheck: policy.withCheck
5157
+ };
5158
+ if (existing) existing.push(mappedPolicy);
5159
+ else acc.set(key, [mappedPolicy]);
5160
+ return acc;
5161
+ }, /* @__PURE__ */ new Map());
5162
+ for (const table of raw.tables) {
5163
+ if (!table.rls) continue;
5164
+ table.rls.policies = policiesByTable.get(`${table.schemaName}.${table.name}`) ?? [];
5165
+ }
5166
+ }
5167
+ const grants = raw.grants?.map(mapRawGrant);
5168
+ const { policies: _policies, grants: _grants, ...structureWithoutPolicies } = raw;
4636
5169
  return {
4637
5170
  version,
4638
- ...raw,
5171
+ ...structureWithoutPolicies,
5172
+ grants,
4639
5173
  defaultPrivileges: raw.defaultPrivileges && [...raw.defaultPrivileges.reduce((acc, privilege) => {
4640
5174
  nullsToUndefined(privilege);
4641
5175
  const key = `${privilege.grantor}.${privilege.grantee}.${privilege.schema}`;
@@ -4660,6 +5194,20 @@ async function introspectDbSchema(db, params) {
4660
5194
  }, /* @__PURE__ */ new Map()).values()]
4661
5195
  };
4662
5196
  }
5197
+ const mapRawGrant = (raw) => {
5198
+ nullsToUndefined(raw);
5199
+ const privileges = [];
5200
+ const grantablePrivileges = [];
5201
+ for (let i = 0; i < raw.privileges.length; i++) (raw.isGrantables[i] ? grantablePrivileges : privileges).push(raw.privileges[i]);
5202
+ const grant = {
5203
+ to: [raw.grantee],
5204
+ grantedBy: raw.grantor,
5205
+ [raw.target]: raw.schema ? [`${raw.schema}.${raw.name}`] : [raw.name]
5206
+ };
5207
+ if (privileges.length) grant.privileges = privileges;
5208
+ if (grantablePrivileges.length) grant.grantablePrivileges = grantablePrivileges;
5209
+ return grant;
5210
+ };
4663
5211
  const nullsToUndefined = (obj) => {
4664
5212
  for (const key in obj) if (obj[key] === null) obj[key] = void 0;
4665
5213
  };
@@ -4684,7 +5232,7 @@ const makeStructureToAstCtx = (config, currentSchema) => ({
4684
5232
  });
4685
5233
  const structureToAst = async (ctx, adapter, config) => {
4686
5234
  const ast = [];
4687
- const data = await introspectDbSchema(adapter);
5235
+ const data = await introspectDbSchema(adapter, { rls: true });
4688
5236
  for (const name of data.schemas) {
4689
5237
  if (name === "public") continue;
4690
5238
  ast.push({
@@ -4704,6 +5252,18 @@ const structureToAst = async (ctx, adapter, config) => {
4704
5252
  for (const table of data.tables) {
4705
5253
  if (table.name === migrationsTable && table.schemaName === migrationsSchema) continue;
4706
5254
  ast.push(tableToAst(ctx, data, table, "create", domains));
5255
+ for (const policy of table.rls?.policies || []) ast.push({
5256
+ type: "policy",
5257
+ action: "create",
5258
+ schema: table.schemaName === ctx.currentSchema ? void 0 : table.schemaName,
5259
+ table: table.name,
5260
+ name: policy.name,
5261
+ as: policy.mode,
5262
+ for: policy.command,
5263
+ to: policy.roles,
5264
+ using: policy.using,
5265
+ withCheck: policy.withCheck
5266
+ });
4707
5267
  if (table.rls?.enable) ast.push({
4708
5268
  type: "tableRls",
4709
5269
  action: "enable",