true-pg 0.2.3 → 0.3.1

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.
Files changed (49) hide show
  1. package/README.md +12 -8
  2. package/lib/bin.js +3 -7
  3. package/lib/extractor/adapter.d.ts +15 -0
  4. package/lib/extractor/adapter.js +53 -0
  5. package/lib/extractor/canonicalise.d.ts +64 -0
  6. package/lib/extractor/canonicalise.js +245 -0
  7. package/lib/extractor/fetchExtensionItemIds.d.ts +12 -0
  8. package/lib/extractor/fetchExtensionItemIds.js +43 -0
  9. package/lib/extractor/fetchTypes.d.ts +4 -0
  10. package/lib/extractor/fetchTypes.js +65 -0
  11. package/lib/extractor/index.d.ts +95 -0
  12. package/lib/extractor/index.js +140 -0
  13. package/lib/extractor/kinds/composite.d.ts +15 -0
  14. package/lib/extractor/kinds/composite.js +13 -0
  15. package/lib/extractor/kinds/domain.d.ts +15 -0
  16. package/lib/extractor/kinds/domain.js +13 -0
  17. package/lib/extractor/kinds/enum.d.ts +9 -0
  18. package/lib/extractor/kinds/enum.js +20 -0
  19. package/lib/extractor/kinds/function.d.ts +73 -0
  20. package/lib/extractor/kinds/function.js +179 -0
  21. package/lib/extractor/kinds/materialized-view.d.ts +17 -0
  22. package/lib/extractor/kinds/materialized-view.js +64 -0
  23. package/lib/extractor/kinds/parts/commentMapQueryPart.d.ts +2 -0
  24. package/lib/extractor/kinds/parts/commentMapQueryPart.js +14 -0
  25. package/lib/extractor/kinds/parts/indexMapQueryPart.d.ts +2 -0
  26. package/lib/extractor/kinds/parts/indexMapQueryPart.js +27 -0
  27. package/lib/extractor/kinds/range.d.ts +15 -0
  28. package/lib/extractor/kinds/range.js +13 -0
  29. package/lib/extractor/kinds/table.d.ts +212 -0
  30. package/lib/extractor/kinds/table.js +217 -0
  31. package/lib/extractor/kinds/util/parseInlineTable.d.ts +9 -0
  32. package/lib/extractor/kinds/util/parseInlineTable.js +135 -0
  33. package/lib/extractor/kinds/util/parseInlineTable.test.d.ts +1 -0
  34. package/lib/extractor/kinds/util/parseInlineTable.test.js +26 -0
  35. package/lib/extractor/kinds/view.d.ts +17 -0
  36. package/lib/extractor/kinds/view.js +63 -0
  37. package/lib/extractor/pgtype.d.ts +41 -0
  38. package/lib/extractor/pgtype.js +30 -0
  39. package/lib/index.d.ts +2 -2
  40. package/lib/index.js +38 -28
  41. package/lib/kysely/builtins.js +6 -4
  42. package/lib/kysely/index.js +103 -59
  43. package/lib/types.d.ts +38 -10
  44. package/lib/types.js +14 -3
  45. package/lib/util.d.ts +11 -0
  46. package/lib/util.js +6 -0
  47. package/lib/zod/builtins.js +11 -9
  48. package/lib/zod/index.js +107 -60
  49. package/package.json +3 -4
@@ -0,0 +1,140 @@
1
+ import { Client as Pg } from "pg";
2
+ import { PGlite as Pglite } from "@electric-sql/pglite";
3
+ import { DbAdapter } from "./adapter.js";
4
+ import extractTable, {} from "./kinds/table.js";
5
+ import extractView, {} from "./kinds/view.js";
6
+ import extractMaterializedView, {} from "./kinds/materialized-view.js";
7
+ import extractEnum, {} from "./kinds/enum.js";
8
+ import extractComposite, {} from "./kinds/composite.js";
9
+ import extractFunction, {} from "./kinds/function.js";
10
+ import extractDomain, {} from "./kinds/domain.js";
11
+ import extractRange, {} from "./kinds/range.js";
12
+ import fetchTypes from "./fetchTypes.js";
13
+ import { canonicalise, Canonical } from "./canonicalise.js";
14
+ export { Canonical };
15
+ export { FunctionReturnTypeKind } from "./kinds/function.js";
16
+ const emptySchema = {
17
+ tables: [],
18
+ views: [],
19
+ materializedViews: [],
20
+ enums: [],
21
+ composites: [],
22
+ functions: [],
23
+ domains: [],
24
+ ranges: [],
25
+ };
26
+ const populatorMap = {
27
+ table: extractTable,
28
+ view: extractView,
29
+ materializedView: extractMaterializedView,
30
+ enum: extractEnum,
31
+ composite: extractComposite,
32
+ function: extractFunction,
33
+ domain: extractDomain,
34
+ range: extractRange,
35
+ };
36
+ export class Extractor {
37
+ db;
38
+ /**
39
+ * @param connectionConfig - Connection string or configuration object for Postgres connection
40
+ */
41
+ constructor(opts) {
42
+ let pg;
43
+ if (opts.pg)
44
+ pg = opts.pg;
45
+ else if (opts.uri)
46
+ pg = new Pg({ connectionString: opts.uri });
47
+ else if (opts.config)
48
+ pg = new Pg(opts.config);
49
+ else {
50
+ console.error("One of these options are required in your config file: pg, uri, config. See documentation for more information.");
51
+ process.exit(1);
52
+ }
53
+ this.db = new DbAdapter(pg);
54
+ }
55
+ async canonicalise(types) {
56
+ return canonicalise(this.db, types);
57
+ }
58
+ async getBuiltinTypes() {
59
+ await this.db.connect();
60
+ const db = this.db;
61
+ const query = `
62
+ SELECT
63
+ t.typname AS name,
64
+ t.typlen AS internal_size,
65
+ pg_catalog.format_type(t.oid, NULL) AS format,
66
+ CASE t.typtype
67
+ WHEN 'b' THEN 'base'
68
+ WHEN 'c' THEN 'composite'
69
+ WHEN 'd' THEN 'domain'
70
+ WHEN 'e' THEN 'enum'
71
+ WHEN 'p' THEN 'pseudo'
72
+ WHEN 'r' THEN 'range'
73
+ ELSE 'unknown'
74
+ END AS kind
75
+ FROM pg_catalog.pg_type t
76
+ WHERE t.typnamespace = 'pg_catalog'::regnamespace
77
+ ORDER BY name;
78
+ `;
79
+ const result = await db.query(query);
80
+ await db.close();
81
+ return result;
82
+ }
83
+ /**
84
+ * Perform the extraction
85
+ * @param options - Optional options
86
+ * @returns A record of all the schemas extracted, indexed by schema name.
87
+ */
88
+ async extractSchemas(options) {
89
+ await this.db.connect();
90
+ const db = this.db;
91
+ const q = await db.query(`
92
+ SELECT nspname FROM pg_catalog.pg_namespace
93
+ WHERE nspname != 'information_schema'
94
+ AND nspname NOT LIKE 'pg_%'
95
+ `);
96
+ const allSchemaNames = q.map(r => r.nspname);
97
+ const schemaNames = options?.schemas ?? allSchemaNames;
98
+ if (options?.schemas) {
99
+ const missingSchemas = schemaNames.filter(schemaName => !allSchemaNames.includes(schemaName));
100
+ if (missingSchemas.length > 0) {
101
+ throw new Error(`No schemas found for ${missingSchemas.join(", ")}`);
102
+ }
103
+ }
104
+ const pgTypes = await fetchTypes(db, schemaNames);
105
+ const filtered = options?.typeFilter ? pgTypes.filter(element => options.typeFilter(element)) : pgTypes;
106
+ const typesToExtract = filtered.filter(x => x.kind);
107
+ const skipped = filtered.filter(x => !x.kind).map(x => x.name);
108
+ if (skipped.length) {
109
+ console.warn("Skipping types of unsupported kinds:", skipped.join(", "));
110
+ console.warn("This is a bug!");
111
+ }
112
+ options?.onProgressStart?.(typesToExtract.length);
113
+ const populated = (await Promise.all(typesToExtract.map(async (pgType) => {
114
+ const result = await populatorMap[pgType.kind](db, pgType);
115
+ options?.onProgress?.();
116
+ return result;
117
+ }))).flat();
118
+ const schemas = {};
119
+ for (const p of populated) {
120
+ if (!(p.schemaName in schemas)) {
121
+ schemas[p.schemaName] = {
122
+ name: p.schemaName,
123
+ ...emptySchema,
124
+ };
125
+ }
126
+ schemas[p.schemaName][`${p.kind}s`] = [
127
+ ...schemas[p.schemaName][`${p.kind}s`],
128
+ p,
129
+ ];
130
+ }
131
+ const result =
132
+ // options?.resolveViews
133
+ // ? resolveViewColumns(schemas)
134
+ // :
135
+ schemas;
136
+ options?.onProgressEnd?.();
137
+ await db.close();
138
+ return result;
139
+ }
140
+ }
@@ -0,0 +1,15 @@
1
+ import type { DbAdapter } from "../adapter.ts";
2
+ import type { PgType } from "../pgtype.ts";
3
+ import { Canonical } from "../canonicalise.ts";
4
+ /**
5
+ * Composite type in a schema with details.
6
+ */
7
+ export interface CompositeTypeDetails extends PgType<"composite"> {
8
+ /**
9
+ * Canonical representation of the composite type
10
+ * with full attribute details.
11
+ */
12
+ canonical: Canonical.Composite;
13
+ }
14
+ declare const extractComposite: (db: DbAdapter, composite: PgType<"composite">) => Promise<CompositeTypeDetails>;
15
+ export default extractComposite;
@@ -0,0 +1,13 @@
1
+ import { Canonical, canonicalise } from "../canonicalise.js";
2
+ const extractComposite = async (db, composite) => {
3
+ // Form the fully qualified type name
4
+ const fullTypeName = `"${composite.schemaName}"."${composite.name}"`;
5
+ // Get canonical type information with all the metadata
6
+ const [canonical] = await canonicalise(db, [fullTypeName]);
7
+ // Return the composite type with its canonical representation
8
+ return {
9
+ ...composite,
10
+ canonical: canonical,
11
+ };
12
+ };
13
+ export default extractComposite;
@@ -0,0 +1,15 @@
1
+ import type { DbAdapter } from "../adapter.ts";
2
+ import type { PgType } from "../pgtype.ts";
3
+ import { Canonical } from "../canonicalise.ts";
4
+ /**
5
+ * Domain type in a schema with details.
6
+ */
7
+ export interface DomainDetails extends PgType<"domain"> {
8
+ /**
9
+ * Canonical representation of the domain type
10
+ * with full attribute details.
11
+ */
12
+ canonical: Canonical.Domain;
13
+ }
14
+ declare const extractDomain: (db: DbAdapter, domain: PgType<"domain">) => Promise<DomainDetails>;
15
+ export default extractDomain;
@@ -0,0 +1,13 @@
1
+ import { Canonical, canonicalise } from "../canonicalise.js";
2
+ const extractDomain = async (db, domain) => {
3
+ // Form the fully qualified type name
4
+ const fullTypeName = `"${domain.schemaName}"."${domain.name}"`;
5
+ // Get canonical type information with all the metadata
6
+ const [canonical] = await canonicalise(db, [fullTypeName]);
7
+ // Return the composite type with its canonical representation
8
+ return {
9
+ ...domain,
10
+ canonical: canonical,
11
+ };
12
+ };
13
+ export default extractDomain;
@@ -0,0 +1,9 @@
1
+ import { DbAdapter } from "../adapter.ts";
2
+ import type { PgType } from "../pgtype.ts";
3
+ /** Enum type in a schema. */
4
+ export interface EnumDetails extends PgType<"enum"> {
5
+ /** Array of enum values in order. */
6
+ values: string[];
7
+ }
8
+ declare const extractEnum: (db: DbAdapter, pgEnum: PgType<"enum">) => Promise<EnumDetails>;
9
+ export default extractEnum;
@@ -0,0 +1,20 @@
1
+ import { DbAdapter } from "../adapter.js";
2
+ const extractEnum = async (db, pgEnum) => {
3
+ const results = await db.query(`
4
+ SELECT
5
+ json_agg(pg_enum.enumlabel ORDER BY pg_enum.enumsortorder) as "values"
6
+ FROM
7
+ pg_type
8
+ JOIN pg_namespace ON pg_type.typnamespace = pg_namespace.oid
9
+ JOIN pg_enum ON pg_type.oid = pg_enum.enumtypid
10
+ WHERE
11
+ pg_type.typtype = 'e'
12
+ AND pg_namespace.nspname = $2
13
+ AND typname = $1
14
+ `, [pgEnum.name, pgEnum.schemaName]);
15
+ return {
16
+ ...pgEnum,
17
+ ...results[0],
18
+ };
19
+ };
20
+ export default extractEnum;
@@ -0,0 +1,73 @@
1
+ import type { DbAdapter } from "../adapter.ts";
2
+ import type { PgType } from "../pgtype.ts";
3
+ import { Canonical } from "../canonicalise.ts";
4
+ declare const parameterModeMap: {
5
+ readonly i: "IN";
6
+ readonly o: "OUT";
7
+ readonly b: "INOUT";
8
+ readonly v: "VARIADIC";
9
+ readonly t: "TABLE";
10
+ };
11
+ type ParameterMode = (typeof parameterModeMap)[keyof typeof parameterModeMap];
12
+ export type FunctionParameter = {
13
+ name: string;
14
+ type: Canonical;
15
+ mode: ParameterMode;
16
+ hasDefault: boolean;
17
+ ordinalPosition: number;
18
+ };
19
+ declare const volatilityMap: {
20
+ readonly i: "IMMUTABLE";
21
+ readonly s: "STABLE";
22
+ readonly v: "VOLATILE";
23
+ };
24
+ type FunctionVolatility = (typeof volatilityMap)[keyof typeof volatilityMap];
25
+ declare const parallelSafetyMap: {
26
+ readonly s: "SAFE";
27
+ readonly r: "RESTRICTED";
28
+ readonly u: "UNSAFE";
29
+ };
30
+ type FunctionParallelSafety = (typeof parallelSafetyMap)[keyof typeof parallelSafetyMap];
31
+ export declare enum FunctionReturnTypeKind {
32
+ Regular = "regular",
33
+ InlineTable = "inline_table",
34
+ ExistingTable = "table"
35
+ }
36
+ export declare namespace FunctionReturnType {
37
+ type Regular = {
38
+ kind: FunctionReturnTypeKind.Regular;
39
+ type: Canonical;
40
+ isSet: boolean;
41
+ };
42
+ type InlineTable = {
43
+ kind: FunctionReturnTypeKind.InlineTable;
44
+ columns: {
45
+ name: string;
46
+ type: Canonical;
47
+ }[];
48
+ isSet: boolean;
49
+ };
50
+ type ExistingTable = {
51
+ kind: FunctionReturnTypeKind.ExistingTable;
52
+ schema: string;
53
+ name: string;
54
+ isSet: boolean;
55
+ };
56
+ }
57
+ export type FunctionReturnType = FunctionReturnType.Regular | FunctionReturnType.InlineTable | FunctionReturnType.ExistingTable;
58
+ export interface FunctionDetails extends PgType<"function"> {
59
+ parameters: FunctionParameter[];
60
+ returnType: FunctionReturnType;
61
+ language: string;
62
+ definition: string;
63
+ isStrict: boolean;
64
+ isSecurityDefiner: boolean;
65
+ isLeakProof: boolean;
66
+ volatility: FunctionVolatility;
67
+ parallelSafety: FunctionParallelSafety;
68
+ estimatedCost: number;
69
+ estimatedRows: number | null;
70
+ comment: string | null;
71
+ }
72
+ declare function extractFunction(db: DbAdapter, pgType: PgType<"function">): Promise<FunctionDetails[]>;
73
+ export default extractFunction;
@@ -0,0 +1,179 @@
1
+ import { canonicalise, Canonical } from "../canonicalise.js";
2
+ import { parsePostgresTableDefinition } from "./util/parseInlineTable.js";
3
+ const parameterModeMap = {
4
+ i: "IN",
5
+ o: "OUT",
6
+ b: "INOUT",
7
+ v: "VARIADIC",
8
+ t: "TABLE",
9
+ };
10
+ const removeNulls = (o) => {
11
+ for (const key in o)
12
+ if (o[key] === null)
13
+ delete o[key];
14
+ return o;
15
+ };
16
+ const INPUT_MODES = ["i", "b", "v"];
17
+ const volatilityMap = {
18
+ i: "IMMUTABLE",
19
+ s: "STABLE",
20
+ v: "VOLATILE",
21
+ };
22
+ const parallelSafetyMap = {
23
+ s: "SAFE",
24
+ r: "RESTRICTED",
25
+ u: "UNSAFE",
26
+ };
27
+ export var FunctionReturnTypeKind;
28
+ (function (FunctionReturnTypeKind) {
29
+ FunctionReturnTypeKind["Regular"] = "regular";
30
+ FunctionReturnTypeKind["InlineTable"] = "inline_table";
31
+ FunctionReturnTypeKind["ExistingTable"] = "table";
32
+ })(FunctionReturnTypeKind || (FunctionReturnTypeKind = {}));
33
+ async function extractFunction(db, pgType) {
34
+ const query = `
35
+ SELECT
36
+ p.proname as name,
37
+ format_type(p.prorettype, null) as return_type_string,
38
+ l.lanname AS language,
39
+ p.prosrc AS definition,
40
+ p.proisstrict AS is_strict,
41
+ p.prosecdef AS is_security_definer,
42
+ p.proleakproof AS is_leak_proof,
43
+ p.proretset AS returns_set,
44
+ p.provolatile AS volatility,
45
+ p.proparallel AS parallel_safety,
46
+ p.procost AS estimated_cost,
47
+ CASE WHEN p.proretset THEN p.prorows ELSE NULL END AS estimated_rows,
48
+ d.description AS comment,
49
+ p.prorettype,
50
+ p.proargnames as arg_names,
51
+ array_to_json(p.proargmodes) AS arg_modes,
52
+ array_to_json(COALESCE(p.proallargtypes::regtype[], p.proargtypes::regtype[])) AS arg_types,
53
+ pronargs,
54
+ p.pronargdefaults as default_arg_count,
55
+ p.proargdefaults as arg_defaults,
56
+ pg_get_function_arguments(p.oid) AS arg_list,
57
+ pg_get_function_identity_arguments(p.oid) AS identity_args,
58
+ pg_get_function_result(p.oid) as declared_return_type,
59
+ ret_typ.typtype as return_type_kind_code,
60
+ ret_typ_ns.nspname as return_type_schema,
61
+ ret_typ.typname as return_type_name,
62
+ ret_typ.typrelid as return_type_relation_oid,
63
+ ret_rel.relkind as return_type_relation_kind
64
+ FROM pg_proc p
65
+ LEFT JOIN pg_namespace n ON n.oid = p.pronamespace
66
+ LEFT JOIN pg_description d ON d.objoid = p.oid
67
+ LEFT JOIN pg_language l ON l.oid = p.prolang
68
+ LEFT JOIN pg_type ret_typ ON ret_typ.oid = p.prorettype
69
+ LEFT JOIN pg_namespace ret_typ_ns ON ret_typ.typnamespace = ret_typ_ns.oid
70
+ LEFT JOIN pg_class ret_rel ON ret_rel.oid = ret_typ.typrelid
71
+ WHERE n.nspname = $1 AND p.proname = $2
72
+ `;
73
+ const rows = await db.query(query, [pgType.schemaName, pgType.name]);
74
+ const functions = Promise.all(rows.map(async (row) => {
75
+ if (row.arg_names && !row.arg_modes)
76
+ row.arg_modes = row.arg_names.map(() => "i");
77
+ const argModes = row.arg_modes?.map(mode => parameterModeMap[mode]) ?? [];
78
+ const canonical_arg_types = row.arg_types ? await canonicalise(db, row.arg_types) : [];
79
+ let returnType;
80
+ const tableMatch = row.declared_return_type.match(/^TABLE\((.*)\)$/i);
81
+ if (tableMatch) {
82
+ const columnDefs = parsePostgresTableDefinition(row.declared_return_type);
83
+ const columnTypes = columnDefs.map(col => col.type);
84
+ const canonicalColumnTypes = await canonicalise(db, columnTypes);
85
+ returnType = {
86
+ kind: FunctionReturnTypeKind.InlineTable,
87
+ columns: columnDefs.map((col, i) => ({
88
+ name: col.name,
89
+ type: canonicalColumnTypes[i],
90
+ })),
91
+ isSet: row.returns_set,
92
+ };
93
+ }
94
+ else {
95
+ // "c" = composite type
96
+ if (row.return_type_kind_code === "c") {
97
+ if (
98
+ // "r" = table
99
+ row.return_type_relation_kind === "r" ||
100
+ // "v" = view
101
+ row.return_type_relation_kind === "v" ||
102
+ // "m" = materialized view
103
+ row.return_type_relation_kind === "m") {
104
+ returnType = {
105
+ kind: FunctionReturnTypeKind.ExistingTable,
106
+ schema: row.return_type_schema,
107
+ name: row.return_type_name,
108
+ isSet: row.returns_set,
109
+ };
110
+ }
111
+ else if (
112
+ // "c" = composite type
113
+ row.return_type_relation_kind === "c") {
114
+ const canonicalReturnType = (await canonicalise(db, [row.return_type_string]))[0];
115
+ returnType = {
116
+ kind: FunctionReturnTypeKind.Regular,
117
+ type: canonicalReturnType,
118
+ isSet: row.returns_set,
119
+ };
120
+ }
121
+ else {
122
+ console.warn(`Composite return type '${row.return_type_string}' has unexpected relkind '${row.return_type_relation_kind}' for function ${pgType.schemaName}.${row.name}`);
123
+ const canonicalReturnType = (await canonicalise(db, [row.return_type_string]))[0];
124
+ returnType = {
125
+ kind: FunctionReturnTypeKind.Regular,
126
+ type: canonicalReturnType,
127
+ isSet: row.returns_set,
128
+ };
129
+ }
130
+ }
131
+ else {
132
+ const canonicalReturnType = (await canonicalise(db, [row.return_type_string]))[0];
133
+ returnType = {
134
+ kind: FunctionReturnTypeKind.Regular,
135
+ type: canonicalReturnType,
136
+ isSet: row.returns_set,
137
+ };
138
+ }
139
+ }
140
+ // Filter to include IN, INOUT, and VARIADIC parameters as input parameters
141
+ const inputParams = row.arg_modes
142
+ ?.map((mode, index) => ({ mode, index }))
143
+ .filter(item => INPUT_MODES.includes(item.mode))
144
+ .map(item => item.index) ?? [];
145
+ const parameters = row.arg_types?.map((_, i) => {
146
+ const name = row.arg_names?.[i] ?? `$${i + 1}`;
147
+ const isInputParam = INPUT_MODES.includes(row.arg_modes?.[i] ?? "i");
148
+ const inputParamIndex = inputParams.indexOf(i);
149
+ const hasDefault = isInputParam && inputParamIndex >= inputParams.length - (row.default_arg_count ?? 0);
150
+ return {
151
+ name,
152
+ type: canonical_arg_types[i],
153
+ mode: argModes[i] ?? "IN",
154
+ hasDefault,
155
+ ordinalPosition: i + 1,
156
+ };
157
+ }) ?? [];
158
+ const func = {
159
+ name: row.name,
160
+ schemaName: pgType.schemaName,
161
+ kind: "function",
162
+ comment: row.comment,
163
+ definition: row.definition,
164
+ estimatedCost: row.estimated_cost,
165
+ estimatedRows: row.estimated_rows,
166
+ language: row.language,
167
+ isStrict: row.is_strict,
168
+ isSecurityDefiner: row.is_security_definer,
169
+ isLeakProof: row.is_leak_proof,
170
+ parameters,
171
+ volatility: volatilityMap[row.volatility],
172
+ parallelSafety: parallelSafetyMap[row.parallel_safety],
173
+ returnType: removeNulls(returnType),
174
+ };
175
+ return func;
176
+ }));
177
+ return functions;
178
+ }
179
+ export default extractFunction;
@@ -0,0 +1,17 @@
1
+ import type { DbAdapter } from "../adapter.ts";
2
+ import type { PgType } from "../pgtype.ts";
3
+ import { Canonical } from "../canonicalise.ts";
4
+ export interface MaterializedViewColumn {
5
+ name: string;
6
+ type: Canonical;
7
+ isNullable: boolean;
8
+ ordinalPosition: number;
9
+ comment: string | null;
10
+ }
11
+ export interface MaterializedViewDetails extends PgType<"materializedView"> {
12
+ columns: MaterializedViewColumn[];
13
+ definition: string;
14
+ isPopulated: boolean;
15
+ }
16
+ declare const extractMaterializedView: (db: DbAdapter, mview: PgType<"materializedView">) => Promise<MaterializedViewDetails>;
17
+ export default extractMaterializedView;
@@ -0,0 +1,64 @@
1
+ import { Canonical, canonicalise } from "../canonicalise.js";
2
+ const extractMaterializedView = async (db, mview) => {
3
+ // 1. Query for columns (using pg_attribute for potentially more accurate nullability)
4
+ const columnQuery = await db.query(`
5
+ SELECT
6
+ attr.attname AS "name",
7
+ format_type(attr.atttypid, attr.atttypmod)
8
+ || CASE
9
+ WHEN attr.attndims > 1 THEN repeat('[]', attr.attndims - 1)
10
+ ELSE ''
11
+ END AS "definedType",
12
+ NOT attr.attnotnull AS "isNullable", -- Use pg_attribute.attnotnull
13
+ attr.attnum AS "ordinalPosition", -- Use pg_attribute.attnum
14
+ col_description(c.oid, attr.attnum) AS "comment"
15
+ FROM
16
+ pg_catalog.pg_class c
17
+ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
18
+ JOIN pg_catalog.pg_attribute attr ON c.oid = attr.attrelid
19
+ WHERE
20
+ c.relname = $1 -- Materialized view name
21
+ AND n.nspname = $2 -- Schema name
22
+ AND c.relkind = 'm' -- Materialized view
23
+ AND attr.attnum > 0 -- Exclude system columns
24
+ AND NOT attr.attisdropped
25
+ ORDER BY attr.attnum;
26
+ `, [mview.name, mview.schemaName]);
27
+ // 2. Get canonical types
28
+ const definedTypes = columnQuery.map(row => row.definedType);
29
+ const canonicalTypes = await canonicalise(db, definedTypes);
30
+ const columns = columnQuery.map((row, index) => ({
31
+ name: row.name,
32
+ type: canonicalTypes[index],
33
+ isNullable: row.isNullable,
34
+ ordinalPosition: row.ordinalPosition,
35
+ comment: row.comment,
36
+ }));
37
+ // 3. Query for materialized view definition, comment, and properties
38
+ const mviewInfoQuery = await db.query(`
39
+ SELECT
40
+ m.definition,
41
+ d.description AS "comment",
42
+ m.ispopulated AS "isPopulated"
43
+ FROM
44
+ pg_catalog.pg_matviews m
45
+ JOIN pg_catalog.pg_class c ON m.matviewname = c.relname AND c.relkind = 'm'
46
+ JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid AND m.schemaname = n.nspname
47
+ LEFT JOIN pg_catalog.pg_description d ON c.oid = d.objoid AND d.objsubid = 0
48
+ WHERE
49
+ m.matviewname = $1
50
+ AND m.schemaname = $2;
51
+ `, [mview.name, mview.schemaName]);
52
+ const mviewInfo = mviewInfoQuery[0];
53
+ if (!mviewInfo) {
54
+ throw new Error(`Could not find materialized view "${mview.schemaName}"."${mview.name}".`);
55
+ }
56
+ return {
57
+ ...mview,
58
+ columns,
59
+ comment: mviewInfo.comment ?? mview.comment,
60
+ definition: mviewInfo.definition,
61
+ isPopulated: mviewInfo.isPopulated,
62
+ };
63
+ };
64
+ export default extractMaterializedView;
@@ -0,0 +1,2 @@
1
+ declare const commentMapQueryPart = "\n\tSELECT\n\t\ta.attname as \"column_name\",\n\t\tcol_description(c.oid, a.attnum::int) as \"comment\"\n\tFROM\n\t\tpg_class c\n\t\tJOIN pg_attribute a on a.attrelid=c.oid\n\t\tJOIN pg_namespace n ON c.relnamespace = n.oid\n\tWHERE\n\t\tn.nspname = $2\n\t\tAND c.relname = $1\n";
2
+ export default commentMapQueryPart;
@@ -0,0 +1,14 @@
1
+ // Extract comments from attributes. Used for tables, views, materialized views and composite types.
2
+ const commentMapQueryPart = `
3
+ SELECT
4
+ a.attname as "column_name",
5
+ col_description(c.oid, a.attnum::int) as "comment"
6
+ FROM
7
+ pg_class c
8
+ JOIN pg_attribute a on a.attrelid=c.oid
9
+ JOIN pg_namespace n ON c.relnamespace = n.oid
10
+ WHERE
11
+ n.nspname = $2
12
+ AND c.relname = $1
13
+ `;
14
+ export default commentMapQueryPart;
@@ -0,0 +1,2 @@
1
+ declare const indexMapQueryPart = "\n\tSELECT\n\t\ta.attname AS column_name,\n\t\tbool_or(ix.indisprimary) AS is_primary,\n\t\tjson_agg(json_build_object(\n\t\t\t\t'name', i.relname,\n\t\t\t\t'isPrimary', ix.indisprimary)\n\t\t\t) AS indices\n\tFROM\n\t\tpg_class t,\n\t\tpg_class i,\n\t\tpg_index ix,\n\t\tpg_attribute a,\n\t\tpg_namespace n\n\tWHERE\n\t\tt.oid = ix.indrelid\n\t\tAND i.oid = ix.indexrelid\n\t\tAND a.attrelid = t.oid\n\t\tAND n.oid = t.relnamespace\n\t\tAND a.attnum = ANY (ix.indkey)\n\t\tAND t.relkind = 'r'\n\t\tAND t.relname = $1\n\t\tAND n.nspname = $2\n\tGROUP BY\n\t\ta.attname\n";
2
+ export default indexMapQueryPart;
@@ -0,0 +1,27 @@
1
+ const indexMapQueryPart = `
2
+ SELECT
3
+ a.attname AS column_name,
4
+ bool_or(ix.indisprimary) AS is_primary,
5
+ json_agg(json_build_object(
6
+ 'name', i.relname,
7
+ 'isPrimary', ix.indisprimary)
8
+ ) AS indices
9
+ FROM
10
+ pg_class t,
11
+ pg_class i,
12
+ pg_index ix,
13
+ pg_attribute a,
14
+ pg_namespace n
15
+ WHERE
16
+ t.oid = ix.indrelid
17
+ AND i.oid = ix.indexrelid
18
+ AND a.attrelid = t.oid
19
+ AND n.oid = t.relnamespace
20
+ AND a.attnum = ANY (ix.indkey)
21
+ AND t.relkind = 'r'
22
+ AND t.relname = $1
23
+ AND n.nspname = $2
24
+ GROUP BY
25
+ a.attname
26
+ `;
27
+ export default indexMapQueryPart;
@@ -0,0 +1,15 @@
1
+ import type { DbAdapter } from "../adapter.ts";
2
+ import type { PgType } from "../pgtype.ts";
3
+ import { Canonical } from "../canonicalise.ts";
4
+ /**
5
+ * Range type in a schema with details.
6
+ */
7
+ export interface RangeDetails extends PgType<"range"> {
8
+ /**
9
+ * Canonical representation of the range type
10
+ * with full attribute details.
11
+ */
12
+ canonical: Canonical.Range;
13
+ }
14
+ declare const extractRange: (db: DbAdapter, range: PgType<"range">) => Promise<RangeDetails>;
15
+ export default extractRange;
@@ -0,0 +1,13 @@
1
+ import { Canonical, canonicalise } from "../canonicalise.js";
2
+ const extractRange = async (db, range) => {
3
+ // Form the fully qualified type name
4
+ const fullTypeName = `"${range.schemaName}"."${range.name}"`;
5
+ // Get canonical type information with all the metadata
6
+ const [canonical] = await canonicalise(db, [fullTypeName]);
7
+ // Return the composite type with its canonical representation
8
+ return {
9
+ ...range,
10
+ canonical: canonical,
11
+ };
12
+ };
13
+ export default extractRange;