kern-orm 0.15.0 → 0.16.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/dist/{index-DFcelQx0.d.mts → index-DD_wYhRD.d.mts} +10 -6
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +8 -3682
- package/dist/schema-lVpsWpEF.mjs +3685 -0
- package/dist/testing.d.mts +17 -1
- package/dist/testing.mjs +332 -1
- package/dist/types.d.mts +1 -1
- package/package.json +2 -2
package/dist/testing.d.mts
CHANGED
|
@@ -1 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
import { vt as AnyDb } from "./index-DD_wYhRD.mjs";
|
|
2
|
+
export * from "kern-db/testing";
|
|
3
|
+
/** Mismatch of a table of a `Db` and the database. */
|
|
4
|
+
interface TableMismatch {
|
|
5
|
+
/** Key of the table in the schema of the `Db`. */
|
|
6
|
+
readonly table: string;
|
|
7
|
+
/** What does not match, such as a missing column. */
|
|
8
|
+
readonly message: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Finds the mismatches of the tables of `db` and its PostgreSQL schema, such as a missing column or a key without a unique index, resolving to an empty array when they match.
|
|
12
|
+
*
|
|
13
|
+
* It reads the catalog in the ambient transaction, such as a test transaction in which migrations have created the tables, and requires PostgreSQL 17 or newer.
|
|
14
|
+
* It checks that each table or view exists with the columns of the table under their physical names and of their types; for a table, also the nullability of each column, a default of each column marked with `hasDefault()`, no other column that every insert must give, a unique index of each key, a unique index of `(true)` of a singleton table, and a foreign key of each required relation `one` to a table.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findTableMismatches(db: AnyDb): Promise<TableMismatch[]>;
|
|
17
|
+
export type { TableMismatch };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,2 +1,333 @@
|
|
|
1
|
+
import { C as formatRelationName, S as collectDefinitions, r as schemaRelations } from "./schema-lVpsWpEF.mjs";
|
|
2
|
+
import * as t from "kern-db/types";
|
|
3
|
+
import { param } from "kern-db";
|
|
1
4
|
export * from "kern-db/testing";
|
|
2
|
-
|
|
5
|
+
// src/inspection/tables.ts
|
|
6
|
+
const minimumServerVersion = 17e4;
|
|
7
|
+
/**
|
|
8
|
+
* Finds the mismatches of the tables of `db` and its PostgreSQL schema, such as a missing column or a key without a unique index, resolving to an empty array when they match.
|
|
9
|
+
*
|
|
10
|
+
* It reads the catalog in the ambient transaction, such as a test transaction in which migrations have created the tables, and requires PostgreSQL 17 or newer.
|
|
11
|
+
* It checks that each table or view exists with the columns of the table under their physical names and of their types; for a table, also the nullability of each column, a default of each column marked with `hasDefault()`, no other column that every insert must give, a unique index of each key, a unique index of `(true)` of a singleton table, and a foreign key of each required relation `one` to a table.
|
|
12
|
+
*/
|
|
13
|
+
async function findTableMismatches(db) {
|
|
14
|
+
const database = db.$database;
|
|
15
|
+
const tables = db.$schema.$tables;
|
|
16
|
+
const catalog = await readCatalog(database, db.$schemaName, Object.values(tables));
|
|
17
|
+
const mismatches = [];
|
|
18
|
+
for (const key of Object.keys(tables)) for (const message of findTableProblems(tables[key], catalog)) mismatches.push({
|
|
19
|
+
table: key,
|
|
20
|
+
message
|
|
21
|
+
});
|
|
22
|
+
for (const definition of collectDefinitions(db.$schema[schemaRelations])) {
|
|
23
|
+
const message = findRelationProblem(definition, catalog);
|
|
24
|
+
if (message !== void 0) mismatches.push({
|
|
25
|
+
table: definition.key,
|
|
26
|
+
message
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return mismatches;
|
|
30
|
+
}
|
|
31
|
+
/** Reads the catalog of `tables` in PostgreSQL schema `schemaName`, or in `current_schema()` when it is `undefined`. */
|
|
32
|
+
async function readCatalog(database, schemaName, tables) {
|
|
33
|
+
const server = await database.query`
|
|
34
|
+
SELECT current_setting('server_version_num')::integer AS version, current_schema() AS schema
|
|
35
|
+
`.select({
|
|
36
|
+
version: t.integer,
|
|
37
|
+
schema: t.text.nullable()
|
|
38
|
+
}).take();
|
|
39
|
+
if (server.version < minimumServerVersion) throw new Error(`findTableMismatches requires PostgreSQL 17 or newer, but the server is ${server.version}`);
|
|
40
|
+
const resolvedSchema = schemaName ?? server.schema;
|
|
41
|
+
if (resolvedSchema === null) throw new Error("Cannot resolve the PostgreSQL schema: current_schema() is NULL, since no schema of the search path exists");
|
|
42
|
+
const names = [...new Set(tables.map((table) => table.name))];
|
|
43
|
+
const sqlTypes = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const table of tables) for (const column of Object.values(table.columns)) sqlTypes.add(column.type.sqlType);
|
|
45
|
+
return {
|
|
46
|
+
schemaName: resolvedSchema,
|
|
47
|
+
relations: await readRelations(database, resolvedSchema, names),
|
|
48
|
+
indexes: await readIndexes(database, resolvedSchema, names),
|
|
49
|
+
foreignKeys: await readForeignKeys(database, resolvedSchema, names),
|
|
50
|
+
types: await resolveTypes(database, [...sqlTypes])
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Reads the tables and views of `names` in `schemaName` with their columns. */
|
|
54
|
+
async function readRelations(database, schemaName, names) {
|
|
55
|
+
const rows = await database.query`
|
|
56
|
+
SELECT relation.relname::text AS name, relation.relkind::text AS kind,
|
|
57
|
+
attribute.attname::text AS column_name,
|
|
58
|
+
pg_catalog.format_type(attribute.atttypid, attribute.atttypmod) AS type,
|
|
59
|
+
attribute.attnotnull AS rejects_null,
|
|
60
|
+
attribute.attnotnull AND NOT EXISTS (
|
|
61
|
+
SELECT FROM pg_catalog.pg_constraint
|
|
62
|
+
WHERE conrelid = relation.oid AND contype = 'n' AND conkey = ARRAY[attribute.attnum] AND NOT convalidated
|
|
63
|
+
) AS not_null_validated,
|
|
64
|
+
attribute.atthasdef OR attribute.attidentity <> '' OR attribute.attgenerated <> '' AS has_default
|
|
65
|
+
FROM pg_catalog.pg_class AS relation
|
|
66
|
+
JOIN pg_catalog.pg_namespace AS namespace ON namespace.oid = relation.relnamespace
|
|
67
|
+
LEFT JOIN pg_catalog.pg_attribute AS attribute
|
|
68
|
+
ON attribute.attrelid = relation.oid AND attribute.attnum > 0 AND NOT attribute.attisdropped
|
|
69
|
+
WHERE namespace.nspname = ${schemaName} AND relation.relname = ANY(${param(t.array(t.text), names)})
|
|
70
|
+
`.select({
|
|
71
|
+
name: t.text,
|
|
72
|
+
kind: t.text,
|
|
73
|
+
column_name: t.text.nullable(),
|
|
74
|
+
type: t.text.nullable(),
|
|
75
|
+
rejects_null: t.boolean.nullable(),
|
|
76
|
+
not_null_validated: t.boolean.nullable(),
|
|
77
|
+
has_default: t.boolean.nullable()
|
|
78
|
+
});
|
|
79
|
+
const relations = /* @__PURE__ */ new Map();
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
let relation = relations.get(row.name);
|
|
82
|
+
if (relation === void 0) {
|
|
83
|
+
relation = {
|
|
84
|
+
kind: row.kind,
|
|
85
|
+
columns: /* @__PURE__ */ new Map()
|
|
86
|
+
};
|
|
87
|
+
relations.set(row.name, relation);
|
|
88
|
+
}
|
|
89
|
+
if (row.column_name !== null) relation.columns.set(row.column_name, {
|
|
90
|
+
type: row.type,
|
|
91
|
+
rejectsNull: row.rejects_null,
|
|
92
|
+
notNullValidated: row.not_null_validated,
|
|
93
|
+
hasDefault: row.has_default
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return relations;
|
|
97
|
+
}
|
|
98
|
+
/** Reads the unique indexes of the tables of `names` in `schemaName` by the names of their tables. */
|
|
99
|
+
async function readIndexes(database, schemaName, names) {
|
|
100
|
+
const rows = await database.query`
|
|
101
|
+
SELECT relation.relname::text AS table_name, index_relation.relname::text AS name,
|
|
102
|
+
ARRAY(
|
|
103
|
+
SELECT attribute.attname::text
|
|
104
|
+
FROM generate_series(0, index.indnkeyatts - 1) AS ordinal
|
|
105
|
+
JOIN pg_catalog.pg_attribute AS attribute
|
|
106
|
+
ON attribute.attrelid = index.indrelid AND attribute.attnum = index.indkey[ordinal]
|
|
107
|
+
ORDER BY ordinal
|
|
108
|
+
) AS columns,
|
|
109
|
+
index.indexprs IS NOT NULL AS has_expressions,
|
|
110
|
+
CASE WHEN index.indnkeyatts = 1 AND index.indkey[0] = 0
|
|
111
|
+
THEN pg_catalog.pg_get_indexdef(index.indexrelid, 1, true)
|
|
112
|
+
END AS expression,
|
|
113
|
+
index.indpred IS NOT NULL AS is_partial,
|
|
114
|
+
index.indimmediate AS is_immediate,
|
|
115
|
+
index.indisvalid AND index.indisready AND index.indislive AS is_valid
|
|
116
|
+
FROM pg_catalog.pg_index AS index
|
|
117
|
+
JOIN pg_catalog.pg_class AS relation ON relation.oid = index.indrelid
|
|
118
|
+
JOIN pg_catalog.pg_class AS index_relation ON index_relation.oid = index.indexrelid
|
|
119
|
+
JOIN pg_catalog.pg_namespace AS namespace ON namespace.oid = relation.relnamespace
|
|
120
|
+
WHERE index.indisunique AND namespace.nspname = ${schemaName} AND relation.relname = ANY(${param(t.array(t.text), names)})
|
|
121
|
+
ORDER BY index_relation.relname
|
|
122
|
+
`.select({
|
|
123
|
+
table_name: t.text,
|
|
124
|
+
name: t.text,
|
|
125
|
+
columns: t.array(t.text),
|
|
126
|
+
has_expressions: t.boolean,
|
|
127
|
+
expression: t.text.nullable(),
|
|
128
|
+
is_partial: t.boolean,
|
|
129
|
+
is_immediate: t.boolean,
|
|
130
|
+
is_valid: t.boolean
|
|
131
|
+
});
|
|
132
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const row of rows) addToGroup(indexes, row.table_name, {
|
|
134
|
+
name: row.name,
|
|
135
|
+
columns: row.columns,
|
|
136
|
+
hasExpressions: row.has_expressions,
|
|
137
|
+
expression: row.expression,
|
|
138
|
+
partial: row.is_partial,
|
|
139
|
+
immediate: row.is_immediate,
|
|
140
|
+
valid: row.is_valid
|
|
141
|
+
});
|
|
142
|
+
return indexes;
|
|
143
|
+
}
|
|
144
|
+
/** Reads the foreign keys of the tables of `names` in `schemaName` by the names of their tables. */
|
|
145
|
+
async function readForeignKeys(database, schemaName, names) {
|
|
146
|
+
const rows = await database.query`
|
|
147
|
+
SELECT relation.relname::text AS table_name, constraint_.conname::text AS name,
|
|
148
|
+
target.relname::text AS target_name, target_namespace.nspname::text AS target_schema,
|
|
149
|
+
ARRAY(
|
|
150
|
+
SELECT attribute.attname::text
|
|
151
|
+
FROM unnest(constraint_.conkey) WITH ORDINALITY AS key_column(number, ordinal)
|
|
152
|
+
JOIN pg_catalog.pg_attribute AS attribute
|
|
153
|
+
ON attribute.attrelid = constraint_.conrelid AND attribute.attnum = key_column.number
|
|
154
|
+
ORDER BY key_column.ordinal
|
|
155
|
+
) AS from_columns,
|
|
156
|
+
ARRAY(
|
|
157
|
+
SELECT attribute.attname::text
|
|
158
|
+
FROM unnest(constraint_.confkey) WITH ORDINALITY AS key_column(number, ordinal)
|
|
159
|
+
JOIN pg_catalog.pg_attribute AS attribute
|
|
160
|
+
ON attribute.attrelid = constraint_.confrelid AND attribute.attnum = key_column.number
|
|
161
|
+
ORDER BY key_column.ordinal
|
|
162
|
+
) AS to_columns,
|
|
163
|
+
constraint_.condeferrable AS is_deferrable,
|
|
164
|
+
constraint_.convalidated AS is_validated,
|
|
165
|
+
coalesce((to_jsonb(constraint_) ->> 'conenforced')::boolean, true) AS is_enforced,
|
|
166
|
+
coalesce((to_jsonb(constraint_) ->> 'conperiod')::boolean, false) AS is_period
|
|
167
|
+
FROM pg_catalog.pg_constraint AS constraint_
|
|
168
|
+
JOIN pg_catalog.pg_class AS relation ON relation.oid = constraint_.conrelid
|
|
169
|
+
JOIN pg_catalog.pg_namespace AS namespace ON namespace.oid = relation.relnamespace
|
|
170
|
+
JOIN pg_catalog.pg_class AS target ON target.oid = constraint_.confrelid
|
|
171
|
+
JOIN pg_catalog.pg_namespace AS target_namespace ON target_namespace.oid = target.relnamespace
|
|
172
|
+
WHERE constraint_.contype = 'f' AND namespace.nspname = ${schemaName} AND relation.relname = ANY(${param(t.array(t.text), names)})
|
|
173
|
+
ORDER BY constraint_.conname
|
|
174
|
+
`.select({
|
|
175
|
+
table_name: t.text,
|
|
176
|
+
name: t.text,
|
|
177
|
+
target_name: t.text,
|
|
178
|
+
target_schema: t.text,
|
|
179
|
+
from_columns: t.array(t.text),
|
|
180
|
+
to_columns: t.array(t.text),
|
|
181
|
+
is_deferrable: t.boolean,
|
|
182
|
+
is_validated: t.boolean,
|
|
183
|
+
is_enforced: t.boolean,
|
|
184
|
+
is_period: t.boolean
|
|
185
|
+
});
|
|
186
|
+
const foreignKeys = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const row of rows) addToGroup(foreignKeys, row.table_name, {
|
|
188
|
+
name: row.name,
|
|
189
|
+
target: row.target_name,
|
|
190
|
+
targetSchema: row.target_schema,
|
|
191
|
+
from: row.from_columns,
|
|
192
|
+
to: row.to_columns,
|
|
193
|
+
deferrable: row.is_deferrable,
|
|
194
|
+
validated: row.is_validated,
|
|
195
|
+
enforced: row.is_enforced,
|
|
196
|
+
period: row.is_period
|
|
197
|
+
});
|
|
198
|
+
return foreignKeys;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Resolves `sqlTypes` in the search path of the connection, as the casts of queries do, to `format_type` of their types and type modifiers, or to `null` for a type that does not exist.
|
|
202
|
+
*
|
|
203
|
+
* `format_type` gives one text of each type and modifier, qualified when the search path does not find the type, so that aliases, such as `int4` and `integer`, give the same text.
|
|
204
|
+
*/
|
|
205
|
+
async function resolveTypes(database, sqlTypes) {
|
|
206
|
+
const rows = await database.query`
|
|
207
|
+
SELECT sql_type, pg_catalog.format_type(to_regtype(sql_type), to_regtypemod(sql_type)) AS type
|
|
208
|
+
FROM unnest(${param(t.array(t.text), sqlTypes)}) AS sql_type
|
|
209
|
+
`.select({
|
|
210
|
+
sql_type: t.text,
|
|
211
|
+
type: t.text.nullable()
|
|
212
|
+
});
|
|
213
|
+
const types = /* @__PURE__ */ new Map();
|
|
214
|
+
for (const row of rows) types.set(row.sql_type, row.type);
|
|
215
|
+
return types;
|
|
216
|
+
}
|
|
217
|
+
/** Returns the problems of `table` in `catalog`, such as a missing column. */
|
|
218
|
+
function findTableProblems(table, catalog) {
|
|
219
|
+
const relation = catalog.relations.get(table.name);
|
|
220
|
+
if (relation === void 0) return [`Table "${table.name}" does not exist in schema "${catalog.schemaName}"`];
|
|
221
|
+
const isView = isViewKind(relation.kind);
|
|
222
|
+
if (!isView && !isTableKind(relation.kind)) return [`"${table.name}" in schema "${catalog.schemaName}" is not a table or a view`];
|
|
223
|
+
const problems = [];
|
|
224
|
+
const definedNames = /* @__PURE__ */ new Set();
|
|
225
|
+
for (const key of Object.keys(table.columns)) {
|
|
226
|
+
const column = table.columns[key];
|
|
227
|
+
const name = getColumnName(column, key);
|
|
228
|
+
definedNames.add(name);
|
|
229
|
+
const subject = formatColumn(key, name);
|
|
230
|
+
const actual = relation.columns.get(name);
|
|
231
|
+
if (actual === void 0) {
|
|
232
|
+
problems.push(`${subject} does not exist`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const expectedType = catalog.types.get(column.type.sqlType);
|
|
236
|
+
if (expectedType === null) problems.push(`${subject} is of type "${column.type.sqlType}", which does not exist`);
|
|
237
|
+
else if (expectedType !== actual.type) problems.push(`${subject} is of type ${actual.type} rather than ${expectedType}`);
|
|
238
|
+
if (isView) continue;
|
|
239
|
+
if (column.type.isNullable && actual.rejectsNull) problems.push(`${subject} is NOT NULL, but the table defines it as nullable`);
|
|
240
|
+
else if (!column.type.isNullable && !actual.notNullValidated) problems.push(actual.rejectsNull ? `${subject} has a NOT VALID not-null constraint, so existing rows may hold NULL, but the table defines it as not nullable` : `${subject} is nullable, but the table defines it as not nullable`);
|
|
241
|
+
if (column.attributes.hasDefault && !actual.hasDefault) problems.push(`${subject} has no default, but the table marks it with hasDefault()`);
|
|
242
|
+
}
|
|
243
|
+
if (isView) return problems;
|
|
244
|
+
for (const [name, actual] of relation.columns) if (!definedNames.has(name) && actual.rejectsNull && !actual.hasDefault) problems.push(`Column "${name}" is NOT NULL without a default, so every insert of the table, which does not define it, fails`);
|
|
245
|
+
const indexes = catalog.indexes.get(table.name) ?? [];
|
|
246
|
+
const { primaryKey, unique } = table.keys;
|
|
247
|
+
if (primaryKey !== void 0) pushDefined(problems, findKeyProblem(table, "Primary key", primaryKey, indexes));
|
|
248
|
+
for (const key of unique) pushDefined(problems, findKeyProblem(table, "Unique key", key, indexes));
|
|
249
|
+
if (table.singleton) pushDefined(problems, findArbiterProblem("Singleton table", "unique index of (true)", indexes.filter((index) => index.expression === "(true)")));
|
|
250
|
+
return problems;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Returns the problem of key `columns` of `table`, or `undefined` when a unique index backs it.
|
|
254
|
+
*
|
|
255
|
+
* The indexes are those that PostgreSQL infers as the arbiters of `ON CONFLICT` of the columns of the key, which `onConflict` and `upsert` build: of the same set of columns, in any order, without expressions or a predicate, and valid.
|
|
256
|
+
* One of them must exist, and none may be deferrable, since PostgreSQL rejects the insert of a deferrable arbiter.
|
|
257
|
+
*/
|
|
258
|
+
function findKeyProblem(table, kind, columns, indexes) {
|
|
259
|
+
const names = new Set(columns.map((key) => getColumnName(table.columns[key], key)));
|
|
260
|
+
const matching = indexes.filter((index) => {
|
|
261
|
+
const indexColumns = new Set(index.columns);
|
|
262
|
+
return !index.hasExpressions && indexColumns.size === names.size && index.columns.every((name) => names.has(name));
|
|
263
|
+
});
|
|
264
|
+
return findArbiterProblem(`${kind} (${columns.join(", ")})`, "unique index", matching);
|
|
265
|
+
}
|
|
266
|
+
/** Returns the problem of `subject` when the indexes of `matching` cannot serve as its arbiters of `ON CONFLICT`, or `undefined` when they can. */
|
|
267
|
+
function findArbiterProblem(subject, description, matching) {
|
|
268
|
+
const arbiters = matching.filter((index) => index.valid && !index.partial);
|
|
269
|
+
const deferrable = arbiters.find((index) => !index.immediate);
|
|
270
|
+
if (deferrable !== void 0) return `${subject} has deferrable ${description} "${deferrable.name}", which ON CONFLICT rejects as an arbiter`;
|
|
271
|
+
if (arbiters.length > 0) return;
|
|
272
|
+
if (matching.length === 0) return `${subject} has no ${description}`;
|
|
273
|
+
const index = matching[0];
|
|
274
|
+
const flaws = [];
|
|
275
|
+
if (index.partial) flaws.push("partial");
|
|
276
|
+
if (!index.valid) flaws.push("not valid");
|
|
277
|
+
return `${subject} has no usable ${description}: "${index.name}" is ${flaws.join(" and ")}`;
|
|
278
|
+
}
|
|
279
|
+
/** Returns the problem of a required relation `one` of `definition` to a table, or `undefined` when a foreign key backs it or the relation is not checked. */
|
|
280
|
+
function findRelationProblem(definition, catalog) {
|
|
281
|
+
if (definition.kind !== "one" || !definition.required) return;
|
|
282
|
+
const { source, target } = definition;
|
|
283
|
+
for (const table of [source, target]) {
|
|
284
|
+
const relation = catalog.relations.get(table.name);
|
|
285
|
+
if (relation === void 0 || !isTableKind(relation.kind)) return;
|
|
286
|
+
}
|
|
287
|
+
const pairs = /* @__PURE__ */ new Set();
|
|
288
|
+
for (let index = 0; index < definition.from.length; index++) {
|
|
289
|
+
const fromKey = definition.from[index];
|
|
290
|
+
const toKey = definition.to[index];
|
|
291
|
+
pairs.add(formatPair(getColumnName(source.columns[fromKey], fromKey), getColumnName(target.columns[toKey], toKey)));
|
|
292
|
+
}
|
|
293
|
+
const matching = (catalog.foreignKeys.get(source.name) ?? []).filter((foreignKey) => foreignKey.target === target.name && foreignKey.targetSchema === catalog.schemaName && foreignKey.from.length === pairs.size && foreignKey.from.every((name, index) => pairs.has(formatPair(name, foreignKey.to[index]))));
|
|
294
|
+
const subject = `Required relation ${formatRelationName(definition.key, definition.name)}`;
|
|
295
|
+
if (matching.some((foreignKey) => !foreignKey.deferrable && foreignKey.validated && foreignKey.enforced && !foreignKey.period)) return;
|
|
296
|
+
if (matching.length === 0) return `${subject} has no foreign key of its columns`;
|
|
297
|
+
const foreignKey = matching[0];
|
|
298
|
+
const flaws = [];
|
|
299
|
+
if (foreignKey.deferrable) flaws.push("deferrable");
|
|
300
|
+
if (!foreignKey.validated) flaws.push("not valid");
|
|
301
|
+
if (!foreignKey.enforced) flaws.push("not enforced");
|
|
302
|
+
if (foreignKey.period) flaws.push("of a period");
|
|
303
|
+
return `${subject} has no usable foreign key of its columns: "${foreignKey.name}" is ${flaws.join(" and ")}`;
|
|
304
|
+
}
|
|
305
|
+
/** Returns the physical name of `column` under `key`. */
|
|
306
|
+
function getColumnName(column, key) {
|
|
307
|
+
return column.attributes.name ?? key;
|
|
308
|
+
}
|
|
309
|
+
/** Returns the subject of a message of the column of `key`, with its physical name when it differs. */
|
|
310
|
+
function formatColumn(key, name) {
|
|
311
|
+
return key === name ? `Column "${key}"` : `Column "${key}" (${name})`;
|
|
312
|
+
}
|
|
313
|
+
/** Returns a key of a pair of the names of a source column and a target column; a name cannot contain a NUL character. */
|
|
314
|
+
function formatPair(from, to) {
|
|
315
|
+
return `${from}\0${to}`;
|
|
316
|
+
}
|
|
317
|
+
/** Whether `relkind` is of a table: ordinary, partitioned, or foreign. */
|
|
318
|
+
function isTableKind(kind) {
|
|
319
|
+
return kind === "r" || kind === "p" || kind === "f";
|
|
320
|
+
}
|
|
321
|
+
/** Whether `relkind` is of a view or a materialized view. */
|
|
322
|
+
function isViewKind(kind) {
|
|
323
|
+
return kind === "v" || kind === "m";
|
|
324
|
+
}
|
|
325
|
+
function addToGroup(groups, key, item) {
|
|
326
|
+
const group = groups.get(key);
|
|
327
|
+
if (group === void 0) groups.set(key, [item]);
|
|
328
|
+
else group.push(item);
|
|
329
|
+
}
|
|
330
|
+
function pushDefined(problems, problem) {
|
|
331
|
+
if (problem !== void 0) problems.push(problem);
|
|
332
|
+
}
|
|
333
|
+
export { findTableMismatches };
|
package/dist/types.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $i as BaseType, ct as TextTypeOperators, da as MergeOperators, ea as Type, fa as NoOperators, fi as EqualityOperator, ft as AnyExpression, hi as InSignature, ma as GenericOperator, mi as HasEquality, oa as Column, pa as OperatorDefinitions, pi as EqualityOperators, st as TextOperators, ut as NumberTypeOperators, vi as ComparisonSignature } from "./index-
|
|
1
|
+
import { $i as BaseType, ct as TextTypeOperators, da as MergeOperators, ea as Type, fa as NoOperators, fi as EqualityOperator, ft as AnyExpression, hi as InSignature, ma as GenericOperator, mi as HasEquality, oa as Column, pa as OperatorDefinitions, pi as EqualityOperators, st as TextOperators, ut as NumberTypeOperators, vi as ComparisonSignature } from "./index-DD_wYhRD.mjs";
|
|
2
2
|
/**
|
|
3
3
|
* Creates a one-dimensional array type of `element` values.
|
|
4
4
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kern-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Fully typed relational query builder for PostgreSQL with ambient transactions and single-query relation loading",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orm",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"prepublishOnly": "bun run build"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"kern-db": "^0.6.
|
|
48
|
+
"kern-db": "^0.6.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@kern-orm/test-harness": "workspace:*",
|