rake-db 2.34.0 → 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.d.ts +98 -2
- package/dist/index.js +573 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +574 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
|
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
|
-
...
|
|
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",
|