orchid-orm 1.72.7 → 1.72.9

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,26 @@
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, queryToSql, quoteObjectKey, raw, rawSqlToSql, singleQuote, sqlToRawSql, 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 viewDataToSql = (viewData, viewName) => {
9
+ return sqlToRawSql(viewDataToQuerySql(viewData, viewName));
10
+ };
11
+ const viewDataToQuerySql = (viewData, viewName) => {
12
+ if (viewData.query) return queryToSql(viewData.query);
13
+ if (viewData.sql !== void 0) return rawSqlToSql(viewData.sql);
14
+ throw new Error(`Either sql or query is required for view ${viewName}`);
15
+ };
16
+ const compareSqlExpressions = async (expressions, adapter) => {
17
+ if (!expressions.length) return;
10
18
  let id = 1;
11
- for (const { source, compare, handle } of tableExpressions) {
19
+ for (const { source, compare, handle } of expressions) {
12
20
  const viewName = `orchidTmpView${id++}`;
13
21
  const values = [];
14
22
  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})`,
23
+ `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
24
  `SELECT pg_get_viewdef('${viewName}') v`,
17
25
  `DROP VIEW ${viewName}`
18
26
  ].join("; ");
@@ -27,6 +35,50 @@ const compareSqlExpressions = async (tableExpressions, adapter) => {
27
35
  handle(compareSqlExpressionResult(result.rows[0].v, compare[0].inCode));
28
36
  }
29
37
  };
38
+ const compareViewsExpressions = async (adapter, compare) => {
39
+ if (!compare.length) return;
40
+ const queries = [];
41
+ let id = 1;
42
+ compare.forEach(({ inCode, ast }, i) => {
43
+ const viewName = `orchidTmpView${id++}ForViews`;
44
+ queries.push({ batch: [
45
+ `SAVEPOINT "${viewName}S"`,
46
+ `CREATE TEMPORARY${"recursive" in ast.options && ast.options.recursive ? " RECURSIVE" : ""} VIEW "${viewName}" (${ast.options.columns?.map((column) => `"${column}"`).join(", ")}) AS (${inCode})`,
47
+ `SELECT ${i} i, '${viewName}' v, pg_get_viewdef('"${viewName}"') sql`,
48
+ `DROP VIEW "${viewName}"`,
49
+ `RELEASE SAVEPOINT "${viewName}S"`
50
+ ] });
51
+ });
52
+ let results;
53
+ try {
54
+ const sql = queries.flatMap((q) => q.batch).join(";");
55
+ const query = () => adapter.query(sql, []);
56
+ results = await (adapter.isInTransaction() ? adapter.savepoint("orchidOrmGeneratorViews", query) : query());
57
+ } catch {
58
+ results = (await Promise.all(queries.map(async ({ batch: queries }, i) => {
59
+ const sql = queries.join(";");
60
+ const query = () => adapter.query(sql, []);
61
+ return await (adapter.isInTransaction() ? adapter.savepoint(`orchidOrmGeneratorViews${i}`, query) : query()).catch((err) => {
62
+ if (typeof err === "object") {
63
+ const code = getDriverErrorCode(err);
64
+ if (code === "42703" || code === "42704" || code === "42P01") return [];
65
+ }
66
+ throw err;
67
+ });
68
+ }))).flat();
69
+ }
70
+ const handled = /* @__PURE__ */ new Set();
71
+ for (const result of results) for (const row of result.rows) if ("sql" in row) {
72
+ const { i, v } = row;
73
+ const cmp = compare[i];
74
+ const hasQuote = !!cmp.inDb.match(/\w*WITH RECURSIVE "/);
75
+ if (row.sql.replaceAll(hasQuote ? v : `"${v}"`, cmp.ast.name) !== cmp.inDb) cmp.onNotEqual();
76
+ handled.add(i);
77
+ }
78
+ compare.forEach((cmp, i) => {
79
+ if (!handled.has(i)) cmp.onNotEqual();
80
+ });
81
+ };
30
82
  const compareSqlExpressionResult = (resultSql, inCode) => {
31
83
  let pos = 7;
32
84
  const rgx = /\s+AS\s+"\*(inDb-\d+|inCode-\d+-\d+)\*",?/g;
@@ -84,8 +136,9 @@ const processSchemas = async (ast, dbStructure, { codeItems: { schemas }, verify
84
136
  if (i) {
85
137
  const from = dropSchemas[i - 1];
86
138
  dropSchemas.splice(i - 1, 1);
139
+ const views = dbStructure.views || [];
87
140
  renameSchemaInStructures(dbStructure.tables, from, schema);
88
- renameSchemaInStructures(dbStructure.views, from, schema);
141
+ renameSchemaInStructures(views, from, schema);
89
142
  renameSchemaInStructures(dbStructure.indexes, from, schema);
90
143
  renameSchemaInStructures(dbStructure.excludes, from, schema);
91
144
  renameSchemaInStructures(dbStructure.constraints, from, schema);
@@ -412,7 +465,7 @@ const renameColumn = (columns, from, to) => {
412
465
  const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems: { domains }, structureToAstCtx, currentSchema, internal: { generatorIgnore } }, pendingDbTypes) => {
413
466
  const codeDomains = [];
414
467
  if (domains) for (const { schemaName, name, column } of domains) codeDomains.push(makeComparableDomain(currentSchema, schemaName, name, column));
415
- const tableExpressions = [];
468
+ const expressions = [];
416
469
  const holdCodeDomains = /* @__PURE__ */ new Set();
417
470
  for (const domain of dbStructure.domains) {
418
471
  if (generatorIgnore?.schemas?.includes(domain.schemaName) || generatorIgnore?.domains?.includes(domain.name)) continue;
@@ -438,7 +491,7 @@ const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems
438
491
  pushCompareDefault(compare, domain, found);
439
492
  pushCompareChecks(compare, domain, found);
440
493
  const source = `(VALUES (NULL::${getColumnDbType(dbColumn, currentSchema)})) t(value)`;
441
- tableExpressions.push({
494
+ expressions.push({
442
495
  compare,
443
496
  source,
444
497
  handle(i) {
@@ -469,8 +522,8 @@ const processDomains = async (ast, adapter, domainsMap, dbStructure, { codeItems
469
522
  ast.push(createAst(codeDomain));
470
523
  pendingDbTypes.add(codeDomain.schemaName, codeDomain.name);
471
524
  }
472
- if (tableExpressions.length) {
473
- await compareSqlExpressions(tableExpressions, adapter);
525
+ if (expressions.length) {
526
+ await compareSqlExpressions(expressions, adapter);
474
527
  if (holdCodeDomains.size) for (const codeDomain of holdCodeDomains.keys()) {
475
528
  ast.push(createAst(codeDomain));
476
529
  pendingDbTypes.add(codeDomain.schemaName, codeDomain.name);
@@ -1668,18 +1721,19 @@ const processTables = async (ast, domainsMap, adapter, dbStructure, config, { st
1668
1721
  values: [],
1669
1722
  expressions: []
1670
1723
  };
1671
- const tableExpressions = [];
1724
+ const expressions = [];
1672
1725
  const { changeTables, changeTableSchemas, dropTables, tableShapes } = collectChangeAndDropTables(adapter, config, tables, dbStructure, currentSchema, createTables, generatorIgnore);
1673
1726
  applyChangeTableSchemas(changeTableSchemas, currentSchema, ast);
1674
1727
  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);
1728
+ await applyChangeTables(adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, expressions, verifying, pendingDbTypes);
1676
1729
  processForeignKeys(config, ast, changeTables, currentSchema, tableShapes);
1677
- await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(tableExpressions, adapter)]);
1730
+ await Promise.all([applyCompareSql(compareSql, adapter), compareSqlExpressions(expressions, adapter)]);
1678
1731
  await processTableRls(adapter, ast, dbStructure, tables, currentSchema, generatorIgnore);
1679
1732
  for (const dbTable of dropTables) ast.push(tableToAst(structureToAstCtx, dbStructure, dbTable, "drop", domainsMap));
1680
1733
  };
1681
1734
  const collectCreateTables = (tables, dbStructure, currentSchema) => {
1682
1735
  return tables.reduce((acc, codeTable) => {
1736
+ if (codeTable.internal.generatorIgnored) return acc;
1683
1737
  const tableSchema = codeTable.q.schema ?? currentSchema;
1684
1738
  if (!dbStructure.tables.some((t) => t.name === codeTable.table && t.schemaName === tableSchema)) acc.push(codeTable);
1685
1739
  return acc;
@@ -1699,7 +1753,7 @@ const collectChangeAndDropTables = (adapter, config, tables, dbStructure, curren
1699
1753
  });
1700
1754
  const { schema: migrationsSchema = "public", table: migrationsTable } = getMigrationsSchemaAndTable(adapter, config);
1701
1755
  for (const dbTable of dbStructure.tables) {
1702
- if (dbTable.name === migrationsTable && dbTable.schemaName === migrationsSchema || generatorIgnore?.schemas?.includes(dbTable.schemaName) || ignoreTables?.some(({ schema, table }) => table === dbTable.name && schema === dbTable.schemaName)) continue;
1756
+ if (dbTable.name === migrationsTable && dbTable.schemaName === migrationsSchema || generatorIgnore?.schemas?.includes(dbTable.schemaName) || ignoreTables?.some(({ schema, table }) => table === dbTable.name && schema === dbTable.schemaName) || isDefinitionIgnoredDbTable(tables, dbTable, currentSchema)) continue;
1703
1757
  const codeTable = tables.find((t) => t.table === dbTable.name && (t.q.schema ?? currentSchema) === dbTable.schemaName);
1704
1758
  if (codeTable) {
1705
1759
  addChangeTable(dbStructure, changeTables, tableShapes, currentSchema, dbTable, codeTable);
@@ -1724,6 +1778,15 @@ const collectChangeAndDropTables = (adapter, config, tables, dbStructure, curren
1724
1778
  tableShapes
1725
1779
  };
1726
1780
  };
1781
+ const isDefinitionIgnoredDbTable = (tables, dbTable, currentSchema) => {
1782
+ let hasIgnoredSameName = false;
1783
+ for (const codeTable of tables) {
1784
+ if (codeTable.table !== dbTable.name) continue;
1785
+ if ((codeTable.q.schema ?? currentSchema) === dbTable.schemaName) return !!codeTable.internal.generatorIgnored;
1786
+ if (codeTable.internal.generatorIgnored) hasIgnoredSameName = true;
1787
+ }
1788
+ return hasIgnoredSameName;
1789
+ };
1727
1790
  const applyChangeTableSchemas = (changeTableSchemas, currentSchema, ast) => {
1728
1791
  for (const { codeTable, dbTable } of changeTableSchemas) {
1729
1792
  const fromSchema = dbTable.schemaName;
@@ -1738,7 +1801,7 @@ const applyChangeTableSchemas = (changeTableSchemas, currentSchema, ast) => {
1738
1801
  });
1739
1802
  }
1740
1803
  };
1741
- const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, tableExpressions, verifying, pendingDbTypes) => {
1804
+ const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStructure, domainsMap, ast, currentSchema, config, compareSql, expressions, verifying, pendingDbTypes) => {
1742
1805
  const compareExpressions = [];
1743
1806
  const typeCastsCache = {};
1744
1807
  for (const changeTableData of changeTables) {
@@ -1760,7 +1823,7 @@ const applyChangeTables = async (adapter, changeTables, structureToAstCtx, dbStr
1760
1823
  }
1761
1824
  const tableName = codeTable.table;
1762
1825
  const source = `(VALUES (${types.map((x) => `NULL::${x}`).join(", ")})) "${tableName}"(${names.map((x) => `"${x}"`).join(", ")})`;
1763
- tableExpressions.push(...compareExpressions.map((x) => ({
1826
+ expressions.push(...compareExpressions.map((x) => ({
1764
1827
  ...x,
1765
1828
  source
1766
1829
  })));
@@ -2382,13 +2445,16 @@ const normalizeRoleName = (name) => {
2382
2445
  };
2383
2446
  const getSchemaWideTargets = (dbStructure, targetKey, schemas) => {
2384
2447
  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]));
2448
+ if (targetKey === "tables") {
2449
+ const views = dbStructure.views || [];
2450
+ return [...dbStructure.tables.map((table) => ({
2451
+ target: `${table.schemaName}.${table.name}`,
2452
+ outputTarget: `${table.schemaName}.${table.name}`
2453
+ })), ...views.map((view) => ({
2454
+ target: `${view.schemaName}.${view.name}`,
2455
+ outputTarget: `${view.schemaName}.${view.name}`
2456
+ }))].filter((item) => selectedSchemas.has(item.target.split(".")[0]));
2457
+ }
2392
2458
  if (targetKey === "sequences" || targetKey === "routines") return getSchemaWideGrantTargets(dbStructure, targetKey, selectedSchemas);
2393
2459
  return [];
2394
2460
  };
@@ -2455,6 +2521,264 @@ const addGrantAst = (ast, action, grant, privileges, privilegeKey) => {
2455
2521
  grantedBy: grant.grantedBy
2456
2522
  });
2457
2523
  };
2524
+ const processViews = async (ast, adapter, dbStructure, { codeItems: { views: allViews }, currentSchema, internal: { generatorIgnore } }) => {
2525
+ const createViews = [];
2526
+ const changeViews = [];
2527
+ const dropViews = [];
2528
+ const ignoredViews = makeIgnoredViews$1(generatorIgnore, currentSchema);
2529
+ const views = allViews.filter((view) => !view.materialized);
2530
+ for (const codeView of views) {
2531
+ if (isIgnoredView$1(ignoredViews, generatorIgnore, currentSchema, codeView)) continue;
2532
+ const schemaName = codeView.q.schema ?? currentSchema;
2533
+ const dbView = dbStructure.views?.find((view) => view.schemaName === schemaName && view.name === codeView.name);
2534
+ if (dbView) changeViews.push({
2535
+ codeView,
2536
+ dbView
2537
+ });
2538
+ else createViews.push(codeView);
2539
+ }
2540
+ for (const dbView of dbStructure.views ?? []) {
2541
+ if (generatorIgnore?.schemas?.includes(dbView.schemaName) || ignoredViews.some((ignore) => matchesIgnoredView$1(ignore, dbView, currentSchema))) continue;
2542
+ if (!views.find((view) => view.name === dbView.name && (view.q.schema ?? currentSchema) === dbView.schemaName)) dropViews.push(dbView);
2543
+ }
2544
+ for (const codeView of createViews) ast.push(codeViewToAst$1(codeView, currentSchema, "create"));
2545
+ await applyChangeViews$1(ast, adapter, changeViews, currentSchema);
2546
+ for (const dbView of dropViews) ast.push(dbViewToAst$1(dbView, currentSchema, "drop"));
2547
+ };
2548
+ const applyChangeViews$1 = async (ast, adapter, changeViews, currentSchema) => {
2549
+ const compare = [];
2550
+ for (const { codeView, dbView } of changeViews) {
2551
+ const from = dbViewToAst$1(dbView, currentSchema, "drop");
2552
+ const to = codeViewToAst$1(codeView, currentSchema, "create");
2553
+ if (!isViewOptionsEqual(from.options, to.options)) {
2554
+ pushRecreateView$1(ast, from, to);
2555
+ continue;
2556
+ }
2557
+ const codeSql = typeof to.sql === "string" ? to.sql : to.sql.toSQL({ values: [] });
2558
+ compare.push({
2559
+ inDb: dbView.sql,
2560
+ inCode: codeSql,
2561
+ ast: to,
2562
+ onNotEqual() {
2563
+ pushRecreateView$1(ast, from, to);
2564
+ }
2565
+ });
2566
+ }
2567
+ await compareViewsExpressions(adapter, compare);
2568
+ };
2569
+ const pushRecreateView$1 = (ast, from, to) => {
2570
+ ast.push(from, to);
2571
+ };
2572
+ const codeViewToAst$1 = (view, currentSchema, action) => {
2573
+ const schema = view.q.schema ?? currentSchema;
2574
+ const sql = viewDataToSql(view.viewData, view.name);
2575
+ return {
2576
+ type: "view",
2577
+ action,
2578
+ schema: schema === currentSchema ? void 0 : schema,
2579
+ name: view.name,
2580
+ shape: view.shape,
2581
+ sql,
2582
+ options: {
2583
+ recursive: view.viewData.recursive,
2584
+ columns: Object.keys(view.shape),
2585
+ checkOption: view.viewData.checkOption,
2586
+ securityBarrier: view.viewData.securityBarrier,
2587
+ securityInvoker: view.viewData.securityInvoker ?? true
2588
+ },
2589
+ deps: []
2590
+ };
2591
+ };
2592
+ const dbViewToAst$1 = (view, currentSchema, action) => {
2593
+ return {
2594
+ type: "view",
2595
+ action,
2596
+ schema: view.schemaName === currentSchema ? void 0 : view.schemaName,
2597
+ name: view.name,
2598
+ shape: {},
2599
+ sql: raw({ raw: view.sql }),
2600
+ options: dbViewOptionsToAst(view),
2601
+ deps: view.deps
2602
+ };
2603
+ };
2604
+ const dbViewOptionsToAst = (view) => {
2605
+ const options = {};
2606
+ options.columns = view.columns.map((column) => column.name);
2607
+ if (view.isRecursive) options.recursive = true;
2608
+ if (view.with) for (const pair of view.with) {
2609
+ const [key, value] = pair.split("=");
2610
+ switch (key) {
2611
+ case "check_option":
2612
+ if (value === "LOCAL" || value === "CASCADED") options.checkOption = value;
2613
+ break;
2614
+ case "security_barrier":
2615
+ options.securityBarrier = value === "true";
2616
+ break;
2617
+ case "security_invoker":
2618
+ options.securityInvoker = value === "true";
2619
+ break;
2620
+ }
2621
+ }
2622
+ return options;
2623
+ };
2624
+ const isViewOptionsEqual = (from, to) => {
2625
+ 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);
2626
+ };
2627
+ const isStringArrayEqual$1 = (a, b) => {
2628
+ if ((a?.length ?? 0) !== (b?.length ?? 0)) return false;
2629
+ if (!a?.length && !b?.length) return true;
2630
+ return !!a?.every((item, i) => item === b?.[i]);
2631
+ };
2632
+ const makeIgnoredViews$1 = (generatorIgnore, currentSchema) => {
2633
+ const views = generatorIgnore?.views;
2634
+ if (!views) return [];
2635
+ return views.map((name) => {
2636
+ if (typeof name !== "string") return { name };
2637
+ const parts = name.split(".");
2638
+ return parts.length === 2 ? {
2639
+ schema: parts[0],
2640
+ name: parts[1]
2641
+ } : {
2642
+ schema: currentSchema,
2643
+ name
2644
+ };
2645
+ });
2646
+ };
2647
+ const isIgnoredView$1 = (ignoredViews, generatorIgnore, currentSchema, codeView) => {
2648
+ const schemaName = codeView.q.schema ?? currentSchema;
2649
+ return generatorIgnore?.schemas?.includes(schemaName) === true || ignoredViews.some((ignore) => {
2650
+ return matchesIgnoredView$1(ignore, {
2651
+ schemaName,
2652
+ name: codeView.name
2653
+ }, currentSchema);
2654
+ });
2655
+ };
2656
+ const matchesIgnoredView$1 = (ignore, view, currentSchema) => {
2657
+ return typeof ignore.name === "string" ? ignore.schema === view.schemaName && ignore.name === view.name : ignore.name.test(normalizedViewName$1(view, currentSchema));
2658
+ };
2659
+ const normalizedViewName$1 = (view, currentSchema) => {
2660
+ return view.schemaName === currentSchema ? view.name : `${view.schemaName}.${view.name}`;
2661
+ };
2662
+ const processMaterializedViews = async (ast, adapter, dbStructure, { codeItems: { views: allViews }, currentSchema, internal: { generatorIgnore } }) => {
2663
+ const views = allViews.filter((view) => view.materialized);
2664
+ const createViews = [];
2665
+ const changeViews = [];
2666
+ const dropViews = [];
2667
+ const ignoredViews = makeIgnoredViews(generatorIgnore, currentSchema);
2668
+ for (const codeView of views) {
2669
+ if (isIgnoredView(ignoredViews, generatorIgnore, currentSchema, codeView)) continue;
2670
+ const schemaName = codeView.q.schema ?? currentSchema;
2671
+ const dbView = dbStructure.materializedViews?.find((view) => view.schemaName === schemaName && view.name === codeView.name);
2672
+ if (dbView) changeViews.push({
2673
+ codeView,
2674
+ dbView
2675
+ });
2676
+ else createViews.push(codeView);
2677
+ }
2678
+ for (const dbView of dbStructure.materializedViews ?? []) {
2679
+ if (generatorIgnore?.schemas?.includes(dbView.schemaName) || ignoredViews.some((ignore) => matchesIgnoredView(ignore, dbView, currentSchema))) continue;
2680
+ if (!views.find((view) => view.name === dbView.name && (view.q.schema ?? currentSchema) === dbView.schemaName)) dropViews.push(dbView);
2681
+ }
2682
+ for (const codeView of createViews) ast.push(codeViewToAst(codeView, currentSchema, "create"));
2683
+ await applyChangeViews(ast, adapter, changeViews, currentSchema);
2684
+ for (const dbView of dropViews) ast.push(dbViewToAst(dbView, currentSchema, "drop"));
2685
+ };
2686
+ const applyChangeViews = async (ast, adapter, changeViews, currentSchema) => {
2687
+ const compare = [];
2688
+ for (const { codeView, dbView } of changeViews) {
2689
+ const from = dbViewToAst(dbView, currentSchema, "drop");
2690
+ const to = codeViewToAst(codeView, currentSchema, "create");
2691
+ if (!isMaterializedViewOptionsEqual(from.options, to.options)) {
2692
+ pushRecreateView(ast, from, to);
2693
+ continue;
2694
+ }
2695
+ const codeSql = typeof to.sql === "string" ? to.sql : to.sql.toSQL({ values: [] });
2696
+ compare.push({
2697
+ inDb: dbView.sql,
2698
+ inCode: codeSql,
2699
+ ast: to,
2700
+ onNotEqual() {
2701
+ pushRecreateView(ast, from, to);
2702
+ }
2703
+ });
2704
+ }
2705
+ await compareViewsExpressions(adapter, compare);
2706
+ };
2707
+ const pushRecreateView = (ast, from, to) => {
2708
+ ast.push(from, to);
2709
+ };
2710
+ const codeViewToAst = (view, currentSchema, action) => {
2711
+ const schema = view.q.schema ?? currentSchema;
2712
+ const sql = viewDataToSql(view.viewData, view.name);
2713
+ return {
2714
+ type: "materializedView",
2715
+ action,
2716
+ schema: schema === currentSchema ? void 0 : schema,
2717
+ name: view.name,
2718
+ shape: view.shape,
2719
+ sql,
2720
+ options: {
2721
+ columns: Object.keys(view.shape),
2722
+ withData: view.viewData.withData
2723
+ },
2724
+ deps: []
2725
+ };
2726
+ };
2727
+ const dbViewToAst = (view, currentSchema, action) => {
2728
+ return {
2729
+ type: "materializedView",
2730
+ action,
2731
+ schema: view.schemaName === currentSchema ? void 0 : view.schemaName,
2732
+ name: view.name,
2733
+ shape: {},
2734
+ sql: raw({ raw: view.sql }),
2735
+ options: dbMaterializedViewOptionsToAst(view),
2736
+ deps: view.deps
2737
+ };
2738
+ };
2739
+ const dbMaterializedViewOptionsToAst = (view) => {
2740
+ const options = { columns: view.columns.map((column) => column.name) };
2741
+ if (!view.isPopulated) options.withData = false;
2742
+ return options;
2743
+ };
2744
+ const isMaterializedViewOptionsEqual = (from, to) => {
2745
+ return (from.withData ?? true) === (to.withData ?? true) && isStringArrayEqual(from.columns, to.columns);
2746
+ };
2747
+ const isStringArrayEqual = (a, b) => {
2748
+ if ((a?.length ?? 0) !== (b?.length ?? 0)) return false;
2749
+ if (!a?.length && !b?.length) return true;
2750
+ return !!a?.every((item, i) => item === b?.[i]);
2751
+ };
2752
+ const makeIgnoredViews = (generatorIgnore, currentSchema) => {
2753
+ const views = generatorIgnore?.views;
2754
+ if (!views) return [];
2755
+ return views.map((name) => {
2756
+ if (typeof name !== "string") return { name };
2757
+ const parts = name.split(".");
2758
+ return parts.length === 2 ? {
2759
+ schema: parts[0],
2760
+ name: parts[1]
2761
+ } : {
2762
+ schema: currentSchema,
2763
+ name
2764
+ };
2765
+ });
2766
+ };
2767
+ const isIgnoredView = (ignoredViews, generatorIgnore, currentSchema, codeView) => {
2768
+ const schemaName = codeView.q.schema ?? currentSchema;
2769
+ return generatorIgnore?.schemas?.includes(schemaName) === true || ignoredViews.some((ignore) => {
2770
+ return matchesIgnoredView(ignore, {
2771
+ schemaName,
2772
+ name: codeView.name
2773
+ }, currentSchema);
2774
+ });
2775
+ };
2776
+ const matchesIgnoredView = (ignore, view, currentSchema) => {
2777
+ return typeof ignore.name === "string" ? ignore.schema === view.schemaName && ignore.name === view.name : ignore.name.test(normalizedViewName(view, currentSchema));
2778
+ };
2779
+ const normalizedViewName = (view, currentSchema) => {
2780
+ return view.schemaName === currentSchema ? view.name : `${view.schemaName}.${view.name}`;
2781
+ };
2458
2782
  /**
2459
2783
  * This is needed to compare SQLs of table expressions.
2460
2784
  * Need to exclude table columns of pending types, such as enums or domains,
@@ -2481,6 +2805,8 @@ const composeMigration = async (adapter, config, ast, dbStructure, params) => {
2481
2805
  await processDomains(ast, adapter, domainsMap, dbStructure, params, pendingDbTypes);
2482
2806
  await processEnums(ast, dbStructure, params, pendingDbTypes);
2483
2807
  await processTables(ast, domainsMap, adapter, dbStructure, config, params, pendingDbTypes);
2808
+ await processViews(ast, adapter, dbStructure, params);
2809
+ await processMaterializedViews(ast, adapter, dbStructure, params);
2484
2810
  return astToMigration(currentSchema, config, ast);
2485
2811
  };
2486
2812
  const rollbackErr = /* @__PURE__ */ new Error("Rollback");
@@ -2656,7 +2982,12 @@ const report = (ast, config, currentSchema) => {
2656
2982
  case "domain":
2657
2983
  code.push(`${a.action === "create" ? green("+ create domain") : red("- drop domain")} ${dbItemName(a, currentSchema)}`);
2658
2984
  break;
2985
+ case "materializedView":
2986
+ code.push(`${a.action === "create" ? green("+ create materialized view") : red("- drop materialized view")} ${dbItemName(a, currentSchema)}`);
2987
+ break;
2659
2988
  case "view":
2989
+ code.push(`${a.action === "create" ? green("+ create view") : red("- drop view")} ${dbItemName(a, currentSchema)}`);
2990
+ break;
2660
2991
  case "collation":
2661
2992
  case "constraint": break;
2662
2993
  case "renameTableItem":
@@ -2814,7 +3145,8 @@ const generate = async (adapters, config, args, afterPull) => {
2814
3145
  const { columnTypes, internal } = db.$qb;
2815
3146
  const structureParams = {
2816
3147
  loadDefaultPrivileges: internal.roles?.some((role) => role.defaultPrivileges !== void 0) ?? false,
2817
- loadGrants: !!internal.grants || hasCodeTablesWithGrants(db)
3148
+ loadGrants: !!internal.grants || hasCodeItemsWithGrants(db),
3149
+ loadViews: hasCodeViews(db)
2818
3150
  };
2819
3151
  const rolesDbStructureParam = internal.roles ? internal.managedRolesSql ? { whereSql: internal.managedRolesSql } : emptyObject : void 0;
2820
3152
  const { dbStructure } = await migrateAndPullStructures(adapters, config, db, rolesDbStructureParam, structureParams, afterPull);
@@ -2822,6 +3154,7 @@ const generate = async (adapters, config, args, afterPull) => {
2822
3154
  const adapterSchema = adapter.getSchema();
2823
3155
  const currentSchema = (typeof adapterSchema === "function" ? adapterSchema() : adapterSchema) ?? "public";
2824
3156
  const codeItems = await getActualItems(db, currentSchema, internal, columnTypes);
3157
+ const generatorIgnore = getGeneratorIgnoreWithDefinitionIgnoredTableLikes(internal.generatorIgnore, codeItems.tables, codeItems.views);
2825
3158
  const effectiveGrants = getEffectiveGrants(internal.grants, codeItems);
2826
3159
  const generateMigrationParams = {
2827
3160
  structureToAstCtx: makeStructureToAstCtx(config, currentSchema),
@@ -2829,6 +3162,7 @@ const generate = async (adapters, config, args, afterPull) => {
2829
3162
  currentSchema,
2830
3163
  internal: {
2831
3164
  ...internal,
3165
+ generatorIgnore,
2832
3166
  grants: effectiveGrants
2833
3167
  }
2834
3168
  };
@@ -2879,6 +3213,7 @@ const migrateAndPullStructures = async (adapters, config, db, roles, structurePa
2879
3213
  schemas: [],
2880
3214
  tables: [],
2881
3215
  views: [],
3216
+ materializedViews: [],
2882
3217
  indexes: [],
2883
3218
  excludes: [],
2884
3219
  constraints: [],
@@ -2893,7 +3228,8 @@ const migrateAndPullStructures = async (adapters, config, db, roles, structurePa
2893
3228
  rls: hasCodeTablesWithRls(db),
2894
3229
  roles,
2895
3230
  loadDefaultPrivileges: structureParams?.loadDefaultPrivileges,
2896
- loadGrants: structureParams?.loadGrants
3231
+ loadGrants: structureParams?.loadGrants,
3232
+ loadViews: structureParams?.loadViews
2897
3233
  })));
2898
3234
  const dbStructure = dbStructures[0];
2899
3235
  for (let i = 1; i < dbStructures.length; i++) compareDbStructures(dbStructure, dbStructures[i], i);
@@ -2906,11 +3242,17 @@ const hasCodeTablesWithRls = (db) => {
2906
3242
  }
2907
3243
  return false;
2908
3244
  };
2909
- const hasCodeTablesWithGrants = (db) => {
3245
+ const hasCodeViews = (db) => {
3246
+ return !!Object.keys(db.$views ?? {}).length;
3247
+ };
3248
+ const hasCodeItemsWithGrants = (db) => {
2910
3249
  for (const key in db) {
2911
3250
  if (key[0] === "$") continue;
2912
3251
  if (db[key].internal.tableGrants?.length) return true;
2913
3252
  }
3253
+ const views = db.$views;
3254
+ if (!views) return false;
3255
+ for (const key in views) if (views[key].internal.tableGrants?.length) return true;
2914
3256
  return false;
2915
3257
  };
2916
3258
  const getEffectiveGrants = (grants, codeItems) => {
@@ -2928,8 +3270,41 @@ const getEffectiveGrants = (grants, codeItems) => {
2928
3270
  effectiveGrants.push(internalGrant);
2929
3271
  }
2930
3272
  }
3273
+ for (const view of codeItems.views) {
3274
+ const viewGrants = view.internal.tableGrants;
3275
+ if (!viewGrants?.length) continue;
3276
+ const viewTarget = view.q.schema ? `${view.q.schema}.${view.name}` : view.name;
3277
+ for (const grant of viewGrants) {
3278
+ const internalGrant = {
3279
+ ...grant,
3280
+ to: toArray(grant.to),
3281
+ tables: [viewTarget]
3282
+ };
3283
+ effectiveGrants.push(internalGrant);
3284
+ }
3285
+ }
2931
3286
  return effectiveGrants.length ? effectiveGrants : void 0;
2932
3287
  };
3288
+ const getGeneratorIgnoreWithDefinitionIgnoredTableLikes = (generatorIgnore, tables, views) => {
3289
+ const ignoredTables = new Set(generatorIgnore?.tables);
3290
+ const ignoredViews = new Set(generatorIgnore?.views);
3291
+ let hasDefinitionIgnoredTableLikes = false;
3292
+ for (const table of tables) {
3293
+ if (!table.internal.generatorIgnored) continue;
3294
+ hasDefinitionIgnoredTableLikes = true;
3295
+ ignoredTables.add(table.q.schema ? `${table.q.schema}.${table.table}` : table.table);
3296
+ }
3297
+ for (const view of views) {
3298
+ if (!view.internal.generatorIgnored) continue;
3299
+ hasDefinitionIgnoredTableLikes = true;
3300
+ ignoredViews.add(view.q.schema ? `${view.q.schema}.${view.name}` : view.name);
3301
+ }
3302
+ return hasDefinitionIgnoredTableLikes ? {
3303
+ ...generatorIgnore,
3304
+ tables: [...ignoredTables],
3305
+ views: [...ignoredViews]
3306
+ } : generatorIgnore;
3307
+ };
2933
3308
  const compareDbStructures = (a, b, i, path) => {
2934
3309
  let err;
2935
3310
  if (typeof a !== typeof b) err = true;
@@ -2946,6 +3321,7 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2946
3321
  schemas: /* @__PURE__ */ new Set(void 0),
2947
3322
  enums: /* @__PURE__ */ new Map(),
2948
3323
  tables: [],
3324
+ views: [],
2949
3325
  domains: []
2950
3326
  };
2951
3327
  codeItems.schemas.add(currentSchema);
@@ -2971,25 +3347,26 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
2971
3347
  },
2972
3348
  q: { schema: getQuerySchema(table) }
2973
3349
  });
2974
- for (const key in table.relations) {
3350
+ if (!table.internal.generatorIgnored) for (const key in table.relations) {
2975
3351
  const column = table.shape[key];
2976
3352
  if (column && "joinTable" in column) processHasAndBelongsToManyColumn(column, habtmTables, codeItems);
2977
3353
  }
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
- }
3354
+ processCodeItemShape(table.shape, currentSchema, codeItems, domains);
3355
+ }
3356
+ const views = db.$views;
3357
+ if (views) for (const key in views) {
3358
+ const view = views[key];
3359
+ const schema = getQuerySchema(view);
3360
+ if (schema) codeItems.schemas.add(schema);
3361
+ codeItems.views.push({
3362
+ name: view.table,
3363
+ shape: view.shape,
3364
+ internal: view.internal,
3365
+ q: { schema },
3366
+ materialized: view.internal.materialized,
3367
+ viewData: view.internal.viewData ?? {}
3368
+ });
3369
+ processCodeItemShape(view.shape, currentSchema, codeItems, domains);
2993
3370
  }
2994
3371
  if (internal.extensions) for (const extension of internal.extensions) {
2995
3372
  const [schema] = getSchemaAndTableFromName(currentSchema, extension.name);
@@ -3016,6 +3393,23 @@ const getActualItems = async (db, currentSchema, internal, columnTypes) => {
3016
3393
  if (internal.grants) for (const grant of internal.grants) addGrantSchemas(codeItems.schemas, currentSchema, grant);
3017
3394
  return codeItems;
3018
3395
  };
3396
+ const processCodeItemShape = (shape, currentSchema, codeItems, domains) => {
3397
+ for (const key in shape) {
3398
+ const column = shape[key];
3399
+ if (column.data.computed) delete shape[key];
3400
+ else if (column instanceof DomainColumn) {
3401
+ const [schemaName = currentSchema, name] = getSchemaAndTableFromName(currentSchema, column.dataType);
3402
+ domains.set(column.dataType, {
3403
+ schemaName,
3404
+ name,
3405
+ column: column.data.as ?? UnknownColumn.instance
3406
+ });
3407
+ } else {
3408
+ const en = column.dataType === "enum" ? column : column instanceof ArrayColumn && column.data.item.dataType === "enum" ? column.data.item : void 0;
3409
+ if (en) processEnumColumn(en, currentSchema, codeItems);
3410
+ }
3411
+ }
3412
+ };
3019
3413
  const addGrantSchemas = (schemas, currentSchema, grant) => {
3020
3414
  for (const schema of [
3021
3415
  ...grant.schemas ?? [],