orchid-orm 1.72.6 → 1.72.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.
@@ -1,18 +1,18 @@
1
1
  import { astToMigration, concatSchemaAndName, createMigrationInterface, dbColumnToAst, encodeColumnDefault, getConstraintName, getDbStructureTableData, getDbTableColumnsChecks, getDbVersion, getExcludeName, getIndexName, getMigrationsSchemaAndTable, getSchemaAndTableFromName, instantiateDbColumn, introspectDbSchema, makeDomainsMap, makeFileVersion, makeStructureToAstCtx, migrate, migrateAndClose, promptSelect, rakeDbCommands, saveMigratedVersion, structureToAst, tableToAst, writeMigrationFile } from "rake-db";
2
- import { ArrayColumn, DomainColumn, EnumColumn, RawSql, UnknownColumn, VirtualColumn, addCode, codeToString, colors, columnsShapeToCode, deepCompare, emptyArray, emptyObject, exhaustive, getColumnBaseType, getDriverErrorCode, getFreeSetAlias, getImportPath, getQuerySchema, getSupportedDefaultPrivileges, pathToLog, pluralize, pushTableDataCode, quoteObjectKey, singleQuote, toArray, toCamelCase, toPascalCase, toSnakeCase } from "pqb/internal";
2
+ import { ArrayColumn, DomainColumn, EnumColumn, RawSql, UnknownColumn, VirtualColumn, addCode, codeToString, colors, columnsShapeToCode, deepCompare, emptyArray, emptyObject, exhaustive, getColumnBaseType, getDriverErrorCode, getFreeSetAlias, getImportPath, getQuerySchema, getSupportedDefaultPrivileges, pathToLog, pluralize, pushTableDataCode, quoteObjectKey, raw, singleQuote, toArray, toCamelCase, toPascalCase, toSnakeCase } from "pqb/internal";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import fs from "node:fs/promises";
6
6
  import typescript from "typescript";
7
7
  export * from "rake-db";
8
- const compareSqlExpressions = async (tableExpressions, adapter) => {
9
- if (!tableExpressions.length) return;
8
+ const compareSqlExpressions = async (expressions, adapter) => {
9
+ if (!expressions.length) return;
10
10
  let id = 1;
11
- for (const { source, compare, handle } of tableExpressions) {
11
+ for (const { source, compare, handle } of expressions) {
12
12
  const viewName = `orchidTmpView${id++}`;
13
13
  const values = [];
14
14
  const combinedQueries = [
15
- `CREATE TEMPORARY VIEW ${viewName} AS (SELECT ${compare.map(({ inDb, inCode }, i) => `${inDb} AS "*inDb-${i}*", ${inCode.map((s, j) => `(${typeof s === "string" ? s : s.toSQL({ values })}) "*inCode-${i}-${j}*"`).join(", ")}`).join(", ")} FROM ${source})`,
15
+ `CREATE TEMPORARY VIEW ${viewName} AS (SELECT ${compare.map(({ inDb, inCode }, i) => `${inDb} AS "*inDb-${i}*", ${inCode.map((s, j) => `(${typeof s === "string" ? s : s.toSQL({ values })}) "*inCode-${i}-${j}*"`).join(", ")}`).join(", ")}${source ? ` FROM ${source}` : ""})`,
16
16
  `SELECT pg_get_viewdef('${viewName}') v`,
17
17
  `DROP VIEW ${viewName}`
18
18
  ].join("; ");
@@ -27,6 +27,50 @@ const compareSqlExpressions = async (tableExpressions, adapter) => {
27
27
  handle(compareSqlExpressionResult(result.rows[0].v, compare[0].inCode));
28
28
  }
29
29
  };
30
+ const compareViewsExpressions = async (adapter, compare) => {
31
+ if (!compare.length) return;
32
+ const queries = [];
33
+ let id = 1;
34
+ compare.forEach(({ inCode, ast }, i) => {
35
+ const viewName = `orchidTmpView${id++}ForViews`;
36
+ queries.push({ batch: [
37
+ `SAVEPOINT "${viewName}S"`,
38
+ `CREATE TEMPORARY${"recursive" in ast.options && ast.options.recursive ? " RECURSIVE" : ""} VIEW "${viewName}" (${ast.options.columns?.map((column) => `"${column}"`).join(", ")}) AS (${inCode})`,
39
+ `SELECT ${i} i, '${viewName}' v, pg_get_viewdef('"${viewName}"') sql`,
40
+ `DROP VIEW "${viewName}"`,
41
+ `RELEASE SAVEPOINT "${viewName}S"`
42
+ ] });
43
+ });
44
+ let results;
45
+ try {
46
+ const sql = queries.flatMap((q) => q.batch).join(";");
47
+ const query = () => adapter.query(sql, []);
48
+ results = await (adapter.isInTransaction() ? adapter.savepoint("orchidOrmGeneratorViews", query) : query());
49
+ } catch {
50
+ results = (await Promise.all(queries.map(async ({ batch: queries }, i) => {
51
+ const sql = queries.join(";");
52
+ const query = () => adapter.query(sql, []);
53
+ return await (adapter.isInTransaction() ? adapter.savepoint(`orchidOrmGeneratorViews${i}`, query) : query()).catch((err) => {
54
+ if (typeof err === "object") {
55
+ const code = getDriverErrorCode(err);
56
+ if (code === "42703" || code === "42704" || code === "42P01") return [];
57
+ }
58
+ throw err;
59
+ });
60
+ }))).flat();
61
+ }
62
+ const handled = /* @__PURE__ */ new Set();
63
+ for (const result of results) for (const row of result.rows) if ("sql" in row) {
64
+ const { i, v } = row;
65
+ const cmp = compare[i];
66
+ const hasQuote = !!cmp.inDb.match(/\w*WITH RECURSIVE "/);
67
+ if (row.sql.replaceAll(hasQuote ? v : `"${v}"`, cmp.ast.name) !== cmp.inDb) cmp.onNotEqual();
68
+ handled.add(i);
69
+ }
70
+ compare.forEach((cmp, i) => {
71
+ if (!handled.has(i)) cmp.onNotEqual();
72
+ });
73
+ };
30
74
  const compareSqlExpressionResult = (resultSql, inCode) => {
31
75
  let pos = 7;
32
76
  const rgx = /\s+AS\s+"\*(inDb-\d+|inCode-\d+-\d+)\*",?/g;
@@ -84,8 +128,9 @@ const processSchemas = async (ast, dbStructure, { codeItems: { schemas }, verify
84
128
  if (i) {
85
129
  const from = dropSchemas[i - 1];
86
130
  dropSchemas.splice(i - 1, 1);
131
+ const views = dbStructure.views || [];
87
132
  renameSchemaInStructures(dbStructure.tables, from, schema);
88
- renameSchemaInStructures(dbStructure.views, from, schema);
133
+ renameSchemaInStructures(views, from, schema);
89
134
  renameSchemaInStructures(dbStructure.indexes, from, schema);
90
135
  renameSchemaInStructures(dbStructure.excludes, from, schema);
91
136
  renameSchemaInStructures(dbStructure.constraints, from, schema);
@@ -412,7 +457,7 @@ const renameColumn = (columns, from, to) => {
412
457
  const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems: { domains }, structureToAstCtx, currentSchema, internal: { generatorIgnore } }, pendingDbTypes) => {
413
458
  const codeDomains = [];
414
459
  if (domains) for (const { schemaName, name, column } of domains) codeDomains.push(makeComparableDomain(currentSchema, schemaName, name, column));
415
- const tableExpressions = [];
460
+ const expressions = [];
416
461
  const holdCodeDomains = /* @__PURE__ */ new Set();
417
462
  for (const domain of dbStructure.domains) {
418
463
  if (generatorIgnore?.schemas?.includes(domain.schemaName) || generatorIgnore?.domains?.includes(domain.name)) continue;
@@ -438,7 +483,7 @@ const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems
438
483
  pushCompareDefault(compare, domain, found);
439
484
  pushCompareChecks(compare, domain, found);
440
485
  const source = `(VALUES (NULL::${getColumnDbType(dbColumn, currentSchema)})) t(value)`;
441
- tableExpressions.push({
486
+ expressions.push({
442
487
  compare,
443
488
  source,
444
489
  handle(i) {
@@ -469,8 +514,8 @@ const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems
469
514
  ast.push(createAst(codeDomain));
470
515
  pendingDbTypes.add(codeDomain.schemaName, codeDomain.name);
471
516
  }
472
- if (tableExpressions.length) {
473
- await compareSqlExpressions(tableExpressions, adapter);
517
+ if (expressions.length) {
518
+ await compareSqlExpressions(expressions, adapter);
474
519
  if (holdCodeDomains.size) for (const codeDomain of holdCodeDomains.keys()) {
475
520
  ast.push(createAst(codeDomain));
476
521
  pendingDbTypes.add(codeDomain.schemaName, codeDomain.name);
@@ -1668,13 +1713,13 @@ const processTables = async (ast, domainsMap, adapter, dbStructure, config, { st
1668
1713
  values: [],
1669
1714
  expressions: []
1670
1715
  };
1671
- const tableExpressions = [];
1716
+ const expressions = [];
1672
1717
  const { changeTables, changeTableSchemas, dropTables, tableShapes } = collectChangeAndDropTables(adapter, config, tables, dbStructure, currentSchema, createTables, generatorIgnore);
1673
1718
  applyChangeTableSchemas(changeTableSchemas, currentSchema, ast);
1674
1719
  await applyCreateOrRenameTables(dbStructure, createTables, dropTables, changeTables, tableShapes, currentSchema, ast, verifying);
1675
- await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes);
1720
+ await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, expressions, verifying, pendingDbTypes);
1676
1721
  processForeignKeys(config, ast, changeTables, currentSchema, tableShapes);
1677
- await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(tableExpressions, adapter)]);
1722
+ await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(expressions, adapter)]);
1678
1723
  await processTableRls(adapter, ast, dbStructure, tables, currentSchema, generatorIgnore);
1679
1724
  for (const dbTable of dropTables) ast.push(tableToAst(structureToAstCtx, dbStructure, dbTable, "drop", domainsMap));
1680
1725
  };
@@ -1738,7 +1783,7 @@ const applyChangeTableSchemas = (changeTableSchemas, currentSchema, ast) => {
1738
1783
  });
1739
1784
  }
1740
1785
  };
1741
- const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes) => {
1786
+ const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, expressions, verifying, pendingDbTypes) => {
1742
1787
  const compareExpressions = [];
1743
1788
  const typeCastsCache = {};
1744
1789
  for (const changeTableData of changeTables) {
@@ -1760,7 +1805,7 @@ const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStr
1760
1805
  }
1761
1806
  const tableName = codeTable.table;
1762
1807
  const source = `(VALUES (${types.map((x) => `NULL::${x}`).join(", ")})) "${tableName}"(${names.map((x) => `"${x}"`).join(", ")})`;
1763
- tableExpressions.push(...compareExpressions.map((x) => ({
1808
+ expressions.push(...compareExpressions.map((x) => ({
1764
1809
  ...x,
1765
1810
  source
1766
1811
  })));
@@ -2382,13 +2427,16 @@ const normalizeRoleName = (name) => {
2382
2427
  };
2383
2428
  const getSchemaWideTargets = (dbStructure, targetKey, schemas) => {
2384
2429
  const selectedSchemas = new Set(schemas);
2385
- if (targetKey === "tables") return [...dbStructure.tables.map((table) => ({
2386
- target: `${table.schemaName}.${table.name}`,
2387
- outputTarget: `${table.schemaName}.${table.name}`
2388
- })), ...dbStructure.views.map((view) => ({
2389
- target: `${view.schemaName}.${view.name}`,
2390
- outputTarget: `${view.schemaName}.${view.name}`
2391
- }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2430
+ if (targetKey === "tables") {
2431
+ const views = dbStructure.views || [];
2432
+ return [...dbStructure.tables.map((table) => ({
2433
+ target: `${table.schemaName}.${table.name}`,
2434
+ outputTarget: `${table.schemaName}.${table.name}`
2435
+ })), ...views.map((view) => ({
2436
+ target: `${view.schemaName}.${view.name}`,
2437
+ outputTarget: `${view.schemaName}.${view.name}`
2438
+ }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2439
+ }
2392
2440
  if (targetKey === "sequences" || targetKey === "routines") return getSchemaWideGrantTargets(dbStructure, targetKey, selectedSchemas);
2393
2441
  return [];
2394
2442
  };
@@ -2455,6 +2503,264 @@ const addGrantAst = (ast, action, grant, privileges, privilegeKey) => {
2455
2503
  grantedBy: grant.grantedBy
2456
2504
  });
2457
2505
  };
2506
+ const processViews = async (ast, adapter, dbStructure, { codeItems: { views: allViews }, currentSchema, internal: { generatorIgnore } }) => {
2507
+ const createViews = [];
2508
+ const changeViews = [];
2509
+ const dropViews = [];
2510
+ const ignoredViews = makeIgnoredViews$1(generatorIgnore, currentSchema);
2511
+ const views = allViews.filter((view) => !view.materialized);
2512
+ for (const codeView of views) {
2513
+ if (isIgnoredView$1(ignoredViews, generatorIgnore, currentSchema, codeView)) continue;
2514
+ const schemaName = codeView.q.schema ?? currentSchema;
2515
+ const dbView = dbStructure.views?.find((view) => view.schemaName === schemaName && view.name === codeView.name);
2516
+ if (dbView) changeViews.push({
2517
+ codeView,
2518
+ dbView
2519
+ });
2520
+ else createViews.push(codeView);
2521
+ }
2522
+ for (const dbView of dbStructure.views ?? []) {
2523
+ if (generatorIgnore?.schemas?.includes(dbView.schemaName) || ignoredViews.some((ignore) => matchesIgnoredView$1(ignore, dbView, currentSchema))) continue;
2524
+ if (!views.find((view) => view.name === dbView.name && (view.q.schema ?? currentSchema) === dbView.schemaName)) dropViews.push(dbView);
2525
+ }
2526
+ for (const codeView of createViews) ast.push(codeViewToAst$1(codeView, currentSchema, "create"));
2527
+ await applyChangeViews$1(ast, adapter, changeViews, currentSchema);
2528
+ for (const dbView of dropViews) ast.push(dbViewToAst$1(dbView, currentSchema, "drop"));
2529
+ };
2530
+ const applyChangeViews$1 = async (ast, adapter, changeViews, currentSchema) => {
2531
+ const compare = [];
2532
+ for (const { codeView, dbView } of changeViews) {
2533
+ const from = dbViewToAst$1(dbView, currentSchema, "drop");
2534
+ const to = codeViewToAst$1(codeView, currentSchema, "create");
2535
+ if (!isViewOptionsEqual(from.options, to.options)) {
2536
+ pushRecreateView$1(ast, from, to);
2537
+ continue;
2538
+ }
2539
+ const codeSql = typeof to.sql === "string" ? to.sql : to.sql.toSQL({ values: [] });
2540
+ compare.push({
2541
+ inDb: dbView.sql,
2542
+ inCode: codeSql,
2543
+ ast: to,
2544
+ onNotEqual() {
2545
+ pushRecreateView$1(ast, from, to);
2546
+ }
2547
+ });
2548
+ }
2549
+ await compareViewsExpressions(adapter, compare);
2550
+ };
2551
+ const pushRecreateView$1 = (ast, from, to) => {
2552
+ ast.push(from, to);
2553
+ };
2554
+ const codeViewToAst$1 = (view, currentSchema, action) => {
2555
+ const schema = view.q.schema ?? currentSchema;
2556
+ const sql = typeof view.viewData.sql === "string" ? raw({ raw: view.viewData.sql }) : view.viewData.sql ?? raw({ raw: "" });
2557
+ return {
2558
+ type: "view",
2559
+ action,
2560
+ schema: schema === currentSchema ? void 0 : schema,
2561
+ name: view.name,
2562
+ shape: view.shape,
2563
+ sql,
2564
+ options: {
2565
+ recursive: view.viewData.recursive,
2566
+ columns: Object.keys(view.shape),
2567
+ checkOption: view.viewData.checkOption,
2568
+ securityBarrier: view.viewData.securityBarrier,
2569
+ securityInvoker: view.viewData.securityInvoker ?? true
2570
+ },
2571
+ deps: []
2572
+ };
2573
+ };
2574
+ const dbViewToAst$1 = (view, currentSchema, action) => {
2575
+ return {
2576
+ type: "view",
2577
+ action,
2578
+ schema: view.schemaName === currentSchema ? void 0 : view.schemaName,
2579
+ name: view.name,
2580
+ shape: {},
2581
+ sql: raw({ raw: view.sql }),
2582
+ options: dbViewOptionsToAst(view),
2583
+ deps: view.deps
2584
+ };
2585
+ };
2586
+ const dbViewOptionsToAst = (view) => {
2587
+ const options = {};
2588
+ options.columns = view.columns.map((column) => column.name);
2589
+ if (view.isRecursive) options.recursive = true;
2590
+ if (view.with) for (const pair of view.with) {
2591
+ const [key, value] = pair.split("=");
2592
+ switch (key) {
2593
+ case "check_option":
2594
+ if (value === "LOCAL" || value === "CASCADED") options.checkOption = value;
2595
+ break;
2596
+ case "security_barrier":
2597
+ options.securityBarrier = value === "true";
2598
+ break;
2599
+ case "security_invoker":
2600
+ options.securityInvoker = value === "true";
2601
+ break;
2602
+ }
2603
+ }
2604
+ return options;
2605
+ };
2606
+ const isViewOptionsEqual = (from, to) => {
2607
+ return from.recursive === to.recursive && from.checkOption === to.checkOption && (from.securityBarrier ?? false) === (to.securityBarrier ?? false) && (from.securityInvoker ?? false) === (to.securityInvoker ?? false) && isStringArrayEqual$1(from.columns, to.columns);
2608
+ };
2609
+ const isStringArrayEqual$1 = (a, b) => {
2610
+ if ((a?.length ?? 0) !== (b?.length ?? 0)) return false;
2611
+ if (!a?.length && !b?.length) return true;
2612
+ return !!a?.every((item, i) => item === b?.[i]);
2613
+ };
2614
+ const makeIgnoredViews$1 = (generatorIgnore, currentSchema) => {
2615
+ const views = generatorIgnore?.views;
2616
+ if (!views) return [];
2617
+ return views.map((name) => {
2618
+ if (typeof name !== "string") return { name };
2619
+ const parts = name.split(".");
2620
+ return parts.length === 2 ? {
2621
+ schema: parts[0],
2622
+ name: parts[1]
2623
+ } : {
2624
+ schema: currentSchema,
2625
+ name
2626
+ };
2627
+ });
2628
+ };
2629
+ const isIgnoredView$1 = (ignoredViews, generatorIgnore, currentSchema, codeView) => {
2630
+ const schemaName = codeView.q.schema ?? currentSchema;
2631
+ return generatorIgnore?.schemas?.includes(schemaName) === true || ignoredViews.some((ignore) => {
2632
+ return matchesIgnoredView$1(ignore, {
2633
+ schemaName,
2634
+ name: codeView.name
2635
+ }, currentSchema);
2636
+ });
2637
+ };
2638
+ const matchesIgnoredView$1 = (ignore, view, currentSchema) => {
2639
+ return typeof ignore.name === "string" ? ignore.schema === view.schemaName && ignore.name === view.name : ignore.name.test(normalizedViewName$1(view, currentSchema));
2640
+ };
2641
+ const normalizedViewName$1 = (view, currentSchema) => {
2642
+ return view.schemaName === currentSchema ? view.name : `${view.schemaName}.${view.name}`;
2643
+ };
2644
+ const processMaterializedViews = async (ast, adapter, dbStructure, { codeItems: { views: allViews }, currentSchema, internal: { generatorIgnore } }) => {
2645
+ const views = allViews.filter((view) => view.materialized);
2646
+ const createViews = [];
2647
+ const changeViews = [];
2648
+ const dropViews = [];
2649
+ const ignoredViews = makeIgnoredViews(generatorIgnore, currentSchema);
2650
+ for (const codeView of views) {
2651
+ if (isIgnoredView(ignoredViews, generatorIgnore, currentSchema, codeView)) continue;
2652
+ const schemaName = codeView.q.schema ?? currentSchema;
2653
+ const dbView = dbStructure.materializedViews?.find((view) => view.schemaName === schemaName && view.name === codeView.name);
2654
+ if (dbView) changeViews.push({
2655
+ codeView,
2656
+ dbView
2657
+ });
2658
+ else createViews.push(codeView);
2659
+ }
2660
+ for (const dbView of dbStructure.materializedViews ?? []) {
2661
+ if (generatorIgnore?.schemas?.includes(dbView.schemaName) || ignoredViews.some((ignore) => matchesIgnoredView(ignore, dbView, currentSchema))) continue;
2662
+ if (!views.find((view) => view.name === dbView.name && (view.q.schema ?? currentSchema) === dbView.schemaName)) dropViews.push(dbView);
2663
+ }
2664
+ for (const codeView of createViews) ast.push(codeViewToAst(codeView, currentSchema, "create"));
2665
+ await applyChangeViews(ast, adapter, changeViews, currentSchema);
2666
+ for (const dbView of dropViews) ast.push(dbViewToAst(dbView, currentSchema, "drop"));
2667
+ };
2668
+ const applyChangeViews = async (ast, adapter, changeViews, currentSchema) => {
2669
+ const compare = [];
2670
+ for (const { codeView, dbView } of changeViews) {
2671
+ const from = dbViewToAst(dbView, currentSchema, "drop");
2672
+ const to = codeViewToAst(codeView, currentSchema, "create");
2673
+ if (!isMaterializedViewOptionsEqual(from.options, to.options)) {
2674
+ pushRecreateView(ast, from, to);
2675
+ continue;
2676
+ }
2677
+ const codeSql = typeof to.sql === "string" ? to.sql : to.sql.toSQL({ values: [] });
2678
+ compare.push({
2679
+ inDb: dbView.sql,
2680
+ inCode: codeSql,
2681
+ ast: to,
2682
+ onNotEqual() {
2683
+ pushRecreateView(ast, from, to);
2684
+ }
2685
+ });
2686
+ }
2687
+ await compareViewsExpressions(adapter, compare);
2688
+ };
2689
+ const pushRecreateView = (ast, from, to) => {
2690
+ ast.push(from, to);
2691
+ };
2692
+ const codeViewToAst = (view, currentSchema, action) => {
2693
+ const schema = view.q.schema ?? currentSchema;
2694
+ const sql = typeof view.viewData.sql === "string" ? raw({ raw: view.viewData.sql }) : view.viewData.sql ?? raw({ raw: "" });
2695
+ return {
2696
+ type: "materializedView",
2697
+ action,
2698
+ schema: schema === currentSchema ? void 0 : schema,
2699
+ name: view.name,
2700
+ shape: view.shape,
2701
+ sql,
2702
+ options: {
2703
+ columns: Object.keys(view.shape),
2704
+ withData: view.viewData.withData
2705
+ },
2706
+ deps: []
2707
+ };
2708
+ };
2709
+ const dbViewToAst = (view, currentSchema, action) => {
2710
+ return {
2711
+ type: "materializedView",
2712
+ action,
2713
+ schema: view.schemaName === currentSchema ? void 0 : view.schemaName,
2714
+ name: view.name,
2715
+ shape: {},
2716
+ sql: raw({ raw: view.sql }),
2717
+ options: dbMaterializedViewOptionsToAst(view),
2718
+ deps: view.deps
2719
+ };
2720
+ };
2721
+ const dbMaterializedViewOptionsToAst = (view) => {
2722
+ const options = { columns: view.columns.map((column) => column.name) };
2723
+ if (!view.isPopulated) options.withData = false;
2724
+ return options;
2725
+ };
2726
+ const isMaterializedViewOptionsEqual = (from, to) => {
2727
+ return (from.withData ?? true) === (to.withData ?? true) && isStringArrayEqual(from.columns, to.columns);
2728
+ };
2729
+ const isStringArrayEqual = (a, b) => {
2730
+ if ((a?.length ?? 0) !== (b?.length ?? 0)) return false;
2731
+ if (!a?.length && !b?.length) return true;
2732
+ return !!a?.every((item, i) => item === b?.[i]);
2733
+ };
2734
+ const makeIgnoredViews = (generatorIgnore, currentSchema) => {
2735
+ const views = generatorIgnore?.views;
2736
+ if (!views) return [];
2737
+ return views.map((name) => {
2738
+ if (typeof name !== "string") return { name };
2739
+ const parts = name.split(".");
2740
+ return parts.length === 2 ? {
2741
+ schema: parts[0],
2742
+ name: parts[1]
2743
+ } : {
2744
+ schema: currentSchema,
2745
+ name
2746
+ };
2747
+ });
2748
+ };
2749
+ const isIgnoredView = (ignoredViews, generatorIgnore, currentSchema, codeView) => {
2750
+ const schemaName = codeView.q.schema ?? currentSchema;
2751
+ return generatorIgnore?.schemas?.includes(schemaName) === true || ignoredViews.some((ignore) => {
2752
+ return matchesIgnoredView(ignore, {
2753
+ schemaName,
2754
+ name: codeView.name
2755
+ }, currentSchema);
2756
+ });
2757
+ };
2758
+ const matchesIgnoredView = (ignore, view, currentSchema) => {
2759
+ return typeof ignore.name === "string" ? ignore.schema === view.schemaName && ignore.name === view.name : ignore.name.test(normalizedViewName(view, currentSchema));
2760
+ };
2761
+ const normalizedViewName = (view, currentSchema) => {
2762
+ return view.schemaName === currentSchema ? view.name : `${view.schemaName}.${view.name}`;
2763
+ };
2458
2764
  /**
2459
2765
  * This is needed to compare SQLs of table expressions.
2460
2766
  * Need to exclude table columns of pending types, such as enums or domains,
@@ -2481,6 +2787,8 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
2481
2787
  await processDomains(ast, adapter, domainsMap, dbStructure, params, pendingDbTypes);
2482
2788
  await processEnums(ast, dbStructure, params, pendingDbTypes);
2483
2789
  await processTables(ast, domainsMap, adapter, dbStructure, config, params, pendingDbTypes);
2790
+ await processViews(ast, adapter, dbStructure, params);
2791
+ await processMaterializedViews(ast, adapter, dbStructure, params);
2484
2792
  return astToMigration(currentSchema, config, ast);
2485
2793
  };
2486
2794
  const rollbackErr = /* @__PURE__ */ new Error("Rollback");
@@ -2656,7 +2964,12 @@ const report = (ast, config, currentSchema) => {
2656
2964
  case "domain":
2657
2965
  code.push(`${a.action === "create" ? green("+ create domain") : red("- drop domain")} ${dbItemName(a, currentSchema)}`);
2658
2966
  break;
2967
+ case "materializedView":
2968
+ code.push(`${a.action === "create" ? green("+ create materialized view") : red("- drop materialized view")} ${dbItemName(a, currentSchema)}`);
2969
+ break;
2659
2970
  case "view":
2971
+ code.push(`${a.action === "create" ? green("+ create view") : red("- drop view")} ${dbItemName(a, currentSchema)}`);
2972
+ break;
2660
2973
  case "collation":
2661
2974
  case "constraint": break;
2662
2975
  case "renameTableItem":
@@ -2814,7 +3127,8 @@ const generate = async (adapters, config, args, afterPull) => {
2814
3127
  const { columnTypes, internal } = db.$qb;
2815
3128
  const structureParams = {
2816
3129
  loadDefaultPrivileges: internal.roles?.some((role) => role.defaultPrivileges !== void 0) ?? false,
2817
- loadGrants: !!internal.grants || hasCodeTablesWithGrants(db)
3130
+ loadGrants: !!internal.grants || hasCodeItemsWithGrants(db),
3131
+ loadViews: hasCodeViews(db)
2818
3132
  };
2819
3133
  const rolesDbStructureParam = internal.roles ? internal.managedRolesSql ? { whereSql: internal.managedRolesSql } : emptyObject : void 0;
2820
3134
  const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, structureParams, afterPull);
@@ -2879,6 +3193,7 @@ const migrateAndPullStructures = async (adapters, config, db, roles, structurePa
2879
3193
  schemas: [],
2880
3194
  tables: [],
2881
3195
  views: [],
3196
+ materializedViews: [],
2882
3197
  indexes: [],
2883
3198
  excludes: [],
2884
3199
  constraints: [],
@@ -2893,7 +3208,8 @@ const migrateAndPullStructures = async (adapters, config, db, roles, structurePa
2893
3208
  rls: hasCodeTablesWithRls(db),
2894
3209
  roles,
2895
3210
  loadDefaultPrivileges: structureParams?.loadDefaultPrivileges,
2896
- loadGrants: structureParams?.loadGrants
3211
+ loadGrants: structureParams?.loadGrants,
3212
+ loadViews: structureParams?.loadViews
2897
3213
  })));
2898
3214
  const dbStructure = dbStructures[0];
2899
3215
  for (let i = 1; i < dbStructures.length; i++) compareDbStructures(dbStructure, dbStructures[i], i);
@@ -2906,11 +3222,17 @@ const hasCodeTablesWithRls = (db) => {
2906
3222
  }
2907
3223
  return false;
2908
3224
  };
2909
- const hasCodeTablesWithGrants = (db) => {
3225
+ const hasCodeViews = (db) => {
3226
+ return !!Object.keys(db.$views ?? {}).length;
3227
+ };
3228
+ const hasCodeItemsWithGrants = (db) => {
2910
3229
  for (const key in db) {
2911
3230
  if (key[0] === "$") continue;
2912
3231
  if (db[key].internal.tableGrants?.length) return true;
2913
3232
  }
3233
+ const views = db.$views;
3234
+ if (!views) return false;
3235
+ for (const key in views) if (views[key].internal.tableGrants?.length) return true;
2914
3236
  return false;
2915
3237
  };
2916
3238
  const getEffectiveGrants = (grants, codeItems) => {
@@ -2928,6 +3250,19 @@ const getEffectiveGrants = (grants, codeItems) => {
2928
3250
  effectiveGrants.push(internalGrant);
2929
3251
  }
2930
3252
  }
3253
+ for (const view of codeItems.views) {
3254
+ const viewGrants = view.internal.tableGrants;
3255
+ if (!viewGrants?.length) continue;
3256
+ const viewTarget = view.q.schema ? `${view.q.schema}.${view.name}` : view.name;
3257
+ for (const grant of viewGrants) {
3258
+ const internalGrant = {
3259
+ ...grant,
3260
+ to: toArray(grant.to),
3261
+ tables: [viewTarget]
3262
+ };
3263
+ effectiveGrants.push(internalGrant);
3264
+ }
3265
+ }
2931
3266
  return effectiveGrants.length ? effectiveGrants : void 0;
2932
3267
  };
2933
3268
  const compareDbStructures = (a, b, i, path) => {
@@ -2946,6 +3281,7 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2946
3281
  schemas: /* @__PURE__ */ new Set(void 0),
2947
3282
  enums: /* @__PURE__ */ new Map(),
2948
3283
  tables: [],
3284
+ views: [],
2949
3285
  domains: []
2950
3286
  };
2951
3287
  codeItems.schemas.add(currentSchema);
@@ -2975,21 +3311,22 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2975
3311
  const column = table.shape[key];
2976
3312
  if (column && "joinTable" in column) processHasAndBelongsToManyColumn(column, habtmTables, codeItems);
2977
3313
  }
2978
- for (const key in table.shape) {
2979
- const column = table.shape[key];
2980
- if (column.data.computed) delete table.shape[key];
2981
- else if (column instanceof DomainColumn) {
2982
- const [schemaName = currentSchema, name] = getSchemaAndTableFromName(currentSchema, column.dataType);
2983
- domains.set(column.dataType, {
2984
- schemaName,
2985
- name,
2986
- column: column.data.as ?? UnknownColumn.instance
2987
- });
2988
- } else {
2989
- const en = column.dataType === "enum" ? column : column instanceof ArrayColumn && column.data.item.dataType === "enum" ? column.data.item : void 0;
2990
- if (en) processEnumColumn(en, currentSchema, codeItems);
2991
- }
2992
- }
3314
+ processCodeItemShape(table.shape, currentSchema, codeItems, domains);
3315
+ }
3316
+ const views = db.$views;
3317
+ if (views) for (const key in views) {
3318
+ const view = views[key];
3319
+ const schema = getQuerySchema(view);
3320
+ if (schema) codeItems.schemas.add(schema);
3321
+ codeItems.views.push({
3322
+ name: view.table,
3323
+ shape: view.shape,
3324
+ internal: view.internal,
3325
+ q: { schema },
3326
+ materialized: view.internal.materialized,
3327
+ viewData: view.internal.viewData ?? {}
3328
+ });
3329
+ processCodeItemShape(view.shape, currentSchema, codeItems, domains);
2993
3330
  }
2994
3331
  if (internal.extensions) for (const extension of internal.extensions) {
2995
3332
  const [schema] = getSchemaAndTableFromName(currentSchema, extension.name);
@@ -3016,6 +3353,23 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
3016
3353
  if (internal.grants) for (const grant of internal.grants) addGrantSchemas(codeItems.schemas, currentSchema, grant);
3017
3354
  return codeItems;
3018
3355
  };
3356
+ const processCodeItemShape = (shape, currentSchema, codeItems, domains) => {
3357
+ for (const key in shape) {
3358
+ const column = shape[key];
3359
+ if (column.data.computed) delete shape[key];
3360
+ else if (column instanceof DomainColumn) {
3361
+ const [schemaName = currentSchema, name] = getSchemaAndTableFromName(currentSchema, column.dataType);
3362
+ domains.set(column.dataType, {
3363
+ schemaName,
3364
+ name,
3365
+ column: column.data.as ?? UnknownColumn.instance
3366
+ });
3367
+ } else {
3368
+ const en = column.dataType === "enum" ? column : column instanceof ArrayColumn && column.data.item.dataType === "enum" ? column.data.item : void 0;
3369
+ if (en) processEnumColumn(en, currentSchema, codeItems);
3370
+ }
3371
+ }
3372
+ };
3019
3373
  const addGrantSchemas = (schemas, currentSchema, grant) => {
3020
3374
  for (const schema of [
3021
3375
  ...grant.schemas ?? [],