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.
@@ -28,14 +28,14 @@ let node_fs_promises = require("node:fs/promises");
28
28
  node_fs_promises = __toESM(node_fs_promises);
29
29
  let typescript = require("typescript");
30
30
  typescript = __toESM(typescript);
31
- const compareSqlExpressions = async (tableExpressions, adapter) => {
32
- if (!tableExpressions.length) return;
31
+ const compareSqlExpressions = async (expressions, adapter) => {
32
+ if (!expressions.length) return;
33
33
  let id = 1;
34
- for (const { source, compare, handle } of tableExpressions) {
34
+ for (const { source, compare, handle } of expressions) {
35
35
  const viewName = `orchidTmpView${id++}`;
36
36
  const values = [];
37
37
  const combinedQueries = [
38
- `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})`,
38
+ `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}` : ""})`,
39
39
  `SELECT pg_get_viewdef('${viewName}') v`,
40
40
  `DROP VIEW ${viewName}`
41
41
  ].join("; ");
@@ -50,6 +50,50 @@ const compareSqlExpressions = async (tableExpressions, adapter) => {
50
50
  handle(compareSqlExpressionResult(result.rows[0].v, compare[0].inCode));
51
51
  }
52
52
  };
53
+ const compareViewsExpressions = async (adapter, compare) => {
54
+ if (!compare.length) return;
55
+ const queries = [];
56
+ let id = 1;
57
+ compare.forEach(({ inCode, ast }, i) => {
58
+ const viewName = `orchidTmpView${id++}ForViews`;
59
+ queries.push({ batch: [
60
+ `SAVEPOINT "${viewName}S"`,
61
+ `CREATE TEMPORARY${"recursive" in ast.options && ast.options.recursive ? " RECURSIVE" : ""} VIEW "${viewName}" (${ast.options.columns?.map((column) => `"${column}"`).join(", ")}) AS (${inCode})`,
62
+ `SELECT ${i} i, '${viewName}' v, pg_get_viewdef('"${viewName}"') sql`,
63
+ `DROP VIEW "${viewName}"`,
64
+ `RELEASE SAVEPOINT "${viewName}S"`
65
+ ] });
66
+ });
67
+ let results;
68
+ try {
69
+ const sql = queries.flatMap((q) => q.batch).join(";");
70
+ const query = () => adapter.query(sql, []);
71
+ results = await (adapter.isInTransaction() ? adapter.savepoint("orchidOrmGeneratorViews", query) : query());
72
+ } catch {
73
+ results = (await Promise.all(queries.map(async ({ batch: queries }, i) => {
74
+ const sql = queries.join(";");
75
+ const query = () => adapter.query(sql, []);
76
+ return await (adapter.isInTransaction() ? adapter.savepoint(`orchidOrmGeneratorViews${i}`, query) : query()).catch((err) => {
77
+ if (typeof err === "object") {
78
+ const code = (0, pqb_internal.getDriverErrorCode)(err);
79
+ if (code === "42703" || code === "42704" || code === "42P01") return [];
80
+ }
81
+ throw err;
82
+ });
83
+ }))).flat();
84
+ }
85
+ const handled = /* @__PURE__ */ new Set();
86
+ for (const result of results) for (const row of result.rows) if ("sql" in row) {
87
+ const { i, v } = row;
88
+ const cmp = compare[i];
89
+ const hasQuote = !!cmp.inDb.match(/\w*WITH RECURSIVE "/);
90
+ if (row.sql.replaceAll(hasQuote ? v : `"${v}"`, cmp.ast.name) !== cmp.inDb) cmp.onNotEqual();
91
+ handled.add(i);
92
+ }
93
+ compare.forEach((cmp, i) => {
94
+ if (!handled.has(i)) cmp.onNotEqual();
95
+ });
96
+ };
53
97
  const compareSqlExpressionResult = (resultSql, inCode) => {
54
98
  let pos = 7;
55
99
  const rgx = /\s+AS\s+"\*(inDb-\d+|inCode-\d+-\d+)\*",?/g;
@@ -107,8 +151,9 @@ const processSchemas = async (ast, dbStructure, { codeItems: { schemas }, verify
107
151
  if (i) {
108
152
  const from = dropSchemas[i - 1];
109
153
  dropSchemas.splice(i - 1, 1);
154
+ const views = dbStructure.views || [];
110
155
  renameSchemaInStructures(dbStructure.tables, from, schema);
111
- renameSchemaInStructures(dbStructure.views, from, schema);
156
+ renameSchemaInStructures(views, from, schema);
112
157
  renameSchemaInStructures(dbStructure.indexes, from, schema);
113
158
  renameSchemaInStructures(dbStructure.excludes, from, schema);
114
159
  renameSchemaInStructures(dbStructure.constraints, from, schema);
@@ -435,7 +480,7 @@ const renameColumn = (columns, from, to) => {
435
480
  const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems: { domains }, structureToAstCtx, currentSchema, internal: { generatorIgnore } }, pendingDbTypes) => {
436
481
  const codeDomains = [];
437
482
  if (domains) for (const { schemaName, name, column } of domains) codeDomains.push(makeComparableDomain(currentSchema, schemaName, name, column));
438
- const tableExpressions = [];
483
+ const expressions = [];
439
484
  const holdCodeDomains = /* @__PURE__ */ new Set();
440
485
  for (const domain of dbStructure.domains) {
441
486
  if (generatorIgnore?.schemas?.includes(domain.schemaName) || generatorIgnore?.domains?.includes(domain.name)) continue;
@@ -461,7 +506,7 @@ const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems
461
506
  pushCompareDefault(compare, domain, found);
462
507
  pushCompareChecks(compare, domain, found);
463
508
  const source = `(VALUES (NULL::${getColumnDbType(dbColumn, currentSchema)})) t(value)`;
464
- tableExpressions.push({
509
+ expressions.push({
465
510
  compare,
466
511
  source,
467
512
  handle(i) {
@@ -492,8 +537,8 @@ const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems
492
537
  ast.push(createAst(codeDomain));
493
538
  pendingDbTypes.add(codeDomain.schemaName, codeDomain.name);
494
539
  }
495
- if (tableExpressions.length) {
496
- await compareSqlExpressions(tableExpressions, adapter);
540
+ if (expressions.length) {
541
+ await compareSqlExpressions(expressions, adapter);
497
542
  if (holdCodeDomains.size) for (const codeDomain of holdCodeDomains.keys()) {
498
543
  ast.push(createAst(codeDomain));
499
544
  pendingDbTypes.add(codeDomain.schemaName, codeDomain.name);
@@ -1691,13 +1736,13 @@ const processTables = async (ast, domainsMap, adapter, dbStructure, config, { st
1691
1736
  values: [],
1692
1737
  expressions: []
1693
1738
  };
1694
- const tableExpressions = [];
1739
+ const expressions = [];
1695
1740
  const { changeTables, changeTableSchemas, dropTables, tableShapes } = collectChangeAndDropTables(adapter, config, tables, dbStructure, currentSchema, createTables, generatorIgnore);
1696
1741
  applyChangeTableSchemas(changeTableSchemas, currentSchema, ast);
1697
1742
  await applyCreateOrRenameTables(dbStructure, createTables, dropTables, changeTables, tableShapes, currentSchema, ast, verifying);
1698
- await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes);
1743
+ await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, expressions, verifying, pendingDbTypes);
1699
1744
  processForeignKeys(config, ast, changeTables, currentSchema, tableShapes);
1700
- await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(tableExpressions, adapter)]);
1745
+ await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(expressions, adapter)]);
1701
1746
  await processTableRls(adapter, ast, dbStructure, tables, currentSchema, generatorIgnore);
1702
1747
  for (const dbTable of dropTables) ast.push((0, rake_db.tableToAst)(structureToAstCtx, dbStructure, dbTable, "drop", domainsMap));
1703
1748
  };
@@ -1761,7 +1806,7 @@ const applyChangeTableSchemas = (changeTableSchemas, currentSchema, ast) => {
1761
1806
  });
1762
1807
  }
1763
1808
  };
1764
- const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes) => {
1809
+ const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, expressions, verifying, pendingDbTypes) => {
1765
1810
  const compareExpressions = [];
1766
1811
  const typeCastsCache = {};
1767
1812
  for (const changeTableData of changeTables) {
@@ -1783,7 +1828,7 @@ const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStr
1783
1828
  }
1784
1829
  const tableName = codeTable.table;
1785
1830
  const source = `(VALUES (${types.map((x) => `NULL::${x}`).join(", ")})) "${tableName}"(${names.map((x) => `"${x}"`).join(", ")})`;
1786
- tableExpressions.push(...compareExpressions.map((x) => ({
1831
+ expressions.push(...compareExpressions.map((x) => ({
1787
1832
  ...x,
1788
1833
  source
1789
1834
  })));
@@ -2405,13 +2450,16 @@ const normalizeRoleName = (name) => {
2405
2450
  };
2406
2451
  const getSchemaWideTargets = (dbStructure, targetKey, schemas) => {
2407
2452
  const selectedSchemas = new Set(schemas);
2408
- if (targetKey === "tables") return [...dbStructure.tables.map((table) => ({
2409
- target: `${table.schemaName}.${table.name}`,
2410
- outputTarget: `${table.schemaName}.${table.name}`
2411
- })), ...dbStructure.views.map((view) => ({
2412
- target: `${view.schemaName}.${view.name}`,
2413
- outputTarget: `${view.schemaName}.${view.name}`
2414
- }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2453
+ if (targetKey === "tables") {
2454
+ const views = dbStructure.views || [];
2455
+ return [...dbStructure.tables.map((table) => ({
2456
+ target: `${table.schemaName}.${table.name}`,
2457
+ outputTarget: `${table.schemaName}.${table.name}`
2458
+ })), ...views.map((view) => ({
2459
+ target: `${view.schemaName}.${view.name}`,
2460
+ outputTarget: `${view.schemaName}.${view.name}`
2461
+ }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2462
+ }
2415
2463
  if (targetKey === "sequences" || targetKey === "routines") return getSchemaWideGrantTargets(dbStructure, targetKey, selectedSchemas);
2416
2464
  return [];
2417
2465
  };
@@ -2478,6 +2526,264 @@ const addGrantAst = (ast, action, grant, privileges, privilegeKey) => {
2478
2526
  grantedBy: grant.grantedBy
2479
2527
  });
2480
2528
  };
2529
+ const processViews = async (ast, adapter, dbStructure, { codeItems: { views: allViews }, currentSchema, internal: { generatorIgnore } }) => {
2530
+ const createViews = [];
2531
+ const changeViews = [];
2532
+ const dropViews = [];
2533
+ const ignoredViews = makeIgnoredViews$1(generatorIgnore, currentSchema);
2534
+ const views = allViews.filter((view) => !view.materialized);
2535
+ for (const codeView of views) {
2536
+ if (isIgnoredView$1(ignoredViews, generatorIgnore, currentSchema, codeView)) continue;
2537
+ const schemaName = codeView.q.schema ?? currentSchema;
2538
+ const dbView = dbStructure.views?.find((view) => view.schemaName === schemaName && view.name === codeView.name);
2539
+ if (dbView) changeViews.push({
2540
+ codeView,
2541
+ dbView
2542
+ });
2543
+ else createViews.push(codeView);
2544
+ }
2545
+ for (const dbView of dbStructure.views ?? []) {
2546
+ if (generatorIgnore?.schemas?.includes(dbView.schemaName) || ignoredViews.some((ignore) => matchesIgnoredView$1(ignore, dbView, currentSchema))) continue;
2547
+ if (!views.find((view) => view.name === dbView.name && (view.q.schema ?? currentSchema) === dbView.schemaName)) dropViews.push(dbView);
2548
+ }
2549
+ for (const codeView of createViews) ast.push(codeViewToAst$1(codeView, currentSchema, "create"));
2550
+ await applyChangeViews$1(ast, adapter, changeViews, currentSchema);
2551
+ for (const dbView of dropViews) ast.push(dbViewToAst$1(dbView, currentSchema, "drop"));
2552
+ };
2553
+ const applyChangeViews$1 = async (ast, adapter, changeViews, currentSchema) => {
2554
+ const compare = [];
2555
+ for (const { codeView, dbView } of changeViews) {
2556
+ const from = dbViewToAst$1(dbView, currentSchema, "drop");
2557
+ const to = codeViewToAst$1(codeView, currentSchema, "create");
2558
+ if (!isViewOptionsEqual(from.options, to.options)) {
2559
+ pushRecreateView$1(ast, from, to);
2560
+ continue;
2561
+ }
2562
+ const codeSql = typeof to.sql === "string" ? to.sql : to.sql.toSQL({ values: [] });
2563
+ compare.push({
2564
+ inDb: dbView.sql,
2565
+ inCode: codeSql,
2566
+ ast: to,
2567
+ onNotEqual() {
2568
+ pushRecreateView$1(ast, from, to);
2569
+ }
2570
+ });
2571
+ }
2572
+ await compareViewsExpressions(adapter, compare);
2573
+ };
2574
+ const pushRecreateView$1 = (ast, from, to) => {
2575
+ ast.push(from, to);
2576
+ };
2577
+ const codeViewToAst$1 = (view, currentSchema, action) => {
2578
+ const schema = view.q.schema ?? currentSchema;
2579
+ const sql = typeof view.viewData.sql === "string" ? (0, pqb_internal.raw)({ raw: view.viewData.sql }) : view.viewData.sql ?? (0, pqb_internal.raw)({ raw: "" });
2580
+ return {
2581
+ type: "view",
2582
+ action,
2583
+ schema: schema === currentSchema ? void 0 : schema,
2584
+ name: view.name,
2585
+ shape: view.shape,
2586
+ sql,
2587
+ options: {
2588
+ recursive: view.viewData.recursive,
2589
+ columns: Object.keys(view.shape),
2590
+ checkOption: view.viewData.checkOption,
2591
+ securityBarrier: view.viewData.securityBarrier,
2592
+ securityInvoker: view.viewData.securityInvoker ?? true
2593
+ },
2594
+ deps: []
2595
+ };
2596
+ };
2597
+ const dbViewToAst$1 = (view, currentSchema, action) => {
2598
+ return {
2599
+ type: "view",
2600
+ action,
2601
+ schema: view.schemaName === currentSchema ? void 0 : view.schemaName,
2602
+ name: view.name,
2603
+ shape: {},
2604
+ sql: (0, pqb_internal.raw)({ raw: view.sql }),
2605
+ options: dbViewOptionsToAst(view),
2606
+ deps: view.deps
2607
+ };
2608
+ };
2609
+ const dbViewOptionsToAst = (view) => {
2610
+ const options = {};
2611
+ options.columns = view.columns.map((column) => column.name);
2612
+ if (view.isRecursive) options.recursive = true;
2613
+ if (view.with) for (const pair of view.with) {
2614
+ const [key, value] = pair.split("=");
2615
+ switch (key) {
2616
+ case "check_option":
2617
+ if (value === "LOCAL" || value === "CASCADED") options.checkOption = value;
2618
+ break;
2619
+ case "security_barrier":
2620
+ options.securityBarrier = value === "true";
2621
+ break;
2622
+ case "security_invoker":
2623
+ options.securityInvoker = value === "true";
2624
+ break;
2625
+ }
2626
+ }
2627
+ return options;
2628
+ };
2629
+ const isViewOptionsEqual = (from, to) => {
2630
+ 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);
2631
+ };
2632
+ const isStringArrayEqual$1 = (a, b) => {
2633
+ if ((a?.length ?? 0) !== (b?.length ?? 0)) return false;
2634
+ if (!a?.length && !b?.length) return true;
2635
+ return !!a?.every((item, i) => item === b?.[i]);
2636
+ };
2637
+ const makeIgnoredViews$1 = (generatorIgnore, currentSchema) => {
2638
+ const views = generatorIgnore?.views;
2639
+ if (!views) return [];
2640
+ return views.map((name) => {
2641
+ if (typeof name !== "string") return { name };
2642
+ const parts = name.split(".");
2643
+ return parts.length === 2 ? {
2644
+ schema: parts[0],
2645
+ name: parts[1]
2646
+ } : {
2647
+ schema: currentSchema,
2648
+ name
2649
+ };
2650
+ });
2651
+ };
2652
+ const isIgnoredView$1 = (ignoredViews, generatorIgnore, currentSchema, codeView) => {
2653
+ const schemaName = codeView.q.schema ?? currentSchema;
2654
+ return generatorIgnore?.schemas?.includes(schemaName) === true || ignoredViews.some((ignore) => {
2655
+ return matchesIgnoredView$1(ignore, {
2656
+ schemaName,
2657
+ name: codeView.name
2658
+ }, currentSchema);
2659
+ });
2660
+ };
2661
+ const matchesIgnoredView$1 = (ignore, view, currentSchema) => {
2662
+ return typeof ignore.name === "string" ? ignore.schema === view.schemaName && ignore.name === view.name : ignore.name.test(normalizedViewName$1(view, currentSchema));
2663
+ };
2664
+ const normalizedViewName$1 = (view, currentSchema) => {
2665
+ return view.schemaName === currentSchema ? view.name : `${view.schemaName}.${view.name}`;
2666
+ };
2667
+ const processMaterializedViews = async (ast, adapter, dbStructure, { codeItems: { views: allViews }, currentSchema, internal: { generatorIgnore } }) => {
2668
+ const views = allViews.filter((view) => view.materialized);
2669
+ const createViews = [];
2670
+ const changeViews = [];
2671
+ const dropViews = [];
2672
+ const ignoredViews = makeIgnoredViews(generatorIgnore, currentSchema);
2673
+ for (const codeView of views) {
2674
+ if (isIgnoredView(ignoredViews, generatorIgnore, currentSchema, codeView)) continue;
2675
+ const schemaName = codeView.q.schema ?? currentSchema;
2676
+ const dbView = dbStructure.materializedViews?.find((view) => view.schemaName === schemaName && view.name === codeView.name);
2677
+ if (dbView) changeViews.push({
2678
+ codeView,
2679
+ dbView
2680
+ });
2681
+ else createViews.push(codeView);
2682
+ }
2683
+ for (const dbView of dbStructure.materializedViews ?? []) {
2684
+ if (generatorIgnore?.schemas?.includes(dbView.schemaName) || ignoredViews.some((ignore) => matchesIgnoredView(ignore, dbView, currentSchema))) continue;
2685
+ if (!views.find((view) => view.name === dbView.name && (view.q.schema ?? currentSchema) === dbView.schemaName)) dropViews.push(dbView);
2686
+ }
2687
+ for (const codeView of createViews) ast.push(codeViewToAst(codeView, currentSchema, "create"));
2688
+ await applyChangeViews(ast, adapter, changeViews, currentSchema);
2689
+ for (const dbView of dropViews) ast.push(dbViewToAst(dbView, currentSchema, "drop"));
2690
+ };
2691
+ const applyChangeViews = async (ast, adapter, changeViews, currentSchema) => {
2692
+ const compare = [];
2693
+ for (const { codeView, dbView } of changeViews) {
2694
+ const from = dbViewToAst(dbView, currentSchema, "drop");
2695
+ const to = codeViewToAst(codeView, currentSchema, "create");
2696
+ if (!isMaterializedViewOptionsEqual(from.options, to.options)) {
2697
+ pushRecreateView(ast, from, to);
2698
+ continue;
2699
+ }
2700
+ const codeSql = typeof to.sql === "string" ? to.sql : to.sql.toSQL({ values: [] });
2701
+ compare.push({
2702
+ inDb: dbView.sql,
2703
+ inCode: codeSql,
2704
+ ast: to,
2705
+ onNotEqual() {
2706
+ pushRecreateView(ast, from, to);
2707
+ }
2708
+ });
2709
+ }
2710
+ await compareViewsExpressions(adapter, compare);
2711
+ };
2712
+ const pushRecreateView = (ast, from, to) => {
2713
+ ast.push(from, to);
2714
+ };
2715
+ const codeViewToAst = (view, currentSchema, action) => {
2716
+ const schema = view.q.schema ?? currentSchema;
2717
+ const sql = typeof view.viewData.sql === "string" ? (0, pqb_internal.raw)({ raw: view.viewData.sql }) : view.viewData.sql ?? (0, pqb_internal.raw)({ raw: "" });
2718
+ return {
2719
+ type: "materializedView",
2720
+ action,
2721
+ schema: schema === currentSchema ? void 0 : schema,
2722
+ name: view.name,
2723
+ shape: view.shape,
2724
+ sql,
2725
+ options: {
2726
+ columns: Object.keys(view.shape),
2727
+ withData: view.viewData.withData
2728
+ },
2729
+ deps: []
2730
+ };
2731
+ };
2732
+ const dbViewToAst = (view, currentSchema, action) => {
2733
+ return {
2734
+ type: "materializedView",
2735
+ action,
2736
+ schema: view.schemaName === currentSchema ? void 0 : view.schemaName,
2737
+ name: view.name,
2738
+ shape: {},
2739
+ sql: (0, pqb_internal.raw)({ raw: view.sql }),
2740
+ options: dbMaterializedViewOptionsToAst(view),
2741
+ deps: view.deps
2742
+ };
2743
+ };
2744
+ const dbMaterializedViewOptionsToAst = (view) => {
2745
+ const options = { columns: view.columns.map((column) => column.name) };
2746
+ if (!view.isPopulated) options.withData = false;
2747
+ return options;
2748
+ };
2749
+ const isMaterializedViewOptionsEqual = (from, to) => {
2750
+ return (from.withData ?? true) === (to.withData ?? true) && isStringArrayEqual(from.columns, to.columns);
2751
+ };
2752
+ const isStringArrayEqual = (a, b) => {
2753
+ if ((a?.length ?? 0) !== (b?.length ?? 0)) return false;
2754
+ if (!a?.length && !b?.length) return true;
2755
+ return !!a?.every((item, i) => item === b?.[i]);
2756
+ };
2757
+ const makeIgnoredViews = (generatorIgnore, currentSchema) => {
2758
+ const views = generatorIgnore?.views;
2759
+ if (!views) return [];
2760
+ return views.map((name) => {
2761
+ if (typeof name !== "string") return { name };
2762
+ const parts = name.split(".");
2763
+ return parts.length === 2 ? {
2764
+ schema: parts[0],
2765
+ name: parts[1]
2766
+ } : {
2767
+ schema: currentSchema,
2768
+ name
2769
+ };
2770
+ });
2771
+ };
2772
+ const isIgnoredView = (ignoredViews, generatorIgnore, currentSchema, codeView) => {
2773
+ const schemaName = codeView.q.schema ?? currentSchema;
2774
+ return generatorIgnore?.schemas?.includes(schemaName) === true || ignoredViews.some((ignore) => {
2775
+ return matchesIgnoredView(ignore, {
2776
+ schemaName,
2777
+ name: codeView.name
2778
+ }, currentSchema);
2779
+ });
2780
+ };
2781
+ const matchesIgnoredView = (ignore, view, currentSchema) => {
2782
+ return typeof ignore.name === "string" ? ignore.schema === view.schemaName && ignore.name === view.name : ignore.name.test(normalizedViewName(view, currentSchema));
2783
+ };
2784
+ const normalizedViewName = (view, currentSchema) => {
2785
+ return view.schemaName === currentSchema ? view.name : `${view.schemaName}.${view.name}`;
2786
+ };
2481
2787
  /**
2482
2788
  * This is needed to compare SQLs of table expressions.
2483
2789
  * Need to exclude table columns of pending types, such as enums or domains,
@@ -2504,6 +2810,8 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
2504
2810
  await processDomains(ast, adapter, domainsMap, dbStructure, params, pendingDbTypes);
2505
2811
  await processEnums(ast, dbStructure, params, pendingDbTypes);
2506
2812
  await processTables(ast, domainsMap, adapter, dbStructure, config, params, pendingDbTypes);
2813
+ await processViews(ast, adapter, dbStructure, params);
2814
+ await processMaterializedViews(ast, adapter, dbStructure, params);
2507
2815
  return (0, rake_db.astToMigration)(currentSchema, config, ast);
2508
2816
  };
2509
2817
  const rollbackErr = /* @__PURE__ */ new Error("Rollback");
@@ -2679,7 +2987,12 @@ const report = (ast, config, currentSchema) => {
2679
2987
  case "domain":
2680
2988
  code.push(`${a.action === "create" ? green("+ create domain") : red("- drop domain")} ${dbItemName(a, currentSchema)}`);
2681
2989
  break;
2990
+ case "materializedView":
2991
+ code.push(`${a.action === "create" ? green("+ create materialized view") : red("- drop materialized view")} ${dbItemName(a, currentSchema)}`);
2992
+ break;
2682
2993
  case "view":
2994
+ code.push(`${a.action === "create" ? green("+ create view") : red("- drop view")} ${dbItemName(a, currentSchema)}`);
2995
+ break;
2683
2996
  case "collation":
2684
2997
  case "constraint": break;
2685
2998
  case "renameTableItem":
@@ -2837,7 +3150,8 @@ const generate = async (adapters, config, args, afterPull) => {
2837
3150
  const { columnTypes, internal } = db.$qb;
2838
3151
  const structureParams = {
2839
3152
  loadDefaultPrivileges: internal.roles?.some((role) => role.defaultPrivileges !== void 0) ?? false,
2840
- loadGrants: !!internal.grants || hasCodeTablesWithGrants(db)
3153
+ loadGrants: !!internal.grants || hasCodeItemsWithGrants(db),
3154
+ loadViews: hasCodeViews(db)
2841
3155
  };
2842
3156
  const rolesDbStructureParam = internal.roles ? internal.managedRolesSql ? { whereSql: internal.managedRolesSql } : pqb_internal.emptyObject : void 0;
2843
3157
  const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, structureParams, afterPull);
@@ -2902,6 +3216,7 @@ const migrateAndPullStructures = async (adapters, config, db, roles, structurePa
2902
3216
  schemas: [],
2903
3217
  tables: [],
2904
3218
  views: [],
3219
+ materializedViews: [],
2905
3220
  indexes: [],
2906
3221
  excludes: [],
2907
3222
  constraints: [],
@@ -2916,7 +3231,8 @@ const migrateAndPullStructures = async (adapters, config, db, roles, structurePa
2916
3231
  rls: hasCodeTablesWithRls(db),
2917
3232
  roles,
2918
3233
  loadDefaultPrivileges: structureParams?.loadDefaultPrivileges,
2919
- loadGrants: structureParams?.loadGrants
3234
+ loadGrants: structureParams?.loadGrants,
3235
+ loadViews: structureParams?.loadViews
2920
3236
  })));
2921
3237
  const dbStructure = dbStructures[0];
2922
3238
  for (let i = 1; i < dbStructures.length; i++) compareDbStructures(dbStructure, dbStructures[i], i);
@@ -2929,11 +3245,17 @@ const hasCodeTablesWithRls = (db) => {
2929
3245
  }
2930
3246
  return false;
2931
3247
  };
2932
- const hasCodeTablesWithGrants = (db) => {
3248
+ const hasCodeViews = (db) => {
3249
+ return !!Object.keys(db.$views ?? {}).length;
3250
+ };
3251
+ const hasCodeItemsWithGrants = (db) => {
2933
3252
  for (const key in db) {
2934
3253
  if (key[0] === "$") continue;
2935
3254
  if (db[key].internal.tableGrants?.length) return true;
2936
3255
  }
3256
+ const views = db.$views;
3257
+ if (!views) return false;
3258
+ for (const key in views) if (views[key].internal.tableGrants?.length) return true;
2937
3259
  return false;
2938
3260
  };
2939
3261
  const getEffectiveGrants = (grants, codeItems) => {
@@ -2951,6 +3273,19 @@ const getEffectiveGrants = (grants, codeItems) => {
2951
3273
  effectiveGrants.push(internalGrant);
2952
3274
  }
2953
3275
  }
3276
+ for (const view of codeItems.views) {
3277
+ const viewGrants = view.internal.tableGrants;
3278
+ if (!viewGrants?.length) continue;
3279
+ const viewTarget = view.q.schema ? `${view.q.schema}.${view.name}` : view.name;
3280
+ for (const grant of viewGrants) {
3281
+ const internalGrant = {
3282
+ ...grant,
3283
+ to: (0, pqb_internal.toArray)(grant.to),
3284
+ tables: [viewTarget]
3285
+ };
3286
+ effectiveGrants.push(internalGrant);
3287
+ }
3288
+ }
2954
3289
  return effectiveGrants.length ? effectiveGrants : void 0;
2955
3290
  };
2956
3291
  const compareDbStructures = (a, b, i, path) => {
@@ -2969,6 +3304,7 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2969
3304
  schemas: /* @__PURE__ */ new Set(void 0),
2970
3305
  enums: /* @__PURE__ */ new Map(),
2971
3306
  tables: [],
3307
+ views: [],
2972
3308
  domains: []
2973
3309
  };
2974
3310
  codeItems.schemas.add(currentSchema);
@@ -2998,21 +3334,22 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2998
3334
  const column = table.shape[key];
2999
3335
  if (column && "joinTable" in column) processHasAndBelongsToManyColumn(column, habtmTables, codeItems);
3000
3336
  }
3001
- for (const key in table.shape) {
3002
- const column = table.shape[key];
3003
- if (column.data.computed) delete table.shape[key];
3004
- else if (column instanceof pqb_internal.DomainColumn) {
3005
- const [schemaName = currentSchema, name] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, column.dataType);
3006
- domains.set(column.dataType, {
3007
- schemaName,
3008
- name,
3009
- column: column.data.as ?? pqb_internal.UnknownColumn.instance
3010
- });
3011
- } else {
3012
- const en = column.dataType === "enum" ? column : column instanceof pqb_internal.ArrayColumn && column.data.item.dataType === "enum" ? column.data.item : void 0;
3013
- if (en) processEnumColumn(en, currentSchema, codeItems);
3014
- }
3015
- }
3337
+ processCodeItemShape(table.shape, currentSchema, codeItems, domains);
3338
+ }
3339
+ const views = db.$views;
3340
+ if (views) for (const key in views) {
3341
+ const view = views[key];
3342
+ const schema = (0, pqb_internal.getQuerySchema)(view);
3343
+ if (schema) codeItems.schemas.add(schema);
3344
+ codeItems.views.push({
3345
+ name: view.table,
3346
+ shape: view.shape,
3347
+ internal: view.internal,
3348
+ q: { schema },
3349
+ materialized: view.internal.materialized,
3350
+ viewData: view.internal.viewData ?? {}
3351
+ });
3352
+ processCodeItemShape(view.shape, currentSchema, codeItems, domains);
3016
3353
  }
3017
3354
  if (internal.extensions) for (const extension of internal.extensions) {
3018
3355
  const [schema] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, extension.name);
@@ -3039,6 +3376,23 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
3039
3376
  if (internal.grants) for (const grant of internal.grants) addGrantSchemas(codeItems.schemas, currentSchema, grant);
3040
3377
  return codeItems;
3041
3378
  };
3379
+ const processCodeItemShape = (shape, currentSchema, codeItems, domains) => {
3380
+ for (const key in shape) {
3381
+ const column = shape[key];
3382
+ if (column.data.computed) delete shape[key];
3383
+ else if (column instanceof pqb_internal.DomainColumn) {
3384
+ const [schemaName = currentSchema, name] = (0, rake_db.getSchemaAndTableFromName)(currentSchema, column.dataType);
3385
+ domains.set(column.dataType, {
3386
+ schemaName,
3387
+ name,
3388
+ column: column.data.as ?? pqb_internal.UnknownColumn.instance
3389
+ });
3390
+ } else {
3391
+ const en = column.dataType === "enum" ? column : column instanceof pqb_internal.ArrayColumn && column.data.item.dataType === "enum" ? column.data.item : void 0;
3392
+ if (en) processEnumColumn(en, currentSchema, codeItems);
3393
+ }
3394
+ }
3395
+ };
3042
3396
  const addGrantSchemas = (schemas, currentSchema, grant) => {
3043
3397
  for (const schema of [
3044
3398
  ...grant.schemas ?? [],