rake-db 2.36.6 → 2.36.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.d.ts CHANGED
@@ -37,6 +37,15 @@ declare namespace DbStructure {
37
37
  columns: Column[];
38
38
  sql: string;
39
39
  }
40
+ interface MaterializedView {
41
+ schemaName: string;
42
+ name: string;
43
+ deps: RakeDbAst.MaterializedView['deps'];
44
+ columns: Column[];
45
+ sql: string;
46
+ isPopulated: boolean;
47
+ tablespace?: string;
48
+ }
40
49
  interface Procedure {
41
50
  schemaName: string;
42
51
  name: string;
@@ -198,7 +207,8 @@ interface IntrospectedStructure {
198
207
  version: number;
199
208
  schemas: string[];
200
209
  tables: DbStructure.Table[];
201
- views: DbStructure.View[];
210
+ views?: DbStructure.View[];
211
+ materializedViews?: DbStructure.MaterializedView[];
202
212
  indexes: DbStructure.Index[];
203
213
  excludes: DbStructure.Exclude[];
204
214
  constraints: DbStructure.Constraint[];
@@ -217,12 +227,13 @@ interface IntrospectDbStructureParams {
217
227
  roles?: {
218
228
  whereSql?: string;
219
229
  };
230
+ loadViews?: boolean;
220
231
  loadDefaultPrivileges?: boolean;
221
232
  loadGrants?: boolean;
222
233
  }
223
234
  declare function getDbVersion(db: Adapter): Promise<number>;
224
235
  declare function introspectDbSchema(db: Adapter, params?: IntrospectDbStructureParams): Promise<IntrospectedStructure>;
225
- type RakeDbAst = RakeDbAst.Table | RakeDbAst.ChangeTable | RakeDbAst.RenameType | RakeDbAst.Schema | RakeDbAst.RenameSchema | RakeDbAst.Extension | RakeDbAst.Enum | RakeDbAst.EnumValues | RakeDbAst.RenameEnumValues | RakeDbAst.ChangeEnumValues | RakeDbAst.Domain | RakeDbAst.Collation | RakeDbAst.Constraint | RakeDbAst.RenameTableItem | RakeDbAst.View | RakeDbAst.Role | RakeDbAst.RenameRole | RakeDbAst.ChangeRole | RakeDbAst.DefaultPrivilege | RakeDbAst.TableRls | RakeDbAst.Policy | RakeDbAst.PolicyChange | RakeDbAst.Grant;
236
+ type RakeDbAst = RakeDbAst.Table | RakeDbAst.ChangeTable | RakeDbAst.RenameType | RakeDbAst.Schema | RakeDbAst.RenameSchema | RakeDbAst.Extension | RakeDbAst.Enum | RakeDbAst.EnumValues | RakeDbAst.RenameEnumValues | RakeDbAst.ChangeEnumValues | RakeDbAst.Domain | RakeDbAst.Collation | RakeDbAst.Constraint | RakeDbAst.RenameTableItem | RakeDbAst.View | RakeDbAst.MaterializedView | RakeDbAst.Role | RakeDbAst.RenameRole | RakeDbAst.ChangeRole | RakeDbAst.DefaultPrivilege | RakeDbAst.TableRls | RakeDbAst.Policy | RakeDbAst.PolicyChange | RakeDbAst.Grant;
226
237
  declare namespace RakeDbAst {
227
238
  export interface Table extends TableData {
228
239
  type: 'table';
@@ -408,11 +419,83 @@ declare namespace RakeDbAst {
408
419
  temporary?: boolean;
409
420
  recursive?: boolean;
410
421
  columns?: string[];
411
- with?: {
412
- checkOption?: 'LOCAL' | 'CASCADED';
413
- securityBarrier?: boolean;
414
- securityInvoker?: boolean;
415
- };
422
+ checkOption?: 'LOCAL' | 'CASCADED';
423
+ securityBarrier?: boolean;
424
+ securityInvoker?: boolean;
425
+ }
426
+ /**
427
+ * Materialized view migration AST item.
428
+ */
429
+ export interface MaterializedView {
430
+ /**
431
+ * Identifies the AST item as a materialized view.
432
+ */
433
+ type: 'materializedView';
434
+ /**
435
+ * Creates or drops the materialized view.
436
+ */
437
+ action: 'create' | 'drop';
438
+ /**
439
+ * Schema containing the materialized view.
440
+ */
441
+ schema?: string;
442
+ /**
443
+ * Materialized view name without schema.
444
+ */
445
+ name: string;
446
+ /**
447
+ * Column shape used by generated migration dependency analysis.
448
+ */
449
+ shape: ColumnsShape;
450
+ /**
451
+ * SQL query used to define the materialized view.
452
+ */
453
+ sql: RawSqlBase;
454
+ /**
455
+ * Options used when creating or dropping the materialized view.
456
+ */
457
+ options: MaterializedViewOptions;
458
+ /**
459
+ * Relations referenced by the materialized view SQL definition.
460
+ */
461
+ deps: {
462
+ schemaName: string;
463
+ name: string;
464
+ }[];
465
+ }
466
+ /**
467
+ * Options for materialized view create and drop migrations.
468
+ */
469
+ export interface MaterializedViewOptions {
470
+ /**
471
+ * Drops the materialized view only when it exists.
472
+ */
473
+ dropIfExists?: boolean;
474
+ /**
475
+ * Drop behavior for dependent objects.
476
+ */
477
+ dropMode?: DropMode;
478
+ /**
479
+ * Explicit materialized view column names.
480
+ */
481
+ columns?: string[];
482
+ /**
483
+ * Adds WITH DATA or WITH NO DATA when creating the materialized view.
484
+ */
485
+ withData?: boolean;
486
+ }
487
+ /**
488
+ * Options for refreshing a materialized view.
489
+ */
490
+ export interface RefreshMaterializedViewOptions {
491
+ /**
492
+ * Refreshes without blocking concurrent selects.
493
+ */
494
+ concurrently?: boolean;
495
+ /**
496
+ * Adds WITH DATA or WITH NO DATA to the refresh statement.
497
+ */
498
+ withData?: boolean;
416
499
  }
417
500
  export interface Role extends DbStructure.Role {
418
501
  type: 'role';
@@ -1487,11 +1570,9 @@ declare class Migration<CT = unknown> {
1487
1570
  * temporary: true,
1488
1571
  * recursive: true,
1489
1572
  * columns: ['n'],
1490
- * with: {
1491
- * checkOption: 'LOCAL', // or 'CASCADED'
1492
- * securityBarrier: true,
1493
- * securityInvoker: true,
1494
- * },
1573
+ * checkOption: 'LOCAL', // or 'CASCADED'
1574
+ * securityBarrier: true,
1575
+ * securityInvoker: true,
1495
1576
  * },
1496
1577
  * `
1497
1578
  * VALUES (1)
@@ -1529,6 +1610,43 @@ declare class Migration<CT = unknown> {
1529
1610
  * @param sql - SQL to create the view with
1530
1611
  */
1531
1612
  dropView(name: string, sql: string | RawSqlBase): Promise<void>;
1613
+ /**
1614
+ * Create a materialized view, drop it on rollback.
1615
+ *
1616
+ * @param name - name of the materialized view
1617
+ * @param options - materialized view options
1618
+ * @param sql - SQL to create the materialized view with
1619
+ */
1620
+ createMaterializedView(name: string, options: RakeDbAst.MaterializedViewOptions, sql: string | RawSqlBase): Promise<void>;
1621
+ /**
1622
+ * Create a materialized view, drop it on rollback.
1623
+ *
1624
+ * @param name - name of the materialized view
1625
+ * @param sql - SQL to create the materialized view with
1626
+ */
1627
+ createMaterializedView(name: string, sql: string | RawSqlBase): Promise<void>;
1628
+ /**
1629
+ * Drop a materialized view, create it on rollback.
1630
+ *
1631
+ * @param name - name of the materialized view
1632
+ * @param options - materialized view options
1633
+ * @param sql - SQL to create the materialized view with
1634
+ */
1635
+ dropMaterializedView(name: string, options: RakeDbAst.MaterializedViewOptions, sql: string | RawSqlBase): Promise<void>;
1636
+ /**
1637
+ * Drop a materialized view, create it on rollback.
1638
+ *
1639
+ * @param name - name of the materialized view
1640
+ * @param sql - SQL to create the materialized view with
1641
+ */
1642
+ dropMaterializedView(name: string, sql: string | RawSqlBase): Promise<void>;
1643
+ /**
1644
+ * Refresh a materialized view.
1645
+ *
1646
+ * @param name - name of the materialized view
1647
+ * @param options - refresh options
1648
+ */
1649
+ refreshMaterializedView(name: string, options?: RakeDbAst.RefreshMaterializedViewOptions): Promise<void>;
1532
1650
  /**
1533
1651
  * Returns boolean to know if table exists:
1534
1652
  *
package/dist/index.js CHANGED
@@ -427,7 +427,7 @@ const createTable$1 = async (migration, tableMethods, up, tableName, first, seco
427
427
  });
428
428
  } else shape = tableData = pqb_internal.emptyObject;
429
429
  const schema = migration.adapter.getSchema();
430
- const ast = makeAst$4(schema, up, tableName, shape, tableData, options, migration.options.noPrimaryKey);
430
+ const ast = makeAst$5(schema, up, tableName, shape, tableData, options, migration.options.noPrimaryKey);
431
431
  fn && validatePrimaryKey(ast);
432
432
  const queries = astToQueries$1(schema, ast, snakeCase, language);
433
433
  for (const { then, ...query } of queries) {
@@ -442,7 +442,7 @@ const createTable$1 = async (migration, tableMethods, up, tableName, first, seco
442
442
  });
443
443
  } };
444
444
  };
445
- const makeAst$4 = (schema, up, tableName, shape, tableData, options, noPrimaryKey) => {
445
+ const makeAst$5 = (schema, up, tableName, shape, tableData, options, noPrimaryKey) => {
446
446
  const shapePKeys = [];
447
447
  for (const key in shape) if (shape[key].data.primaryKey) shapePKeys.push(key);
448
448
  const { primaryKey } = tableData;
@@ -860,13 +860,13 @@ const changeTable = async (migration, tableChangeMethods, up, tableName, options
860
860
  standaloneCheckChanges.length = 0;
861
861
  const changeData = fn?.(tableChanger) || {};
862
862
  const schema = migration.adapter.getSchema();
863
- const queries = astToQueries(schema, makeAst$3(schema, up, tableName, changeData, changeTableData, options), snakeCase, language);
863
+ const queries = astToQueries(schema, makeAst$4(schema, up, tableName, changeData, changeTableData, options), snakeCase, language);
864
864
  for (const query of queries) {
865
865
  const result = await migration.adapter.arrays(interpolateSqlValues(query));
866
866
  query.then?.(result);
867
867
  }
868
868
  };
869
- const makeAst$3 = (schema, up, name, changeData, changeTableData, options) => {
869
+ const makeAst$4 = (schema, up, name, changeData, changeTableData, options) => {
870
870
  const { comment } = options;
871
871
  const shape = {};
872
872
  const consumedChanges = {};
@@ -1139,10 +1139,10 @@ const renameColumnSql = (from, to) => {
1139
1139
  return `RENAME COLUMN "${from}" TO "${to}"`;
1140
1140
  };
1141
1141
  const createView = async (migration, up, name, options, sql) => {
1142
- const query = astToQuery$1(makeAst$2(migration.adapter.getSchema(), up, name, options, sql));
1142
+ const query = astToQuery$2(makeAst$3(migration.adapter.getSchema(), up, name, options, sql));
1143
1143
  await migration.adapter.arrays(interpolateSqlValues(query));
1144
1144
  };
1145
- const makeAst$2 = (schema, up, fullName, options, sql) => {
1145
+ const makeAst$3 = (schema, up, fullName, options, sql) => {
1146
1146
  if (typeof sql === "string") sql = (0, pqb_internal.raw)({ raw: sql });
1147
1147
  const [s, name] = getSchemaAndTableFromName(schema, fullName);
1148
1148
  return {
@@ -1156,7 +1156,7 @@ const makeAst$2 = (schema, up, fullName, options, sql) => {
1156
1156
  deps: []
1157
1157
  };
1158
1158
  };
1159
- const astToQuery$1 = (ast) => {
1159
+ const astToQuery$2 = (ast) => {
1160
1160
  const values = [];
1161
1161
  const sql = [];
1162
1162
  const { options } = ast;
@@ -1168,11 +1168,10 @@ const astToQuery$1 = (ast) => {
1168
1168
  if (options?.recursive) sql.push("RECURSIVE");
1169
1169
  sql.push(`VIEW ${sqlName}`);
1170
1170
  if (options?.columns) sql.push(`(${options.columns.map((column) => `"${column}"`).join(", ")})`);
1171
- const withOptions = options?.with;
1172
1171
  const list = [];
1173
- if (withOptions?.checkOption) list.push(`check_option = ${(0, pqb_internal.singleQuote)(withOptions.checkOption)}`);
1174
- if (withOptions?.securityBarrier) list.push(`security_barrier = true`);
1175
- if (withOptions?.securityInvoker !== false) list.push(`security_invoker = true`);
1172
+ if (options?.checkOption) list.push(`check_option = ${(0, pqb_internal.singleQuote)(options.checkOption)}`);
1173
+ if (options?.securityBarrier) list.push(`security_barrier = true`);
1174
+ if (options?.securityInvoker !== false) list.push(`security_invoker = true`);
1176
1175
  if (list.length) sql.push(`WITH ( ${list.join(", ")} )`);
1177
1176
  sql.push(`AS (${ast.sql.toSQL({ values })})`);
1178
1177
  } else {
@@ -1186,6 +1185,58 @@ const astToQuery$1 = (ast) => {
1186
1185
  values
1187
1186
  };
1188
1187
  };
1188
+ const createMaterializedView = async (migration, up, name, options, sql) => {
1189
+ const query = astToQuery$1(makeAst$2(migration.adapter.getSchema(), up, name, options, sql));
1190
+ await migration.adapter.arrays(interpolateSqlValues(query));
1191
+ };
1192
+ const refreshMaterializedView = async (migration, name, options = {}) => {
1193
+ if (options.concurrently && options.withData === false) throw new Error("Cannot refresh a materialized view concurrently with WITH NO DATA");
1194
+ const [s, viewName] = getSchemaAndTableFromName(migration.adapter.getSchema(), name);
1195
+ const sql = ["REFRESH MATERIALIZED VIEW"];
1196
+ if (options.concurrently) sql.push("CONCURRENTLY");
1197
+ sql.push(`${s ? `"${s}".` : ""}"${viewName}"`);
1198
+ pushWithData(sql, options);
1199
+ await migration.adapter.arrays(sql.join(" "));
1200
+ };
1201
+ const makeAst$2 = (schema, up, fullName, options, sql) => {
1202
+ if (typeof sql === "string") sql = (0, pqb_internal.raw)({ raw: sql });
1203
+ const [s, name] = getSchemaAndTableFromName(schema, fullName);
1204
+ return {
1205
+ type: "materializedView",
1206
+ action: up ? "create" : "drop",
1207
+ schema: s,
1208
+ name,
1209
+ shape: {},
1210
+ sql,
1211
+ options,
1212
+ deps: []
1213
+ };
1214
+ };
1215
+ const astToQuery$1 = (ast) => {
1216
+ const values = [];
1217
+ const sql = [];
1218
+ const { options } = ast;
1219
+ const sqlName = `${ast.schema ? `"${ast.schema}".` : ""}"${ast.name}"`;
1220
+ if (ast.action === "create") {
1221
+ sql.push(`CREATE MATERIALIZED VIEW ${sqlName}`);
1222
+ if (options?.columns) sql.push(`(${options.columns.map((column) => `"${column}"`).join(", ")})`);
1223
+ sql.push(`AS (${ast.sql.toSQL({ values })})`);
1224
+ pushWithData(sql, options);
1225
+ } else {
1226
+ sql.push("DROP MATERIALIZED VIEW");
1227
+ if (options?.dropIfExists) sql.push(`IF EXISTS`);
1228
+ sql.push(sqlName);
1229
+ if (options?.dropMode) sql.push(options.dropMode);
1230
+ }
1231
+ return {
1232
+ text: sql.join(" "),
1233
+ values
1234
+ };
1235
+ };
1236
+ const pushWithData = (sql, options) => {
1237
+ if (options.withData === true) sql.push("WITH DATA");
1238
+ else if (options.withData === false) sql.push("WITH NO DATA");
1239
+ };
1189
1240
  const serializers = {
1190
1241
  super: (b) => `${b ? "" : "NO"}SUPERUSER`,
1191
1242
  inherit: (b) => `${b ? "" : "NO"}INHERIT`,
@@ -2383,6 +2434,23 @@ var Migration = class {
2383
2434
  const [options, sql] = args.length === 2 ? args : [pqb_internal.emptyObject, args[0]];
2384
2435
  return createView(this, !this.up, name, options, sql);
2385
2436
  }
2437
+ createMaterializedView(name, ...args) {
2438
+ const [options, sql] = args.length === 2 ? args : [pqb_internal.emptyObject, args[0]];
2439
+ return createMaterializedView(this, this.up, name, options, sql);
2440
+ }
2441
+ dropMaterializedView(name, ...args) {
2442
+ const [options, sql] = args.length === 2 ? args : [pqb_internal.emptyObject, args[0]];
2443
+ return createMaterializedView(this, !this.up, name, options, sql);
2444
+ }
2445
+ /**
2446
+ * Refresh a materialized view.
2447
+ *
2448
+ * @param name - name of the materialized view
2449
+ * @param options - refresh options
2450
+ */
2451
+ refreshMaterializedView(name, options) {
2452
+ return refreshMaterializedView(this, name, options);
2453
+ }
2386
2454
  /**
2387
2455
  * Returns boolean to know if table exists:
2388
2456
  *
@@ -3709,10 +3777,11 @@ const astToGenerateItem = (config, ast, currentSchema) => {
3709
3777
  switch (ast.type) {
3710
3778
  case "table":
3711
3779
  case "changeTable":
3712
- case "view": {
3780
+ case "view":
3781
+ case "materializedView": {
3713
3782
  const schema = ast.schema ?? currentSchema;
3714
3783
  const table = `${schema}.${ast.name}`;
3715
- if (ast.type === "table" || ast.type === "view") {
3784
+ if (ast.type === "table" || ast.type === "view" || ast.type === "materializedView") {
3716
3785
  const keys = ast.action === "create" ? add : drop;
3717
3786
  keys.push(table);
3718
3787
  deps.push(schema);
@@ -4251,31 +4320,41 @@ const astEncoders = {
4251
4320
  })}, ${(0, pqb_internal.singleQuote)(ast.from)}, ${(0, pqb_internal.singleQuote)(ast.to)});`];
4252
4321
  },
4253
4322
  view(ast, _, currentSchema) {
4254
- const code = [`await db.createView(${quoteSchemaTable(ast, currentSchema)}`];
4323
+ const code = [`await db.${ast.action}View(${quoteSchemaTable(ast, currentSchema)}`];
4255
4324
  const options = [];
4256
- if (ast.options.recursive) options.push("recursive: true,");
4257
- const w = ast.options.with;
4258
- if (w?.checkOption) options.push(`checkOption: '${w.checkOption}',`);
4259
- if (w?.securityBarrier) options.push(`securityBarrier: ${w.securityBarrier},`);
4260
- if (w?.securityInvoker) options.push(`securityInvoker: ${w.securityInvoker},`);
4325
+ const { options: astOptions } = ast;
4326
+ if (astOptions.createOrReplace !== void 0) options.push(`createOrReplace: ${astOptions.createOrReplace},`);
4327
+ if (astOptions.dropIfExists !== void 0) options.push(`dropIfExists: ${astOptions.dropIfExists},`);
4328
+ if (astOptions.dropMode) options.push(`dropMode: '${astOptions.dropMode}',`);
4329
+ if (astOptions.temporary !== void 0) options.push(`temporary: ${astOptions.temporary},`);
4330
+ if (astOptions.recursive !== void 0) options.push(`recursive: ${astOptions.recursive},`);
4331
+ if (astOptions.columns) options.push(`columns: [${astOptions.columns.map(pqb_internal.singleQuote).join(", ")}],`);
4332
+ if (astOptions.checkOption) options.push(`checkOption: '${astOptions.checkOption}',`);
4333
+ if (astOptions.securityBarrier !== void 0) options.push(`securityBarrier: ${astOptions.securityBarrier},`);
4334
+ if (astOptions.securityInvoker !== void 0) options.push(`securityInvoker: ${astOptions.securityInvoker},`);
4261
4335
  if (options.length) {
4262
4336
  (0, pqb_internal.addCode)(code, ", {");
4263
4337
  code.push(options, "}");
4264
4338
  }
4265
4339
  (0, pqb_internal.addCode)(code, ", ");
4266
- if (!ast.sql._values) {
4267
- const raw = ast.sql._sql;
4268
- let sql;
4269
- if (typeof raw === "string") sql = raw;
4270
- else {
4271
- sql = "";
4272
- const parts = raw[0];
4273
- const last = parts.length - 1;
4274
- for (let i = 0; i < last; i++) sql += parts[i] + `\${${raw[i + 1]}}`;
4275
- sql += parts[last];
4276
- }
4277
- (0, pqb_internal.addCode)(code, (0, pqb_internal.backtickQuote)(sql));
4278
- } else (0, pqb_internal.addCode)(code, (0, pqb_internal.rawSqlToCode)(ast.sql, "db"));
4340
+ addViewSqlCode(code, ast.sql);
4341
+ (0, pqb_internal.addCode)(code, ");");
4342
+ return code;
4343
+ },
4344
+ materializedView(ast, _, currentSchema) {
4345
+ const code = [`await db.${ast.action}MaterializedView(${quoteSchemaTable(ast, currentSchema)}`];
4346
+ const options = [];
4347
+ const { options: astOptions } = ast;
4348
+ if (astOptions.dropIfExists !== void 0) options.push(`dropIfExists: ${astOptions.dropIfExists},`);
4349
+ if (astOptions.dropMode) options.push(`dropMode: '${astOptions.dropMode}',`);
4350
+ if (astOptions.columns) options.push(`columns: [${astOptions.columns.map(pqb_internal.singleQuote).join(", ")}],`);
4351
+ if (astOptions.withData !== void 0) options.push(`withData: ${astOptions.withData},`);
4352
+ if (options.length) {
4353
+ (0, pqb_internal.addCode)(code, ", {");
4354
+ code.push(options, "}");
4355
+ }
4356
+ (0, pqb_internal.addCode)(code, ", ");
4357
+ addViewSqlCode(code, ast.sql);
4279
4358
  (0, pqb_internal.addCode)(code, ");");
4280
4359
  return code;
4281
4360
  },
@@ -4451,6 +4530,21 @@ const isPolicyRecreateDefinition = (policy) => {
4451
4530
  const rawSqlStringToCode = (sql) => {
4452
4531
  return `db.sql${(0, pqb_internal.backtickQuote)(sql)}`;
4453
4532
  };
4533
+ const addViewSqlCode = (code, sqlAst) => {
4534
+ if (!sqlAst._values) {
4535
+ const raw = sqlAst._sql;
4536
+ let sql;
4537
+ if (typeof raw === "string") sql = raw;
4538
+ else {
4539
+ sql = "";
4540
+ const parts = raw[0];
4541
+ const last = parts.length - 1;
4542
+ for (let i = 0; i < last; i++) sql += parts[i] + `\${${raw[i + 1]}}`;
4543
+ sql += parts[last];
4544
+ }
4545
+ (0, pqb_internal.addCode)(code, (0, pqb_internal.backtickQuote)(sql));
4546
+ } else (0, pqb_internal.addCode)(code, (0, pqb_internal.rawSqlToCode)(sqlAst, "db"));
4547
+ };
4454
4548
  const roleParams = (name, ast, compare, to) => {
4455
4549
  const params = [];
4456
4550
  for (const key of [
@@ -4635,6 +4729,39 @@ JOIN pg_class c
4635
4729
  JOIN pg_rewrite r ON r.ev_class = c.oid
4636
4730
  WHERE ${filterSchema("nc.nspname")}
4637
4731
  ORDER BY c.relname`;
4732
+ const materializedViewsSql = `SELECT
4733
+ nc.nspname AS "schemaName",
4734
+ c.relname AS "name",
4735
+ (
4736
+ SELECT COALESCE(json_agg(t.*), '[]')
4737
+ FROM (
4738
+ SELECT
4739
+ ns.nspname AS "schemaName",
4740
+ obj.relname AS "name"
4741
+ FROM pg_class obj
4742
+ JOIN pg_depend dep ON dep.refobjid = obj.oid
4743
+ JOIN pg_rewrite rew ON rew.oid = dep.objid
4744
+ JOIN pg_namespace ns ON ns.oid = obj.relnamespace
4745
+ WHERE rew.ev_class = c.oid AND obj.oid <> c.oid
4746
+ ) t
4747
+ ) "deps",
4748
+ (SELECT coalesce(json_agg(t), '[]') FROM (${columnsSql({
4749
+ schema: "nc",
4750
+ table: "c",
4751
+ where: "a.attrelid = c.oid"
4752
+ })}) t) AS "columns",
4753
+ pg_get_viewdef(c.oid) AS "sql",
4754
+ c.relispopulated AS "isPopulated",
4755
+ spc.spcname AS "tablespace"
4756
+ FROM pg_namespace nc
4757
+ JOIN pg_class c
4758
+ ON nc.oid = c.relnamespace
4759
+ AND c.relkind = 'm'
4760
+ AND c.relpersistence != 't'
4761
+ JOIN pg_rewrite r ON r.ev_class = c.oid
4762
+ LEFT JOIN pg_tablespace spc ON spc.oid = c.reltablespace
4763
+ WHERE ${filterSchema("nc.nspname")}
4764
+ ORDER BY c.relname`;
4638
4765
  const indexesSql = `SELECT
4639
4766
  n.nspname "schemaName",
4640
4767
  t.relname "tableName",
@@ -4936,7 +5063,7 @@ const grantsSql = `SELECT COALESCE(json_agg(t.* ORDER BY t."target", t."schema",
4936
5063
  FROM pg_class c
4937
5064
  JOIN pg_namespace n ON n.oid = c.relnamespace
4938
5065
  JOIN LATERAL aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) ae ON true
4939
- WHERE c.relkind IN ('r', 'p')
5066
+ WHERE c.relkind IN ('r', 'p', 'v')
4940
5067
  AND ${filterSchema("n.nspname")}
4941
5068
  AND ae.grantee <> c.relowner
4942
5069
  GROUP BY "grantor", "grantee", "schema", "name", "target"
@@ -5068,7 +5195,7 @@ FROM pg_policy p
5068
5195
  JOIN pg_class c ON c.oid = p.polrelid
5069
5196
  JOIN pg_namespace n ON n.oid = c.relnamespace
5070
5197
  ORDER BY n.nspname, c.relname, p.polname`;
5071
- 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")}` : ""}`;
5198
+ const sql = (version, params) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(tablesSql(params?.rls), "tables")}, ${params?.loadViews ? `${jsonAgg(viewsSql, "views")}, ${jsonAgg(materializedViewsSql, "materializedViews")}, ` : ""}${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")}` : ""}`;
5072
5199
  async function getDbVersion(db) {
5073
5200
  const { rows: [{ version: versionString }] } = await db.query("SELECT version()");
5074
5201
  return +versionString.match(/\d+/)[0];
@@ -5080,10 +5207,11 @@ async function introspectDbSchema(db, params) {
5080
5207
  domain.checks = domain.checks?.filter((check) => check);
5081
5208
  nullsToUndefined(domain);
5082
5209
  }
5083
- for (const table of raw.tables) for (const column of table.columns) {
5084
- nullsToUndefined(column);
5085
- if (column.identity) nullsToUndefined(column.identity);
5086
- if (column.compression) column.compression = column.compression === "p" ? "pglz" : "lz4";
5210
+ for (const table of raw.tables) processColumns(table.columns);
5211
+ for (const view of raw.views || []) processColumns(view.columns);
5212
+ for (const materializedView of raw.materializedViews || []) {
5213
+ nullsToUndefined(materializedView);
5214
+ processColumns(materializedView.columns);
5087
5215
  }
5088
5216
  const indexes = [];
5089
5217
  const excludes = [];
@@ -5212,6 +5340,13 @@ const mapRawGrant = (raw) => {
5212
5340
  if (grantablePrivileges.length) grant.grantablePrivileges = grantablePrivileges;
5213
5341
  return grant;
5214
5342
  };
5343
+ const processColumns = (columns) => {
5344
+ for (const column of columns) {
5345
+ nullsToUndefined(column);
5346
+ if (column.identity) nullsToUndefined(column.identity);
5347
+ if (column.compression) column.compression = column.compression === "p" ? "pglz" : "lz4";
5348
+ }
5349
+ };
5215
5350
  const nullsToUndefined = (obj) => {
5216
5351
  for (const key in obj) if (obj[key] === null) obj[key] = void 0;
5217
5352
  };
@@ -5309,7 +5444,19 @@ const structureToAst = async (ctx, adapter, config) => {
5309
5444
  tableSchema: table.schemaName === ctx.currentSchema ? void 0 : table.schemaName,
5310
5445
  tableName: fkey.tableName
5311
5446
  });
5312
- for (const view of data.views) ast.push(viewToAst(ctx, data, domains, view));
5447
+ for (const view of data.views || []) ast.push(viewToAst(ctx, data, domains, view));
5448
+ for (const view of data.materializedViews || []) {
5449
+ ast.push(materializedViewToAst(ctx, data, domains, view));
5450
+ const indexes = materializedViewIndexesToAst(view, data);
5451
+ if (indexes.length) ast.push({
5452
+ type: "changeTable",
5453
+ schema: view.schemaName === ctx.currentSchema ? void 0 : view.schemaName,
5454
+ name: view.name,
5455
+ shape: {},
5456
+ add: { indexes },
5457
+ drop: {}
5458
+ });
5459
+ }
5313
5460
  if (data.roles) for (const role of data.roles) ast.push({
5314
5461
  type: "role",
5315
5462
  action: "create",
@@ -5479,13 +5626,22 @@ const isColumnCheck = (it) => {
5479
5626
  const viewToAst = (ctx, data, domains, view) => {
5480
5627
  const shape = makeDbStructureColumnsShape(ctx, data, domains, view);
5481
5628
  const options = {};
5482
- if (view.isRecursive) options.recursive = true;
5483
- if (view.with) {
5484
- const withOptions = {};
5485
- options.with = withOptions;
5486
- for (const pair of view.with) {
5487
- const [key, value] = pair.split("=");
5488
- withOptions[(0, pqb_internal.toCamelCase)(key)] = value === "true" ? true : value === "false" ? false : value;
5629
+ if (view.isRecursive) {
5630
+ options.recursive = true;
5631
+ options.columns = view.columns.map((column) => column.name);
5632
+ }
5633
+ if (view.with) for (const pair of view.with) {
5634
+ const [key, value] = pair.split("=");
5635
+ switch ((0, pqb_internal.toCamelCase)(key)) {
5636
+ case "checkOption":
5637
+ if (value === "LOCAL" || value === "CASCADED") options.checkOption = value;
5638
+ break;
5639
+ case "securityBarrier":
5640
+ options.securityBarrier = value === "true";
5641
+ break;
5642
+ case "securityInvoker":
5643
+ options.securityInvoker = value === "true";
5644
+ break;
5489
5645
  }
5490
5646
  }
5491
5647
  return {
@@ -5499,11 +5655,40 @@ const viewToAst = (ctx, data, domains, view) => {
5499
5655
  deps: view.deps
5500
5656
  };
5501
5657
  };
5658
+ const materializedViewToAst = (ctx, data, domains, view) => {
5659
+ const options = {};
5660
+ if (!view.isPopulated) options.withData = false;
5661
+ return {
5662
+ type: "materializedView",
5663
+ action: "create",
5664
+ schema: view.schemaName === ctx.currentSchema ? void 0 : view.schemaName,
5665
+ name: view.name,
5666
+ shape: makeDbStructureColumnsShape(ctx, data, domains, view),
5667
+ sql: (0, pqb_internal.raw)({ raw: view.sql }),
5668
+ options,
5669
+ deps: view.deps
5670
+ };
5671
+ };
5672
+ const materializedViewIndexesToAst = (view, data) => {
5673
+ return data.indexes.filter(filterByTableSchema(view.name, view.schemaName)).map((index) => ({
5674
+ columns: index.columns.map((column) => ({
5675
+ ..."expression" in column ? { expression: column.expression } : { column: (0, pqb_internal.toCamelCase)(column.column) },
5676
+ collate: column.collate,
5677
+ opclass: column.opclass,
5678
+ order: column.order
5679
+ })),
5680
+ options: {
5681
+ ...makeIndexOrExcludeOptions(view.name, index, "indexes"),
5682
+ include: index.include?.map(pqb_internal.toCamelCase)
5683
+ }
5684
+ }));
5685
+ };
5502
5686
  const makeDbStructureColumnsShape = (ctx, data, domains, table, tableData) => {
5503
5687
  const shape = {};
5504
5688
  const checks = tableData ? getDbTableColumnsChecks(tableData) : void 0;
5689
+ const dbTable = tableData ? table : void 0;
5505
5690
  for (const item of table.columns) {
5506
- const [key, column] = dbColumnToAst(ctx, data, domains, table.name, item, table, tableData, checks);
5691
+ const [key, column] = dbColumnToAst(ctx, data, domains, table.name, item, dbTable, tableData, checks);
5507
5692
  shape[key] = column;
5508
5693
  }
5509
5694
  return shape;