true-pg 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin.js +4 -9
- package/lib/extractor/adapter.d.ts +15 -0
- package/lib/extractor/adapter.js +53 -0
- package/lib/extractor/canonicalise.d.ts +64 -0
- package/lib/extractor/canonicalise.js +245 -0
- package/lib/extractor/fetchExtensionItemIds.d.ts +12 -0
- package/lib/extractor/fetchExtensionItemIds.js +43 -0
- package/lib/extractor/fetchTypes.d.ts +4 -0
- package/lib/extractor/fetchTypes.js +65 -0
- package/lib/extractor/index.d.ts +95 -0
- package/lib/extractor/index.js +140 -0
- package/lib/extractor/kinds/composite.d.ts +15 -0
- package/lib/extractor/kinds/composite.js +13 -0
- package/lib/extractor/kinds/domain.d.ts +15 -0
- package/lib/extractor/kinds/domain.js +13 -0
- package/lib/extractor/kinds/enum.d.ts +9 -0
- package/lib/extractor/kinds/enum.js +20 -0
- package/lib/extractor/kinds/function.d.ts +73 -0
- package/lib/extractor/kinds/function.js +179 -0
- package/lib/extractor/kinds/materialized-view.d.ts +17 -0
- package/lib/extractor/kinds/materialized-view.js +64 -0
- package/lib/extractor/kinds/parts/commentMapQueryPart.d.ts +2 -0
- package/lib/extractor/kinds/parts/commentMapQueryPart.js +14 -0
- package/lib/extractor/kinds/parts/indexMapQueryPart.d.ts +2 -0
- package/lib/extractor/kinds/parts/indexMapQueryPart.js +27 -0
- package/lib/extractor/kinds/range.d.ts +15 -0
- package/lib/extractor/kinds/range.js +13 -0
- package/lib/extractor/kinds/table.d.ts +212 -0
- package/lib/extractor/kinds/table.js +217 -0
- package/lib/extractor/kinds/util/parseInlineTable.d.ts +9 -0
- package/lib/extractor/kinds/util/parseInlineTable.js +135 -0
- package/lib/extractor/kinds/util/parseInlineTable.test.d.ts +1 -0
- package/lib/extractor/kinds/util/parseInlineTable.test.js +26 -0
- package/lib/extractor/kinds/view.d.ts +17 -0
- package/lib/extractor/kinds/view.js +63 -0
- package/lib/extractor/pgtype.d.ts +41 -0
- package/lib/extractor/pgtype.js +30 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +38 -28
- package/lib/kysely/builtins.js +6 -4
- package/lib/kysely/index.js +103 -59
- package/lib/types.d.ts +38 -10
- package/lib/types.js +14 -3
- package/lib/util.d.ts +11 -0
- package/lib/util.js +6 -0
- package/lib/zod/builtins.js +11 -9
- package/lib/zod/index.js +107 -60
- 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;
|