@ultimabase/postgres 0.0.0-stage → 0.1.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/CHANGELOG.md +12 -0
- package/README.md +4 -2
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +254 -0
- package/dist/aggregate.d.ts +8 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +138 -0
- package/dist/decode.d.ts +5 -0
- package/dist/decode.d.ts.map +1 -0
- package/dist/decode.js +30 -0
- package/dist/fills.d.ts +16 -0
- package/dist/fills.d.ts.map +1 -0
- package/dist/fills.js +37 -0
- package/dist/filter.d.ts +50 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +247 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/json-filter.d.ts +12 -0
- package/dist/json-filter.d.ts.map +1 -0
- package/dist/json-filter.js +65 -0
- package/dist/listen.d.ts +10 -0
- package/dist/listen.d.ts.map +1 -0
- package/dist/listen.js +92 -0
- package/dist/logic.d.ts +7 -0
- package/dist/logic.d.ts.map +1 -0
- package/dist/logic.js +18 -0
- package/dist/migrations.d.ts +23 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +217 -0
- package/dist/names.d.ts +66 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +175 -0
- package/dist/notify.d.ts +19 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +75 -0
- package/dist/numbers.d.ts +13 -0
- package/dist/numbers.d.ts.map +1 -0
- package/dist/numbers.js +49 -0
- package/dist/parts.d.ts +36 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +99 -0
- package/dist/plan-indexes.d.ts +12 -0
- package/dist/plan-indexes.d.ts.map +1 -0
- package/dist/plan-indexes.js +49 -0
- package/dist/plan-parts.d.ts +16 -0
- package/dist/plan-parts.d.ts.map +1 -0
- package/dist/plan-parts.js +9 -0
- package/dist/plan.d.ts +6 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +278 -0
- package/dist/pool.d.ts +5 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +41 -0
- package/dist/read.d.ts +47 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +227 -0
- package/dist/scratch.d.ts +22 -0
- package/dist/scratch.d.ts.map +1 -0
- package/dist/scratch.js +77 -0
- package/dist/snapshot.d.ts +66 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +272 -0
- package/dist/write.d.ts +17 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +296 -0
- package/package.json +45 -3
- package/src/adapter.ts +349 -0
- package/src/aggregate.ts +168 -0
- package/src/decode.ts +46 -0
- package/src/fills.ts +52 -0
- package/src/filter.ts +306 -0
- package/src/index.ts +20 -0
- package/src/json-filter.ts +74 -0
- package/src/listen.ts +97 -0
- package/src/logic.ts +28 -0
- package/src/migrations.ts +275 -0
- package/src/names.ts +217 -0
- package/src/notify.ts +101 -0
- package/src/numbers.ts +64 -0
- package/src/parts.ts +143 -0
- package/src/plan-indexes.ts +100 -0
- package/src/plan-parts.ts +35 -0
- package/src/plan.ts +389 -0
- package/src/pool.ts +63 -0
- package/src/read.ts +287 -0
- package/src/scratch.ts +92 -0
- package/src/snapshot.ts +390 -0
- package/src/write.ts +384 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../src/parts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAQ,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAKxC,gDAAgD;AAChD,wBAAgB,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAGzC;AAED,sEAAsE;AACtE,MAAM,MAAM,IAAI,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,GAAG,SAAS,KAAK,GAAG,CAAA;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACrB,CAAA;AAmBD,eAAO,MAAM,QAAQ,QAAS;IAC7B,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CACjC,KAAG,MAAM,EAAgE,CAAA;AAE1E,yEAAyE;AACzE,wBAAgB,QAAQ,CACvB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,GAAG,EAAE,GAClB,IAAI,CAoCN;AAeD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,CAwBtE"}
|
package/dist/parts.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The changes of a write as CTEs, and how they split into statements (D-99).
|
|
2
|
+
import { createQueryId, PostgresQueryCompiler, sql } from 'kysely';
|
|
3
|
+
import { value } from './filter.js';
|
|
4
|
+
import { rows as noted } from './notify.js';
|
|
5
|
+
const compiler = new PostgresQueryCompiler();
|
|
6
|
+
/** How many parameters a piece of SQL binds. */
|
|
7
|
+
export function sizeOf(piece) {
|
|
8
|
+
return compiler.compileQuery(piece.toOperationNode(), createQueryId())
|
|
9
|
+
.parameters.length;
|
|
10
|
+
}
|
|
11
|
+
/** A column's type, which a row of VALUES under a SELECT does not infer. */
|
|
12
|
+
const types = {
|
|
13
|
+
id: 'uuid',
|
|
14
|
+
string: 'text',
|
|
15
|
+
enum: 'text',
|
|
16
|
+
number: 'double precision',
|
|
17
|
+
decimal: 'numeric',
|
|
18
|
+
integer: 'integer',
|
|
19
|
+
boolean: 'boolean',
|
|
20
|
+
dateTime: 'timestamptz',
|
|
21
|
+
date: 'date',
|
|
22
|
+
json: 'jsonb',
|
|
23
|
+
'text[]': 'text[]',
|
|
24
|
+
'double precision[]': 'double precision[]',
|
|
25
|
+
'integer[]': 'integer[]',
|
|
26
|
+
};
|
|
27
|
+
export const fieldsOf = (row) => Object.keys(row).filter((name) => row[name] !== undefined);
|
|
28
|
+
/** Rows inserted into a table, which all set the fields of the first. */
|
|
29
|
+
export function inserted(catalog, model, rows) {
|
|
30
|
+
const table = catalog.table(model);
|
|
31
|
+
const name = sql.id(table.name);
|
|
32
|
+
const fields = fieldsOf(rows[0] ?? {});
|
|
33
|
+
const columns = sql.join(fields.map((field) => sql.id(catalog.column(table, field))));
|
|
34
|
+
const tuples = (typed) => sql.join(rows.map((row, at) => {
|
|
35
|
+
const values = fields.map((field) => {
|
|
36
|
+
const kind = catalog.kind(table, field);
|
|
37
|
+
const item = value(kind, row[field]);
|
|
38
|
+
// The first row's types are the other rows'.
|
|
39
|
+
return typed && at === 0
|
|
40
|
+
? sql `cast(${item} as ${sql.raw(types[kind])})`
|
|
41
|
+
: item;
|
|
42
|
+
});
|
|
43
|
+
return sql `(${sql.join(values)})`;
|
|
44
|
+
}));
|
|
45
|
+
const [only] = rows;
|
|
46
|
+
return {
|
|
47
|
+
table: table.name,
|
|
48
|
+
key: ['id'],
|
|
49
|
+
...(rows.length === 1 && only !== undefined ? { row: only.id } : {}),
|
|
50
|
+
build: (gate) => gate === undefined
|
|
51
|
+
? sql `insert into ${name} (${columns}) values ${tuples(false)} returning *`
|
|
52
|
+
: sql `insert into ${name} (${columns}) select * from (values ${tuples(true)}) as "v" where ${gate} returning *`,
|
|
53
|
+
inserts: true,
|
|
54
|
+
removes: false,
|
|
55
|
+
adds: true,
|
|
56
|
+
reads: new Set(),
|
|
57
|
+
noted: noted('insert', model),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Whether `each` may join a statement that already wrote `prior`. */
|
|
61
|
+
function fits(prior, each) {
|
|
62
|
+
if (prior === undefined)
|
|
63
|
+
return true;
|
|
64
|
+
// A join table's changes of distinct pairs never meet.
|
|
65
|
+
if (each.pairs !== undefined) {
|
|
66
|
+
return [...each.pairs].every((pair) => !prior.pairs.has(pair));
|
|
67
|
+
}
|
|
68
|
+
return prior.inserts && each.inserts;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The parts of one statement each: a part starts a new one when it writes a
|
|
72
|
+
* table the statement writes (inserts, and a join table's distinct pairs,
|
|
73
|
+
* aside) or reads one, since the parts of a statement see the database as it
|
|
74
|
+
* was, or when its parameters would not fit in the `room` left.
|
|
75
|
+
*/
|
|
76
|
+
export function grouped(parts, room) {
|
|
77
|
+
const groups = [[]];
|
|
78
|
+
const written = new Map();
|
|
79
|
+
let used = 0;
|
|
80
|
+
for (const each of parts) {
|
|
81
|
+
const size = sizeOf(each.build(undefined));
|
|
82
|
+
const clash = !fits(written.get(each.table), each) ||
|
|
83
|
+
[...each.reads].some((table) => written.has(table)) ||
|
|
84
|
+
(used > 0 && used + size > room);
|
|
85
|
+
if (clash) {
|
|
86
|
+
groups.push([]);
|
|
87
|
+
written.clear();
|
|
88
|
+
used = 0;
|
|
89
|
+
}
|
|
90
|
+
used += size;
|
|
91
|
+
groups.at(-1)?.push(each);
|
|
92
|
+
const prior = written.get(each.table);
|
|
93
|
+
written.set(each.table, {
|
|
94
|
+
inserts: (prior?.inserts ?? true) && each.inserts,
|
|
95
|
+
pairs: new Set([...(prior?.pairs ?? []), ...(each.pairs ?? [])]),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return groups;
|
|
99
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Emit } from './plan-parts.ts';
|
|
2
|
+
import type { TableSnapshot } from './snapshot.ts';
|
|
3
|
+
/** Drops the indexes of `from` that `to` changes. */
|
|
4
|
+
export declare function dropIndexes(name: string, from: TableSnapshot, to: TableSnapshot, emit: Emit): void;
|
|
5
|
+
/** Whether `to` adds a trigram index `from` lacks. */
|
|
6
|
+
export declare const addsTrigrams: (from: TableSnapshot | undefined, to: TableSnapshot) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Creates the indexes `table` adds to `was`. A trigram index reads the
|
|
9
|
+
* column under the database's collation, as `contains` does (D-141).
|
|
10
|
+
*/
|
|
11
|
+
export declare function addIndexes(name: string, was: TableSnapshot | undefined, table: TableSnapshot, emit: Emit): void;
|
|
12
|
+
//# sourceMappingURL=plan-indexes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-indexes.d.ts","sourceRoot":"","sources":["../src/plan-indexes.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,iBAAiB,CAAA;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAkBlD,qDAAqD;AACrD,wBAAgB,WAAW,CAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,EACnB,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,IAAI,GACR,IAAI,CAoBN;AAED,sDAAsD;AACtD,eAAO,MAAM,YAAY,SAClB,aAAa,GAAG,SAAS,MAC3B,aAAa,KACf,OAA8D,CAAA;AAEjE;;;GAGG;AACH,wBAAgB,UAAU,CACzB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,aAAa,GAAG,SAAS,EAC9B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,IAAI,GACR,IAAI,CAgCN"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The indexes a plan drops and creates: B-tree, GIN on a list (D-120),
|
|
2
|
+
// trigram for `contains` (D-141), and the `_search` column with its GIN
|
|
3
|
+
// index (D-148).
|
|
4
|
+
import { literal } from './fills.js';
|
|
5
|
+
import { added, changed, fields, id, list, same } from './plan-parts.js';
|
|
6
|
+
const indexesOf = (table) => Object.fromEntries(Object.entries(table?.indexes ?? {}).map(([key, columns]) => [
|
|
7
|
+
key,
|
|
8
|
+
{
|
|
9
|
+
columns,
|
|
10
|
+
gin: columns.some((name) => table?.columns[name]?.type.endsWith('[]') === true),
|
|
11
|
+
},
|
|
12
|
+
]));
|
|
13
|
+
/** Drops the indexes of `from` that `to` changes. */
|
|
14
|
+
export function dropIndexes(name, from, to, emit) {
|
|
15
|
+
for (const [key, { columns }] of changed(indexesOf(from), indexesOf(to))) {
|
|
16
|
+
emit(`drop index ${id(key)}`, `dropped the index on ${fields(from, columns)}`);
|
|
17
|
+
}
|
|
18
|
+
for (const [key, column] of changed(from.trigrams, to.trigrams)) {
|
|
19
|
+
emit(`drop index ${id(key)}`, `dropped the contains index on ${fields(from, [column])}`);
|
|
20
|
+
}
|
|
21
|
+
// A generated column holds nothing of its own: a change makes it again.
|
|
22
|
+
if (from.search !== null && !same(from.search, to.search)) {
|
|
23
|
+
emit(`alter table ${id(name)} drop column "_search"`, `dropped the search on ${fields(from, from.search.columns)}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Whether `to` adds a trigram index `from` lacks. */
|
|
27
|
+
export const addsTrigrams = (from, to) => added(from?.trigrams ?? {}, to.trigrams).length > 0;
|
|
28
|
+
/**
|
|
29
|
+
* Creates the indexes `table` adds to `was`. A trigram index reads the
|
|
30
|
+
* column under the database's collation, as `contains` does (D-141).
|
|
31
|
+
*/
|
|
32
|
+
export function addIndexes(name, was, table, emit) {
|
|
33
|
+
for (const [key, { columns, gin }] of added(indexesOf(was), indexesOf(table))) {
|
|
34
|
+
emit(`create index ${id(key)} on ${id(name)}${gin ? ' using gin' : ''} (${list(columns)})`, `indexed ${fields(table, columns)}`);
|
|
35
|
+
}
|
|
36
|
+
for (const [key, column] of added(was?.trigrams ?? {}, table.trigrams)) {
|
|
37
|
+
emit(`create index ${id(key)} on ${id(name)} using gin ((${id(column)} collate "default") gin_trgm_ops)`, `indexed ${fields(table, [column])} for contains`);
|
|
38
|
+
}
|
|
39
|
+
const { search } = table;
|
|
40
|
+
if (search === null || (was !== undefined && same(was.search, search))) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const text = search.columns
|
|
44
|
+
.map((column) => `coalesce(${id(column)}, '')`)
|
|
45
|
+
.join(` || ' ' || `);
|
|
46
|
+
const searched = fields(table, search.columns);
|
|
47
|
+
emit(`alter table ${id(name)} add column "_search" tsvector generated always as (to_tsvector(${literal(search.language)}::regconfig, ${text})) stored`, `added the search on ${searched}`);
|
|
48
|
+
emit(`create index ${id(search.index)} on ${id(name)} using gin ("_search")`, `indexed the search on ${searched}`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Statement } from 'ultimabase';
|
|
2
|
+
import type { TableSnapshot } from './snapshot.ts';
|
|
3
|
+
export declare const id: (name: string) => string;
|
|
4
|
+
export declare const list: (names: readonly string[]) => string;
|
|
5
|
+
export declare const same: (a: unknown, b: unknown) => boolean;
|
|
6
|
+
export type Named<T> = {
|
|
7
|
+
readonly [name: string]: T;
|
|
8
|
+
};
|
|
9
|
+
export type Emit = (sql: string, does: string, more?: Pick<Statement, 'loses' | 'tries' | 'rename'>) => void;
|
|
10
|
+
/** The entries of `from` that `to` lacks or defines otherwise. */
|
|
11
|
+
export declare const changed: <T>(from: Named<T>, to: Named<T>) => [string, T][];
|
|
12
|
+
/** The entries of `to` that `from` lacks or defines otherwise. */
|
|
13
|
+
export declare const added: <T>(from: Named<T>, to: Named<T>) => [string, T][];
|
|
14
|
+
/** The model's names for columns, which messages use. */
|
|
15
|
+
export declare const fields: (table: TableSnapshot, columns: readonly string[]) => string;
|
|
16
|
+
//# sourceMappingURL=plan-parts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-parts.d.ts","sourceRoot":"","sources":["../src/plan-parts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD,eAAO,MAAM,EAAE,SAAU,MAAM,KAAG,MAA2C,CAAA;AAE7E,eAAO,MAAM,IAAI,UAAW,SAAS,MAAM,EAAE,KAAG,MACvB,CAAA;AAEzB,eAAO,MAAM,IAAI,MAAO,OAAO,KAAK,OAAO,KAAG,OACN,CAAA;AAExC,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI;IAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAA;CAAE,CAAA;AAErD,MAAM,MAAM,IAAI,GAAG,CAClB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC,KAChD,IAAI,CAAA;AAET,kEAAkE;AAClE,eAAO,MAAM,OAAO,GAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,KAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EACE,CAAA;AAEvE,kEAAkE;AAClE,eAAO,MAAM,KAAK,GAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,KAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EACjD,CAAA;AAElB,yDAAyD;AACzD,eAAO,MAAM,MAAM,UACX,aAAa,WACX,SAAS,MAAM,EAAE,KACxB,MACwE,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const id = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
2
|
+
export const list = (names) => names.map(id).join(', ');
|
|
3
|
+
export const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
4
|
+
/** The entries of `from` that `to` lacks or defines otherwise. */
|
|
5
|
+
export const changed = (from, to) => Object.entries(from).filter(([name, value]) => !same(value, to[name]));
|
|
6
|
+
/** The entries of `to` that `from` lacks or defines otherwise. */
|
|
7
|
+
export const added = (from, to) => changed(to, from);
|
|
8
|
+
/** The model's names for columns, which messages use. */
|
|
9
|
+
export const fields = (table, columns) => columns.map((column) => table.columns[column]?.field ?? column).join(', ');
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Statement } from 'ultimabase';
|
|
2
|
+
import type { Fills } from './fills.ts';
|
|
3
|
+
import type { Snapshot } from './snapshot.ts';
|
|
4
|
+
/** Every statement from `from`, or an empty database, to `to`. */
|
|
5
|
+
export declare function plan(from: Snapshot | undefined, to: Snapshot, fills: Fills): Statement[];
|
|
6
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAQ,KAAK,EAAE,MAAM,YAAY,CAAA;AAK7C,OAAO,KAAK,EAAqB,QAAQ,EAAiB,MAAM,eAAe,CAAA;AA4S/E,kEAAkE;AAClE,wBAAgB,IAAI,CACnB,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,KAAK,GACV,SAAS,EAAE,CAyEb"}
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { literal } from './fills.js';
|
|
2
|
+
import { addIndexes, addsTrigrams, dropIndexes } from './plan-indexes.js';
|
|
3
|
+
import { added, changed, fields, id, list, same } from './plan-parts.js';
|
|
4
|
+
/** A key's columns as a tried change names them: `Locker (label, size)`. */
|
|
5
|
+
const keyOf = (table, columns) => `${table.model} (${fields(table, columns)})`;
|
|
6
|
+
/** An enum's values; a list's elements are among them, with `<@` (D-127). */
|
|
7
|
+
function checked(name, { column, values, list: all }) {
|
|
8
|
+
const items = values.map(literal).join(', ');
|
|
9
|
+
const test = all ? `<@ array[${items}]::text[]` : `in (${items})`;
|
|
10
|
+
return `constraint ${id(name)} check (${id(column)} ${test})`;
|
|
11
|
+
}
|
|
12
|
+
/** A type as messages show it, without its collation. */
|
|
13
|
+
const shown = (type) => type.replace(' collate "C"', '');
|
|
14
|
+
const column = (name, type, nullable) => `${id(name)} ${type}${nullable ? '' : ' not null'}`;
|
|
15
|
+
/**
|
|
16
|
+
* Columns as a rename compares them: names, types and nullability, sorted,
|
|
17
|
+
* because a stored snapshot's columns come back in jsonb's order.
|
|
18
|
+
*/
|
|
19
|
+
const shape = (table) => Object.entries(table.columns)
|
|
20
|
+
.map(([name, { type, nullable }]) => [name, type, nullable])
|
|
21
|
+
.toSorted(([a], [b]) => String(a).localeCompare(String(b)));
|
|
22
|
+
function create(name, table) {
|
|
23
|
+
const parts = [
|
|
24
|
+
...Object.entries(table.columns).map(([key, { type, nullable }]) => column(key, type, nullable)),
|
|
25
|
+
`constraint ${id(table.primaryKey.name)} primary key (${list(table.primaryKey.columns)})`,
|
|
26
|
+
...Object.entries(table.uniques).map(([key, columns]) => `constraint ${id(key)} unique (${list(columns)})`),
|
|
27
|
+
...Object.entries(table.checks).map(([key, given]) => checked(key, given)),
|
|
28
|
+
];
|
|
29
|
+
return `create table ${id(name)} (\n\t${parts.join(',\n\t')}\n)`;
|
|
30
|
+
}
|
|
31
|
+
/** Drops the keys, checks and indexes of `from` that `to` changes. */
|
|
32
|
+
function dropKeys(name, from, to, emit) {
|
|
33
|
+
const drop = (key) => `alter table ${id(name)} drop constraint ${id(key)}`;
|
|
34
|
+
if (!same(from.primaryKey, to.primaryKey)) {
|
|
35
|
+
emit(drop(from.primaryKey.name), 'dropped the primary key');
|
|
36
|
+
}
|
|
37
|
+
for (const [key, columns] of changed(from.uniques, to.uniques)) {
|
|
38
|
+
emit(drop(key), `dropped the unique key on ${fields(from, columns)}`);
|
|
39
|
+
}
|
|
40
|
+
for (const [key, { column: on }] of changed(from.checks, to.checks)) {
|
|
41
|
+
emit(drop(key), `dropped the values of ${fields(from, [on])}`);
|
|
42
|
+
}
|
|
43
|
+
dropIndexes(name, from, to, emit);
|
|
44
|
+
}
|
|
45
|
+
/** Adds a column; a required one takes its fixed default in the rows. */
|
|
46
|
+
function addColumn(table, key, now, model, fill, emit) {
|
|
47
|
+
const add = `${table} add column ${column(key, now.type, now.nullable)}`;
|
|
48
|
+
if (now.nullable)
|
|
49
|
+
return emit(add, `added ${now.field}`);
|
|
50
|
+
if (fill === undefined) {
|
|
51
|
+
return emit(add, `added ${now.field}`, {
|
|
52
|
+
tries: `adding the required "${model}.${now.field}" fails when ${model} holds rows: make it optional, or give it a value as its default, not a function`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// The rows take the default; the column keeps none, as the models say.
|
|
56
|
+
emit(`${add} default ${fill.sql}`, `added ${now.field}`);
|
|
57
|
+
emit(`${table} alter column ${id(key)} drop default`, `${fill.shown} in the rows it held`);
|
|
58
|
+
}
|
|
59
|
+
/** The types PostgreSQL converts no value of a type to, on PG 15 to 18. */
|
|
60
|
+
const arrays = ['text[]', 'double precision[]', 'integer[]'];
|
|
61
|
+
const scalars = ['boolean', 'timestamptz', 'date', 'jsonb'];
|
|
62
|
+
const numbers = ['double precision', 'numeric', 'integer'];
|
|
63
|
+
const unconvertible = {
|
|
64
|
+
'double precision': ['boolean', 'timestamptz', 'date', 'jsonb', ...arrays],
|
|
65
|
+
numeric: ['boolean', 'timestamptz', 'date', 'jsonb', ...arrays],
|
|
66
|
+
integer: ['timestamptz', 'date', 'jsonb', ...arrays],
|
|
67
|
+
boolean: [
|
|
68
|
+
'double precision',
|
|
69
|
+
'numeric',
|
|
70
|
+
'timestamptz',
|
|
71
|
+
'date',
|
|
72
|
+
'jsonb',
|
|
73
|
+
...arrays,
|
|
74
|
+
],
|
|
75
|
+
date: [...numbers, 'boolean', 'jsonb', ...arrays],
|
|
76
|
+
timestamptz: [
|
|
77
|
+
'double precision',
|
|
78
|
+
'numeric',
|
|
79
|
+
'integer',
|
|
80
|
+
'boolean',
|
|
81
|
+
'jsonb',
|
|
82
|
+
...arrays,
|
|
83
|
+
],
|
|
84
|
+
jsonb: ['timestamptz', 'date', ...arrays],
|
|
85
|
+
'text[]': [...numbers, ...scalars],
|
|
86
|
+
'double precision[]': [...numbers, ...scalars],
|
|
87
|
+
'integer[]': [...numbers, ...scalars],
|
|
88
|
+
};
|
|
89
|
+
/** A type without its precision and scale: `numeric(10,2)` is `numeric`. */
|
|
90
|
+
const family = (type) => type.startsWith('numeric(') ? 'numeric' : type;
|
|
91
|
+
/**
|
|
92
|
+
* Whether converting reads each value: text or json to another type,
|
|
93
|
+
* anything but a boolean to whole numbers, which refuse a fraction (D-132),
|
|
94
|
+
* anything to a decimal, which refuses more integer digits (D-145).
|
|
95
|
+
*/
|
|
96
|
+
const reads = (from, to) => to.startsWith('integer')
|
|
97
|
+
? from !== 'boolean'
|
|
98
|
+
: to.startsWith('numeric') ||
|
|
99
|
+
((from === 'jsonb' || from.startsWith('text')) &&
|
|
100
|
+
!to.startsWith('text collate'));
|
|
101
|
+
/**
|
|
102
|
+
* A date as text is `toISOString()`'s, whatever the session's time zone; a
|
|
103
|
+
* day is `YYYY-MM-DD` whatever its `DateStyle`, and an instant's day is UTC's.
|
|
104
|
+
* Whole numbers come from text, which refuses `1.5` that a cast rounds; a
|
|
105
|
+
* decimal's text through float8, as `12.00` is `12`.
|
|
106
|
+
*/
|
|
107
|
+
function converted(named, from, to) {
|
|
108
|
+
if (from === 'timestamptz' && to.startsWith('text')) {
|
|
109
|
+
return `to_char(${named} at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
110
|
+
}
|
|
111
|
+
if (from === 'date' && to.startsWith('text')) {
|
|
112
|
+
return `to_char(${named}, 'YYYY-MM-DD')`;
|
|
113
|
+
}
|
|
114
|
+
if (from === 'timestamptz' && to === 'date') {
|
|
115
|
+
return `(${named} at time zone 'UTC')::date`;
|
|
116
|
+
}
|
|
117
|
+
if (from === 'date' && to === 'timestamptz') {
|
|
118
|
+
return `(${named}::timestamp at time zone 'UTC')`;
|
|
119
|
+
}
|
|
120
|
+
const whole = to.startsWith('integer') && reads(from, to);
|
|
121
|
+
const via = from.startsWith('numeric') ? 'float8::' : '';
|
|
122
|
+
return `${named}::${whole ? `${via}text::` : ''}${to}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Changes a column's type. One PostgreSQL cannot convert loses its values,
|
|
126
|
+
* as an added column has none: null, or its fixed default when required.
|
|
127
|
+
*/
|
|
128
|
+
function retype(table, key, [was, now], model, fill, emit) {
|
|
129
|
+
const change = `changing "${model}.${now.field}" from ${shown(was)} to ${shown(now.type)}`;
|
|
130
|
+
const alter = `${table} alter column ${id(key)} type ${now.type}`;
|
|
131
|
+
const does = `changed ${now.field} to ${shown(now.type)}`;
|
|
132
|
+
if (!unconvertible[family(was)]?.includes(family(now.type))) {
|
|
133
|
+
return emit(`${alter} using ${converted(id(key), was, now.type)}`, does, {
|
|
134
|
+
loses: `${change} rewrites its data`,
|
|
135
|
+
...(reads(was, now.type)
|
|
136
|
+
? {
|
|
137
|
+
tries: `${change} fails when a row holds a value it cannot convert`,
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const loses = `${change} deletes its data: PostgreSQL converts no ${shown(was)} to ${shown(now.type)}`;
|
|
143
|
+
if (!now.nullable && fill !== undefined) {
|
|
144
|
+
return emit(`${alter} using ${fill.sql}`, `${does}, ${fill.shown} in the rows it held`, { loses });
|
|
145
|
+
}
|
|
146
|
+
emit(`${alter} using null`, `${does} without its values`, {
|
|
147
|
+
loses,
|
|
148
|
+
...(now.nullable
|
|
149
|
+
? {}
|
|
150
|
+
: {
|
|
151
|
+
tries: `${change} empties it, which fails when ${model} holds rows: make it optional, or give it a value as its default, not a function`,
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Changes the columns of a table both snapshots hold. */
|
|
156
|
+
function alterColumns(name, from, to, fills, emit) {
|
|
157
|
+
const table = `alter table ${id(name)}`;
|
|
158
|
+
const fresh = Object.entries(to.columns).filter(([key]) => from.columns[key] === undefined);
|
|
159
|
+
for (const [key, was] of Object.entries(from.columns)) {
|
|
160
|
+
if (to.columns[key] !== undefined)
|
|
161
|
+
continue;
|
|
162
|
+
const twin = fresh.find(([, now]) => now.type === was.type);
|
|
163
|
+
emit(`${table} drop column ${id(key)}`, `removed ${was.field}`, {
|
|
164
|
+
loses: `removing "${from.model}.${was.field}" deletes data`,
|
|
165
|
+
...(twin === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: {
|
|
168
|
+
rename: {
|
|
169
|
+
to: `${to.model}.${twin[1].field}`,
|
|
170
|
+
dbName: key,
|
|
171
|
+
sql: `${table} rename column ${id(key)} to ${id(twin[0])}`,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
for (const [key, now] of Object.entries(to.columns)) {
|
|
177
|
+
const was = from.columns[key];
|
|
178
|
+
if (was === undefined) {
|
|
179
|
+
addColumn(table, key, now, to.model, fills[key], emit);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
// Optional first, so a type that loses its values can hold none.
|
|
183
|
+
if (!was.nullable && now.nullable) {
|
|
184
|
+
emit(`${table} alter column ${id(key)} drop not null`, `made ${now.field} optional`);
|
|
185
|
+
}
|
|
186
|
+
if (was.type !== now.type) {
|
|
187
|
+
retype(table, key, [was.type, now], to.model, fills[key], emit);
|
|
188
|
+
}
|
|
189
|
+
if (was.nullable && !now.nullable) {
|
|
190
|
+
emit(`${table} alter column ${id(key)} set not null`, `made ${now.field} required`, {
|
|
191
|
+
tries: `making "${to.model}.${now.field}" required fails when a row holds no value`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Adds the keys and checks of `to` that `from` lacks or changed. */
|
|
197
|
+
function addKeys(name, from, to, emit) {
|
|
198
|
+
const add = (part) => `alter table ${id(name)} add ${part}`;
|
|
199
|
+
if (!same(from.primaryKey, to.primaryKey)) {
|
|
200
|
+
const { name: key, columns } = to.primaryKey;
|
|
201
|
+
emit(add(`constraint ${id(key)} primary key (${list(columns)})`), `keyed on ${fields(to, columns)}`, {
|
|
202
|
+
tries: `keying ${keyOf(to, columns)} fails when two rows share a value`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
for (const [key, columns] of added(from.uniques, to.uniques)) {
|
|
206
|
+
emit(add(`constraint ${id(key)} unique (${list(columns)})`), `added a unique key on ${fields(to, columns)}`, {
|
|
207
|
+
tries: `a unique key on ${keyOf(to, columns)} fails when two rows share a value`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
for (const [key, given] of added(from.checks, to.checks)) {
|
|
211
|
+
const field = fields(to, [given.column]);
|
|
212
|
+
const values = given.values.join(', ');
|
|
213
|
+
emit(add(checked(key, given)), `limited ${field} to ${values}`, {
|
|
214
|
+
tries: `limiting "${to.model}.${field}" to ${values} fails when a row holds another value`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const foreignKey = (table, key, { column: name, table: target, cascade }) => `alter table ${id(table)} add constraint ${id(key)} foreign key (${id(name)}) references ${id(target)} ("id")${cascade ? ' on delete cascade' : ''} deferrable initially deferred`;
|
|
219
|
+
/** Every statement from `from`, or an empty database, to `to`. */
|
|
220
|
+
export function plan(from, to, fills) {
|
|
221
|
+
const before = from?.tables ?? {};
|
|
222
|
+
const after = to.tables;
|
|
223
|
+
const phases = [[], [], [], [], [], [], []];
|
|
224
|
+
const emit = (phase, model) => (sql, does, more = {}) => {
|
|
225
|
+
phases[phase]?.push({ sql, model, does, ...more });
|
|
226
|
+
};
|
|
227
|
+
const fresh = Object.entries(after).filter(([name]) => before[name] === undefined);
|
|
228
|
+
for (const [name, table] of Object.entries(before)) {
|
|
229
|
+
const now = after[name];
|
|
230
|
+
const keys = changed(table.foreignKeys, now?.foreignKeys ?? {});
|
|
231
|
+
for (const [key, { column: on }] of keys) {
|
|
232
|
+
emit(0, table.model)(`alter table ${id(name)} drop constraint ${id(key)}`, `dropped the foreign key on ${fields(table, [on])}`);
|
|
233
|
+
}
|
|
234
|
+
if (now !== undefined) {
|
|
235
|
+
dropKeys(name, table, now, emit(1, now.model));
|
|
236
|
+
alterColumns(name, table, now, fills[name] ?? {}, emit(3, now.model));
|
|
237
|
+
addKeys(name, table, now, emit(5, now.model));
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const twin = fresh.find(([, created]) => same(shape(created), shape(table)));
|
|
241
|
+
emit(2, table.model)(`drop table ${id(name)}`, 'removed', {
|
|
242
|
+
loses: `removing ${table.model} deletes its rows`,
|
|
243
|
+
...(twin === undefined
|
|
244
|
+
? {}
|
|
245
|
+
: {
|
|
246
|
+
rename: {
|
|
247
|
+
to: twin[1].model,
|
|
248
|
+
dbName: name,
|
|
249
|
+
sql: `alter table ${id(name)} rename to ${id(twin[0])}`,
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
for (const [name, table] of fresh) {
|
|
255
|
+
emit(4, table.model)(create(name, table), 'created');
|
|
256
|
+
}
|
|
257
|
+
// The extension is database-wide; no plan drops it (D-141).
|
|
258
|
+
const trigram = Object.entries(after).find(([name, table]) => addsTrigrams(before[name], table));
|
|
259
|
+
if (trigram !== undefined) {
|
|
260
|
+
emit(5, trigram[1].model)('create extension if not exists pg_trgm', 'installed pg_trgm');
|
|
261
|
+
}
|
|
262
|
+
for (const [name, table] of Object.entries(after)) {
|
|
263
|
+
const was = before[name];
|
|
264
|
+
addIndexes(name, was, table, emit(5, table.model));
|
|
265
|
+
const keys = added(was?.foreignKeys ?? {}, table.foreignKeys);
|
|
266
|
+
for (const [key, given] of keys) {
|
|
267
|
+
const field = fields(table, [given.column]);
|
|
268
|
+
emit(6, table.model)(foreignKey(name, key, given), `added the foreign key on ${field}`,
|
|
269
|
+
// A column added with its key holds no value yet.
|
|
270
|
+
was?.columns[given.column] === undefined
|
|
271
|
+
? {}
|
|
272
|
+
: {
|
|
273
|
+
tries: `linking "${table.model}.${field}" fails when a row points to no row`,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return phases.flat();
|
|
278
|
+
}
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PostgresPool } from 'kysely';
|
|
2
|
+
import type { Pool } from 'pg';
|
|
3
|
+
/** `pool`, whose failures to give a connection say why. */
|
|
4
|
+
export declare function pooled(url: string, pool: Pool, connections: number, wait: number): PostgresPool;
|
|
5
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAC1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AA0C9B,2DAA2D;AAC3D,wBAAgB,MAAM,CACrB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACV,YAAY,CAYd"}
|
package/dist/pool.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parse } from 'pg-connection-string';
|
|
2
|
+
/** The codes of a server the network does not reach, or does not know. */
|
|
3
|
+
const unreachable = /^(ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH)$/;
|
|
4
|
+
/** What pg-pool throws when no connection frees, or opens, in time. */
|
|
5
|
+
const busy = 'timeout exceeded when trying to connect';
|
|
6
|
+
const silent = 'Connection terminated due to connection timeout';
|
|
7
|
+
/**
|
|
8
|
+
* `error`, or one naming the server `url` cannot reach, not its password
|
|
9
|
+
* (D-105), or the connections that stayed busy.
|
|
10
|
+
*/
|
|
11
|
+
function failed(url, error, connections, wait) {
|
|
12
|
+
const message = error instanceof Error ? error.message : '';
|
|
13
|
+
const seconds = `${wait / 1000}s`;
|
|
14
|
+
if (message === busy) {
|
|
15
|
+
return new Error(`Every one of the ${connections} PostgreSQL connections stayed busy for ${seconds}: a transaction may wait on something slow or never end.`, { cause: error });
|
|
16
|
+
}
|
|
17
|
+
const code = Reflect.get(Object(error), 'code');
|
|
18
|
+
const reachable = typeof code !== 'string' || !unreachable.test(code);
|
|
19
|
+
if (reachable && message !== silent)
|
|
20
|
+
return error;
|
|
21
|
+
const { host, port } = parse(url);
|
|
22
|
+
const address = `${host || process.env['PGHOST'] || 'localhost'}:${port || 5432}`;
|
|
23
|
+
const why = reachable ? `no answer in ${seconds}` : code;
|
|
24
|
+
const reached = new Error(`Cannot reach PostgreSQL at ${address} (${why}): is it running, and is its url right?`, { cause: error });
|
|
25
|
+
return reachable ? reached : Object.assign(reached, { code });
|
|
26
|
+
}
|
|
27
|
+
/** `pool`, whose failures to give a connection say why. */
|
|
28
|
+
export function pooled(url, pool, connections, wait) {
|
|
29
|
+
return {
|
|
30
|
+
options: pool.options,
|
|
31
|
+
connect: async () => {
|
|
32
|
+
try {
|
|
33
|
+
return await pool.connect();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw failed(url, error, connections, wait);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
end: async () => pool.end(),
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/read.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CountRead, Nested, Order, Read, Sort } from 'ultimabase';
|
|
2
|
+
import type { Reach, Sql } from './filter.ts';
|
|
3
|
+
import { Filters } from './filter.ts';
|
|
4
|
+
import type { Catalog, Table } from './names.ts';
|
|
5
|
+
/** A sort key in `ORDER BY`; PostgreSQL's default `null`s are D-94's. */
|
|
6
|
+
export declare const sorted: (key: Sql, { direction, nulls }: Sort) => Sql;
|
|
7
|
+
export declare class Compiler {
|
|
8
|
+
readonly filters: Filters;
|
|
9
|
+
readonly catalog: Catalog;
|
|
10
|
+
constructor(catalog: Catalog);
|
|
11
|
+
/** The rows `nested` selects among those `reach` gives, as a jsonb array. */
|
|
12
|
+
list(model: string, nested: Nested, reach: Reach, alias: string): Sql;
|
|
13
|
+
/**
|
|
14
|
+
* The rows at or after the cursor in `order`, `null` where it sorts, as
|
|
15
|
+
* `memory()` compares: `a ≥ c` is `a > c`, or `a = c` and the next key
|
|
16
|
+
* decides.
|
|
17
|
+
*/
|
|
18
|
+
private keyset;
|
|
19
|
+
/**
|
|
20
|
+
* A sort key: a column, through `one` relations, or a count; a row the
|
|
21
|
+
* relations do not reach gives `null` (D-134). A relevance is `ts_rank`'s
|
|
22
|
+
* (D-148).
|
|
23
|
+
*/
|
|
24
|
+
key(table: Table, alias: string, order: Pick<Order, 'field' | 'through' | 'count' | 'relevance'>, steps?: NonNullable<Order['through']>): Sql;
|
|
25
|
+
/** The rows of a `many` relation of the row that `where` selects. */
|
|
26
|
+
private count;
|
|
27
|
+
/**
|
|
28
|
+
* A scalar column, a date in UTC whatever the session's time zone, a
|
|
29
|
+
* decimal as text, which jsonb would make a number (D-145).
|
|
30
|
+
*/
|
|
31
|
+
private scalar;
|
|
32
|
+
/** A row as jsonb: its fields, each `one` as `{ id }`, then its includes. */
|
|
33
|
+
row(model: string, alias: string, nested: Nested): Sql;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `read`'s rows as one jsonb array, its root rows taken from `source`, by
|
|
37
|
+
* default its table, and the tables it reaches besides; a locked read locks
|
|
38
|
+
* the root rows its `where` selects, in `id` order (D-99, D-133).
|
|
39
|
+
*/
|
|
40
|
+
export declare function compileRows(catalog: Catalog, read: Read, source?: Sql): {
|
|
41
|
+
readonly rows: Sql;
|
|
42
|
+
readonly tables: ReadonlySet<string>;
|
|
43
|
+
};
|
|
44
|
+
/** The statement that returns `read`'s rows as one jsonb array, `rows`. */
|
|
45
|
+
export declare function compileFind(catalog: Catalog, read: Read): Sql;
|
|
46
|
+
export declare function compileCount(catalog: Catalog, read: CountRead): Sql;
|
|
47
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,SAAS,EAGT,MAAM,EACN,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAO,OAAO,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AA0BhD,yEAAyE;AACzE,eAAO,MAAM,MAAM,QAAS,GAAG,wBAAwB,IAAI,KAAG,GACiB,CAAA;AAE/E,qBAAa,QAAQ;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IAEzB,YAAY,OAAO,EAAE,OAAO,EAG3B;IAED,6EAA6E;IAC7E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAkCpE;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAqBd;;;;OAIG;IACH,GAAG,CACF,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAC,EAC/D,KAAK,GAAE,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAuB,GACxD,GAAG,CAoBL;IAED,qEAAqE;IACrE,OAAO,CAAC,KAAK;IAWb;;;OAGG;IACH,OAAO,CAAC,MAAM;IAOd,6EAA6E;IAC7E,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAgDrD;CACD;AA0BD;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,MAAM,CAAC,EAAE,GAAG,GACV;IAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,CAgB9D;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,CAE7D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,GAAG,CAMnE"}
|