rake-db 2.30.7 → 2.30.8

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
@@ -790,7 +790,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
790
790
  shape = tableData = pqb.emptyObject;
791
791
  }
792
792
  const schema = migration.adapter.getSchema();
793
- const ast = makeAst$3(
793
+ const ast = makeAst$4(
794
794
  schema,
795
795
  up,
796
796
  tableName,
@@ -820,7 +820,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
820
820
  }
821
821
  };
822
822
  };
823
- const makeAst$3 = (schema, up, tableName, shape, tableData, options, noPrimaryKey) => {
823
+ const makeAst$4 = (schema, up, tableName, shape, tableData, options, noPrimaryKey) => {
824
824
  const shapePKeys = [];
825
825
  for (const key in shape) {
826
826
  const column = shape[key];
@@ -1170,7 +1170,7 @@ const changeTable = async (migration, up, tableName, options, fn) => {
1170
1170
  addOrDropChanges.length = 0;
1171
1171
  const changeData = fn?.(tableChanger) || {};
1172
1172
  const schema = migration.adapter.getSchema();
1173
- const ast = makeAst$2(
1173
+ const ast = makeAst$3(
1174
1174
  schema,
1175
1175
  up,
1176
1176
  tableName,
@@ -1184,7 +1184,7 @@ const changeTable = async (migration, up, tableName, options, fn) => {
1184
1184
  query.then?.(result);
1185
1185
  }
1186
1186
  };
1187
- const makeAst$2 = (schema, up, name, changeData, changeTableData2, options) => {
1187
+ const makeAst$3 = (schema, up, name, changeData, changeTableData2, options) => {
1188
1188
  const { comment } = options;
1189
1189
  const shape = {};
1190
1190
  const consumedChanges = {};
@@ -1636,11 +1636,11 @@ const renameColumnSql = (from, to) => {
1636
1636
 
1637
1637
  const createView = async (migration, up, name, options, sql) => {
1638
1638
  const schema = migration.adapter.getSchema();
1639
- const ast = makeAst$1(schema, up, name, options, sql);
1639
+ const ast = makeAst$2(schema, up, name, options, sql);
1640
1640
  const query = astToQuery$1(ast);
1641
1641
  await migration.adapter.arrays(interpolateSqlValues(query));
1642
1642
  };
1643
- const makeAst$1 = (schema, up, fullName, options, sql) => {
1643
+ const makeAst$2 = (schema, up, fullName, options, sql) => {
1644
1644
  if (typeof sql === "string") {
1645
1645
  sql = pqb.raw({ raw: sql });
1646
1646
  }
@@ -1705,11 +1705,11 @@ const serializers = {
1705
1705
  validUntil: (value) => `VALID UNTIL '${value === void 0 ? "infinity" : value}'`
1706
1706
  };
1707
1707
  const createOrDropRole = async (migration, up, name, params) => {
1708
- const ast = makeAst(up, name, params);
1708
+ const ast = makeAst$1(up, name, params);
1709
1709
  const sql = astToQuery(ast);
1710
1710
  await migration.adapter.arrays(sql);
1711
1711
  };
1712
- const makeAst = (up, name, params) => {
1712
+ const makeAst$1 = (up, name, params) => {
1713
1713
  return {
1714
1714
  type: "role",
1715
1715
  action: up ? "create" : "drop",
@@ -1830,24 +1830,154 @@ const renameAstToQuery = (ast) => {
1830
1830
  return `ALTER ROLE "${ast.from}" RENAME TO "${ast.to}"`;
1831
1831
  };
1832
1832
 
1833
+ const SCHEMA_OBJECT_TYPES = [
1834
+ "tables",
1835
+ "sequences",
1836
+ "functions",
1837
+ "types"
1838
+ ];
1839
+ const GLOBAL_ONLY_OBJECT_TYPES = ["schemas", "largeObjects"];
1840
+ const ALL_OBJECT_TYPES = [
1841
+ ...SCHEMA_OBJECT_TYPES,
1842
+ ...GLOBAL_ONLY_OBJECT_TYPES
1843
+ ];
1844
+ const objectTypeToSql = {
1845
+ tables: "TABLES",
1846
+ sequences: "SEQUENCES",
1847
+ functions: "FUNCTIONS",
1848
+ types: "TYPES",
1849
+ schemas: "SCHEMAS",
1850
+ largeObjects: "LARGE OBJECTS"
1851
+ };
1852
+ const processObjectPrivileges = (value) => {
1853
+ if (!value) return void 0;
1854
+ const { privileges, grantablePrivileges } = value;
1855
+ const grantableSet = new Set(grantablePrivileges ?? []);
1856
+ const filteredPrivileges = privileges?.filter((p) => !grantableSet.has(p));
1857
+ const result = {};
1858
+ if (filteredPrivileges?.length) {
1859
+ result.privileges = [...filteredPrivileges];
1860
+ }
1861
+ if (grantablePrivileges?.length) {
1862
+ result.grantablePrivileges = [...grantablePrivileges];
1863
+ }
1864
+ return Object.keys(result).length ? result : void 0;
1865
+ };
1866
+ const filterAndTransformConfig = (config, schema) => {
1867
+ if (!config) return void 0;
1868
+ const objectTypes = schema ? SCHEMA_OBJECT_TYPES : ALL_OBJECT_TYPES;
1869
+ const result = {};
1870
+ if (config.allGrantable) {
1871
+ for (const key of objectTypes) {
1872
+ result[key] = { grantablePrivileges: ["ALL"] };
1873
+ }
1874
+ } else if (config.all) {
1875
+ for (const key of objectTypes) {
1876
+ result[key] = { privileges: ["ALL"] };
1877
+ }
1878
+ }
1879
+ for (const key of ALL_OBJECT_TYPES) {
1880
+ const value = config[key];
1881
+ if (!value) continue;
1882
+ const processed = processObjectPrivileges(value);
1883
+ if (processed) {
1884
+ result[key] = processed;
1885
+ }
1886
+ }
1887
+ return Object.keys(result).length ? result : void 0;
1888
+ };
1889
+ const changeDefaultPrivileges = async (migration, up, arg) => {
1890
+ const ast = makeAst(up, arg);
1891
+ const sql = astToSql(ast);
1892
+ if (sql.length) {
1893
+ await migration.adapter.arrays(sql.join(";\n"));
1894
+ }
1895
+ };
1896
+ const makeAst = (up, arg) => {
1897
+ if (!up) {
1898
+ const { grant, revoke } = arg;
1899
+ arg = {
1900
+ ...arg,
1901
+ grant: revoke,
1902
+ revoke: grant
1903
+ };
1904
+ }
1905
+ return {
1906
+ type: "defaultPrivilege",
1907
+ owner: arg.owner,
1908
+ grantee: arg.grantee,
1909
+ schema: arg.schema,
1910
+ grant: filterAndTransformConfig(arg.grant, arg.schema),
1911
+ revoke: filterAndTransformConfig(arg.revoke, arg.schema)
1912
+ };
1913
+ };
1914
+ const astToSql = (ast) => {
1915
+ const queries = [];
1916
+ if (ast.grant) {
1917
+ queries.push(...objectConfigToSql(ast, ast.grant, "GRANT"));
1918
+ }
1919
+ if (ast.revoke) {
1920
+ queries.push(...objectConfigToSql(ast, ast.revoke, "REVOKE"));
1921
+ }
1922
+ return queries;
1923
+ };
1924
+ const objectConfigToSql = (ast, config, action) => {
1925
+ const queries = [];
1926
+ for (const [key, value] of Object.entries(config)) {
1927
+ const objectType = key;
1928
+ if (!value) continue;
1929
+ const { privileges, grantablePrivileges } = value;
1930
+ if (privileges?.length) {
1931
+ queries.push(buildQuery(ast, objectType, privileges, false, action));
1932
+ }
1933
+ if (grantablePrivileges?.length) {
1934
+ queries.push(
1935
+ buildQuery(ast, objectType, grantablePrivileges, true, action)
1936
+ );
1937
+ }
1938
+ }
1939
+ return queries;
1940
+ };
1941
+ const buildQuery = (ast, objectType, privileges, grantable, action) => {
1942
+ const parts = ["ALTER DEFAULT PRIVILEGES"];
1943
+ if (ast.owner) {
1944
+ parts.push(`FOR ROLE "${ast.owner}"`);
1945
+ }
1946
+ if (ast.schema) {
1947
+ parts.push(`IN SCHEMA "${ast.schema}"`);
1948
+ }
1949
+ const privilegeList = privileges.map((p) => p === "ALL" ? "ALL PRIVILEGES" : p).join(", ");
1950
+ if (action === "GRANT") {
1951
+ parts.push(
1952
+ `GRANT ${privilegeList} ON ${objectTypeToSql[objectType]} TO "${ast.grantee}"`
1953
+ );
1954
+ if (grantable) {
1955
+ parts.push("WITH GRANT OPTION");
1956
+ }
1957
+ } else {
1958
+ parts.push(
1959
+ `REVOKE ${privilegeList} ON ${objectTypeToSql[objectType]} FROM "${ast.grantee}"`
1960
+ );
1961
+ }
1962
+ return parts.join(" ");
1963
+ };
1964
+
1833
1965
  const createMigrationInterface = (tx, up, config) => {
1834
1966
  const adapter = Object.create(tx);
1835
1967
  const { query, arrays } = adapter;
1836
1968
  const log = pqb.logParamToLogObject(config.logger || console, config.log);
1837
1969
  adapter.query = (text, values) => {
1838
- return wrapWithLog(
1839
- log,
1970
+ return wrapWithEnhancingError(
1840
1971
  text,
1841
1972
  values,
1842
- () => query.call(adapter, text, values)
1973
+ wrapWithLog(log, text, values, () => query.call(adapter, text, values))
1843
1974
  );
1844
1975
  };
1845
1976
  adapter.arrays = (text, values) => {
1846
- return wrapWithLog(
1847
- log,
1977
+ return wrapWithEnhancingError(
1848
1978
  text,
1849
1979
  values,
1850
- () => arrays.call(adapter, text, values)
1980
+ wrapWithLog(log, text, values, () => arrays.call(adapter, text, values))
1851
1981
  );
1852
1982
  };
1853
1983
  Object.assign(adapter, { silentQuery: query, silentArrays: arrays });
@@ -2731,7 +2861,23 @@ class Migration {
2731
2861
  params.to
2732
2862
  );
2733
2863
  }
2864
+ changeDefaultPrivileges(params) {
2865
+ return changeDefaultPrivileges(this, this.up, params);
2866
+ }
2734
2867
  }
2868
+ const wrapWithEnhancingError = async (text, values, promise) => {
2869
+ try {
2870
+ return await promise;
2871
+ } catch (err) {
2872
+ if (err && typeof err === "object" && "message" in err && typeof err.message === "string" && err.constructor.name === "PostgresError") {
2873
+ err.message += `
2874
+ SQL: ${text}${values ? `
2875
+ Variables: ${JSON.stringify(values)}` : ""}`;
2876
+ throw new err.constructor(err);
2877
+ }
2878
+ throw err;
2879
+ }
2880
+ };
2735
2881
  const wrapWithLog = async (log, text, values, fn) => {
2736
2882
  if (!log) {
2737
2883
  return fn();
@@ -4101,9 +4247,24 @@ const astToGenerateItem = (config, ast, currentSchema) => {
4101
4247
  deps.push(tableSchema, `${tableSchema}.${tableName}`);
4102
4248
  break;
4103
4249
  }
4104
- case "role":
4105
- case "changeRole":
4250
+ case "role": {
4251
+ deps.push(`role:${ast.name}`);
4252
+ break;
4253
+ }
4254
+ case "changeRole": {
4255
+ deps.push(`role:${ast.name}`);
4256
+ break;
4257
+ }
4106
4258
  case "renameRole": {
4259
+ drop.push(ast.from);
4260
+ add.push(ast.to);
4261
+ deps.push(`role:${ast.from}`, `role:${ast.to}`);
4262
+ break;
4263
+ }
4264
+ case "defaultPrivilege": {
4265
+ if (ast.schema) deps.push(ast.schema);
4266
+ if (ast.owner) deps.push(`role:${ast.owner}`);
4267
+ deps.push(`role:${ast.grantee}`);
4107
4268
  break;
4108
4269
  }
4109
4270
  default:
@@ -4711,6 +4872,69 @@ const astEncoders = {
4711
4872
  [...from.length ? ["from: {", from, "},"] : [], "to: {", to, "},"],
4712
4873
  "});"
4713
4874
  ];
4875
+ },
4876
+ defaultPrivilege(ast) {
4877
+ const code = [`await db.changeDefaultPrivileges({`];
4878
+ const props = [];
4879
+ if (ast.owner) {
4880
+ props.push(`owner: ${pqb.singleQuote(ast.owner)},`);
4881
+ }
4882
+ props.push(`grantee: ${pqb.singleQuote(ast.grantee)},`);
4883
+ if (ast.schema) {
4884
+ props.push(`schema: ${pqb.singleQuote(ast.schema)},`);
4885
+ }
4886
+ const objectConfigToCode = (config) => {
4887
+ const result = [];
4888
+ for (const key of [
4889
+ "tables",
4890
+ "sequences",
4891
+ "functions",
4892
+ "types",
4893
+ "schemas",
4894
+ "largeObjects"
4895
+ ]) {
4896
+ const value = config?.[key];
4897
+ if (!value) continue;
4898
+ const { privileges, grantablePrivileges } = value;
4899
+ const hasPrivileges = privileges?.length;
4900
+ const hasGrantable = grantablePrivileges?.length;
4901
+ if (!hasPrivileges && !hasGrantable) continue;
4902
+ result.push(`${key}: {`);
4903
+ const lines = [];
4904
+ if (hasPrivileges) {
4905
+ lines.push(
4906
+ `privileges: [${privileges.map(pqb.singleQuote).join(", ")}],`
4907
+ );
4908
+ }
4909
+ if (hasGrantable) {
4910
+ lines.push(
4911
+ `grantablePrivileges: [${grantablePrivileges.map(pqb.singleQuote).join(", ")}],`
4912
+ );
4913
+ }
4914
+ result.push(lines);
4915
+ result.push(`},`);
4916
+ }
4917
+ return result;
4918
+ };
4919
+ if (ast.grant) {
4920
+ const grantCode = objectConfigToCode(ast.grant);
4921
+ if (grantCode.length) {
4922
+ props.push("grant: {");
4923
+ props.push(grantCode);
4924
+ props.push("},");
4925
+ }
4926
+ }
4927
+ if (ast.revoke) {
4928
+ const revokeCode = objectConfigToCode(ast.revoke);
4929
+ if (revokeCode.length) {
4930
+ props.push("revoke: {");
4931
+ props.push(revokeCode);
4932
+ props.push("},");
4933
+ }
4934
+ }
4935
+ code.push(props);
4936
+ pqb.addCode(code, "});");
4937
+ return code;
4714
4938
  }
4715
4939
  };
4716
4940
  const roleParams = (name, ast, compare, to) => {
@@ -5165,6 +5389,25 @@ const roleSql = (params) => `SELECT COALESCE(json_agg(json_build_object(
5165
5389
  'bypassRls', rolbypassrls,
5166
5390
  'config', rolconfig
5167
5391
  )), '[]') FROM pg_roles WHERE ${params.whereSql ?? `rolname != 'postgres' AND rolname !~ '^pg_'`}`;
5392
+ const defaultPrivilegesSql = `SELECT COALESCE(json_agg(t.*), '[]') FROM (
5393
+ SELECT
5394
+ (ae.grantor)::regrole "grantor",
5395
+ (ae.grantee)::regrole "grantee",
5396
+ (SELECT nspname FROM pg_namespace n WHERE n.oid = d.defaclnamespace) "schema",
5397
+ CASE d.defaclobjtype
5398
+ WHEN 'r' THEN 'relation'
5399
+ WHEN 'S' THEN 'sequence'
5400
+ WHEN 'f' THEN 'function'
5401
+ WHEN 'T' THEN 'type'
5402
+ WHEN 'n' THEN 'schema'
5403
+ WHEN 'L' THEN 'large_object'
5404
+ END "object",
5405
+ array_agg(ae.privilege_type) "privileges",
5406
+ array_agg(ae.is_grantable) "isGrantables"
5407
+ FROM pg_default_acl d
5408
+ JOIN LATERAL aclexplode(d.defaclacl) ae ON true
5409
+ GROUP BY "grantor", "grantee", "schema", "object"
5410
+ ) t`;
5168
5411
  const sql = (version, params) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(
5169
5412
  tablesSql,
5170
5413
  "tables"
@@ -5180,19 +5423,22 @@ const sql = (version, params) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg
5180
5423
  )}, ${jsonAgg(domainsSql, "domains")}, ${jsonAgg(
5181
5424
  collationsSql(version),
5182
5425
  "collations"
5183
- )}${params?.roles ? `, (${roleSql(params.roles)}) AS "roles"` : ""}`;
5184
- async function introspectDbSchema(db, params) {
5426
+ )}${params?.roles ? `, (${roleSql(params.roles)}) AS "roles"` : ""}${params?.loadDefaultPrivileges ? `, (${defaultPrivilegesSql}) AS "defaultPrivileges"` : ""}`;
5427
+ async function getDbVersion(db) {
5185
5428
  const {
5186
5429
  rows: [{ version: versionString }]
5187
5430
  } = await db.query("SELECT version()");
5188
- const version = +versionString.match(/\d+/)[0];
5431
+ return +versionString.match(/\d+/)[0];
5432
+ }
5433
+ async function introspectDbSchema(db, params) {
5434
+ const version = await getDbVersion(db);
5189
5435
  const data = await db.query(sql(version, params));
5190
- const result = data.rows[0];
5191
- for (const domain of result.domains) {
5436
+ const raw = data.rows[0];
5437
+ for (const domain of raw.domains) {
5192
5438
  domain.checks = domain.checks?.filter((check) => check);
5193
5439
  nullsToUndefined(domain);
5194
5440
  }
5195
- for (const table of result.tables) {
5441
+ for (const table of raw.tables) {
5196
5442
  for (const column of table.columns) {
5197
5443
  nullsToUndefined(column);
5198
5444
  if (column.identity) nullsToUndefined(column.identity);
@@ -5203,7 +5449,7 @@ async function introspectDbSchema(db, params) {
5203
5449
  }
5204
5450
  const indexes = [];
5205
5451
  const excludes = [];
5206
- for (const index of result.indexes) {
5452
+ for (const index of raw.indexes) {
5207
5453
  nullsToUndefined(index);
5208
5454
  for (const column of index.columns) {
5209
5455
  if (!("expression" in column)) continue;
@@ -5263,10 +5509,10 @@ async function introspectDbSchema(db, params) {
5263
5509
  }
5264
5510
  (index.exclude ? excludes : indexes).push(index);
5265
5511
  }
5266
- result.indexes = indexes;
5267
- result.excludes = excludes;
5268
- if (result.roles) {
5269
- for (const role of result.roles) {
5512
+ raw.indexes = indexes;
5513
+ raw.excludes = excludes;
5514
+ if (raw.roles) {
5515
+ for (const role of raw.roles) {
5270
5516
  nullsToUndefined(role);
5271
5517
  if (role.validUntil) {
5272
5518
  role.validUntil = role.validUntil === "infinity" ? void 0 : new Date(role.validUntil);
@@ -5287,7 +5533,41 @@ async function introspectDbSchema(db, params) {
5287
5533
  }
5288
5534
  }
5289
5535
  }
5290
- return result;
5536
+ return {
5537
+ version,
5538
+ ...raw,
5539
+ defaultPrivileges: raw.defaultPrivileges && [
5540
+ ...raw.defaultPrivileges.reduce((acc, privilege) => {
5541
+ nullsToUndefined(privilege);
5542
+ const key = `${privilege.grantor}.${privilege.grantee}.${privilege.schema}`;
5543
+ const existing = acc.get(key);
5544
+ const objectType = privilege.object === "relation" ? "TABLES" : privilege.object === "sequence" ? "SEQUENCES" : privilege.object === "function" ? "FUNCTIONS" : privilege.object === "type" ? "TYPES" : privilege.object === "schema" ? "SCHEMAS" : privilege.object === "large_object" ? "LARGE_OBJECTS" : null;
5545
+ if (!objectType) {
5546
+ throw new Error(
5547
+ `Unknown default privilege object type: ${privilege.object}`
5548
+ );
5549
+ }
5550
+ const objectConfig = {
5551
+ object: objectType,
5552
+ privilegeConfigs: privilege.privileges.map((priv, i) => ({
5553
+ privilege: priv,
5554
+ isGrantable: privilege.isGrantables[i]
5555
+ }))
5556
+ };
5557
+ if (existing) {
5558
+ existing.objectConfigs.push(objectConfig);
5559
+ } else {
5560
+ acc.set(key, {
5561
+ owner: privilege.grantor,
5562
+ grantee: privilege.grantee,
5563
+ schema: privilege.schema,
5564
+ objectConfigs: [objectConfig]
5565
+ });
5566
+ }
5567
+ return acc;
5568
+ }, /* @__PURE__ */ new Map()).values()
5569
+ ]
5570
+ };
5291
5571
  }
5292
5572
  const nullsToUndefined = (obj) => {
5293
5573
  for (const key in obj) {
@@ -6392,6 +6672,7 @@ exports.encodeColumnDefault = encodeColumnDefault;
6392
6672
  exports.getConstraintName = getConstraintName;
6393
6673
  exports.getDbStructureTableData = getDbStructureTableData;
6394
6674
  exports.getDbTableColumnsChecks = getDbTableColumnsChecks;
6675
+ exports.getDbVersion = getDbVersion;
6395
6676
  exports.getExcludeName = getExcludeName;
6396
6677
  exports.getIndexName = getIndexName;
6397
6678
  exports.getMigrationsSchemaAndTable = getMigrationsSchemaAndTable;