@smart-data-engines/sde 0.1.0-dev.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/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +153 -0
- package/bin/weather.mjs +32 -0
- package/dist/_usage.d.ts +30 -0
- package/dist/_usage.js +194 -0
- package/dist/_usage.js.map +1 -0
- package/dist/bulk.d.ts +9 -0
- package/dist/bulk.js +83 -0
- package/dist/bulk.js.map +1 -0
- package/dist/canonical.d.ts +46 -0
- package/dist/canonical.js +150 -0
- package/dist/canonical.js.map +1 -0
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.js +62 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cutover.d.ts +36 -0
- package/dist/cutover.js +219 -0
- package/dist/cutover.js.map +1 -0
- package/dist/demo/model.d.ts +28 -0
- package/dist/demo/model.js +40 -0
- package/dist/demo/model.js.map +1 -0
- package/dist/demo/project.d.ts +19 -0
- package/dist/demo/project.js +128 -0
- package/dist/demo/project.js.map +1 -0
- package/dist/demo/weather.d.ts +73 -0
- package/dist/demo/weather.js +334 -0
- package/dist/demo/weather.js.map +1 -0
- package/dist/engines/_clickhouse-connection.d.ts +17 -0
- package/dist/engines/_clickhouse-connection.js +182 -0
- package/dist/engines/_clickhouse-connection.js.map +1 -0
- package/dist/engines/_tls-peer-identity.d.ts +2 -0
- package/dist/engines/_tls-peer-identity.js +23 -0
- package/dist/engines/_tls-peer-identity.js.map +1 -0
- package/dist/engines/_write-fences.d.ts +51 -0
- package/dist/engines/_write-fences.js +189 -0
- package/dist/engines/_write-fences.js.map +1 -0
- package/dist/engines/clickhouse.d.ts +193 -0
- package/dist/engines/clickhouse.js +899 -0
- package/dist/engines/clickhouse.js.map +1 -0
- package/dist/engines/postgres.d.ts +293 -0
- package/dist/engines/postgres.js +981 -0
- package/dist/engines/postgres.js.map +1 -0
- package/dist/errors.d.ts +89 -0
- package/dist/errors.js +90 -0
- package/dist/errors.js.map +1 -0
- package/dist/frozen-verification.d.ts +26 -0
- package/dist/frozen-verification.js +67 -0
- package/dist/frozen-verification.js.map +1 -0
- package/dist/generation.d.ts +34 -0
- package/dist/generation.js +81 -0
- package/dist/generation.js.map +1 -0
- package/dist/groups.d.ts +17 -0
- package/dist/groups.js +66 -0
- package/dist/groups.js.map +1 -0
- package/dist/hashing.d.ts +68 -0
- package/dist/hashing.js +146 -0
- package/dist/hashing.js.map +1 -0
- package/dist/in-place-index.d.ts +43 -0
- package/dist/in-place-index.js +272 -0
- package/dist/in-place-index.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection.d.ts +19 -0
- package/dist/inspection.js +31 -0
- package/dist/inspection.js.map +1 -0
- package/dist/internal.d.ts +42 -0
- package/dist/internal.js +56 -0
- package/dist/internal.js.map +1 -0
- package/dist/layout.d.ts +36 -0
- package/dist/layout.js +62 -0
- package/dist/layout.js.map +1 -0
- package/dist/migration.d.ts +197 -0
- package/dist/migration.js +592 -0
- package/dist/migration.js.map +1 -0
- package/dist/model.d.ts +93 -0
- package/dist/model.js +313 -0
- package/dist/model.js.map +1 -0
- package/dist/physical.d.ts +128 -0
- package/dist/physical.js +421 -0
- package/dist/physical.js.map +1 -0
- package/dist/placement.d.ts +157 -0
- package/dist/placement.js +651 -0
- package/dist/placement.js.map +1 -0
- package/dist/provisioning.d.ts +6 -0
- package/dist/provisioning.js +45 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/query.d.ts +68 -0
- package/dist/query.js +340 -0
- package/dist/query.js.map +1 -0
- package/dist/routing.d.ts +25 -0
- package/dist/routing.js +35 -0
- package/dist/routing.js.map +1 -0
- package/dist/schema.d.ts +110 -0
- package/dist/schema.js +337 -0
- package/dist/schema.js.map +1 -0
- package/dist/session.d.ts +195 -0
- package/dist/session.js +870 -0
- package/dist/session.js.map +1 -0
- package/dist/shapes.d.ts +30 -0
- package/dist/shapes.js +112 -0
- package/dist/shapes.js.map +1 -0
- package/dist/staging.d.ts +29 -0
- package/dist/staging.js +214 -0
- package/dist/staging.js.map +1 -0
- package/dist/telemetry.d.ts +468 -0
- package/dist/telemetry.js +872 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/testing/loader.d.ts +38 -0
- package/dist/testing/loader.js +86 -0
- package/dist/testing/loader.js.map +1 -0
- package/dist/testing/memory.d.ts +131 -0
- package/dist/testing/memory.js +311 -0
- package/dist/testing/memory.js.map +1 -0
- package/dist/timestamp.d.ts +20 -0
- package/dist/timestamp.js +89 -0
- package/dist/timestamp.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.js +100 -0
- package/dist/types.js.map +1 -0
- package/dist/verification.d.ts +41 -0
- package/dist/verification.js +169 -0
- package/dist/verification.js.map +1 -0
- package/dist/watermark.d.ts +103 -0
- package/dist/watermark.js +170 -0
- package/dist/watermark.js.map +1 -0
- package/dist/write-fence.d.ts +58 -0
- package/dist/write-fence.js +225 -0
- package/dist/write-fence.js.map +1 -0
- package/package.json +86 -0
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declaring a model, and turning it into canonical bytes plus a version.
|
|
3
|
+
*
|
|
4
|
+
* The declaration is explicit because it has to be - see `types.ts`. What that buys, unexpectedly, is
|
|
5
|
+
* that this file is shorter and clearer than its Python counterpart: there is no annotation
|
|
6
|
+
* resolution, no forward references, no scope in which a name might or might not be visible. The
|
|
7
|
+
* whole class of bugs Python needed `localns` for does not exist here.
|
|
8
|
+
*
|
|
9
|
+
* Two rules from the contract run through everything below:
|
|
10
|
+
*
|
|
11
|
+
* - Arrays are sorted wherever order carries no meaning, **by code point**, not with `.sort()`.
|
|
12
|
+
* - Where order does carry meaning it is an explicit `position` inside each element. Composite keys
|
|
13
|
+
* are the case that matters: `(tenant, id)` and `(id, tenant)` are different keys, and a reader
|
|
14
|
+
* should not have to know which arrays here are load-bearing.
|
|
15
|
+
*/
|
|
16
|
+
import type { FieldType, NeutralType } from './types.js';
|
|
17
|
+
/**
|
|
18
|
+
* The **IR's** format version, and the one the conformance vectors are pinned to.
|
|
19
|
+
*
|
|
20
|
+
* Separate from `MAP_CONTRACT` since the placement map gained a key the IR did not. One counter for
|
|
21
|
+
* two artefacts means every artefact's version moves when any one of them changes - and `contract`
|
|
22
|
+
* is *inside* the IR, whose digest is `model_version`, so bumping it for a change to the map format
|
|
23
|
+
* would give every client a new model version and invalidate every issued map. For a key in a
|
|
24
|
+
* different document.
|
|
25
|
+
*/
|
|
26
|
+
export declare const CONTRACT = 1;
|
|
27
|
+
export interface FieldSpec {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly type: NeutralType;
|
|
30
|
+
readonly nullable: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface RelationSpec {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly source: string;
|
|
35
|
+
readonly target: string;
|
|
36
|
+
}
|
|
37
|
+
export interface EntitySpec {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly fields: readonly FieldSpec[];
|
|
40
|
+
readonly key: readonly string[];
|
|
41
|
+
readonly pii: readonly string[];
|
|
42
|
+
readonly residency: string | null;
|
|
43
|
+
}
|
|
44
|
+
export interface CostCeiling {
|
|
45
|
+
readonly amount: string;
|
|
46
|
+
readonly currency: string;
|
|
47
|
+
}
|
|
48
|
+
export interface LogicalModel {
|
|
49
|
+
readonly entities: readonly EntitySpec[];
|
|
50
|
+
readonly relations: readonly RelationSpec[];
|
|
51
|
+
readonly atomic: readonly (readonly string[])[];
|
|
52
|
+
readonly costCeiling: CostCeiling | null;
|
|
53
|
+
readonly ir: Record<string, unknown>;
|
|
54
|
+
readonly version: string;
|
|
55
|
+
}
|
|
56
|
+
/** What `entity()` takes. Everything except `fields` is optional, and absence means no constraint. */
|
|
57
|
+
export interface EntityDeclaration {
|
|
58
|
+
readonly fields: Readonly<Record<string, FieldType>>;
|
|
59
|
+
readonly relations?: Readonly<Record<string, {
|
|
60
|
+
readonly to: string;
|
|
61
|
+
}>>;
|
|
62
|
+
readonly key?: readonly string[];
|
|
63
|
+
readonly pii?: readonly string[];
|
|
64
|
+
readonly residency?: string;
|
|
65
|
+
readonly atomicWith?: readonly string[];
|
|
66
|
+
}
|
|
67
|
+
export interface Entity extends EntityDeclaration {
|
|
68
|
+
readonly name: string;
|
|
69
|
+
}
|
|
70
|
+
/** Declare an entity. */
|
|
71
|
+
export declare function entity(name: string, declaration: EntityDeclaration): Entity;
|
|
72
|
+
/** Declare a relation target. `relations: { user: ref('User') }`. */
|
|
73
|
+
export declare function ref(to: string): {
|
|
74
|
+
readonly to: string;
|
|
75
|
+
};
|
|
76
|
+
export declare function assemble(entities: readonly EntitySpec[], relations: readonly RelationSpec[], atomic: readonly (readonly string[])[], costCeiling: CostCeiling | null): LogicalModel;
|
|
77
|
+
/** Build a model from declared entities. */
|
|
78
|
+
export declare function buildModel(entities: readonly Entity[], options?: {
|
|
79
|
+
readonly costCeiling?: CostCeiling;
|
|
80
|
+
}): LogicalModel;
|
|
81
|
+
/**
|
|
82
|
+
* The model as the neutral form of format-contract §4a: the inverse of `modelFromNeutral`.
|
|
83
|
+
*
|
|
84
|
+
* It exists because the control plane takes a client's model as *that* document while a client
|
|
85
|
+
* declares it in whichever way their language is comfortable with. Without this, a client has to
|
|
86
|
+
* write their model a second time by hand, and the two copies then drift.
|
|
87
|
+
*
|
|
88
|
+
* **The IR is not this document.** They differ exactly where a key is written; see `checkShape` in
|
|
89
|
+
* the neutral loader.
|
|
90
|
+
*/
|
|
91
|
+
export declare function neutralDeclaration(model: LogicalModel): Record<string, unknown>;
|
|
92
|
+
export declare function irBytes(model: LogicalModel): Buffer;
|
|
93
|
+
export declare function entityOf(model: LogicalModel, name: string): EntitySpec;
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declaring a model, and turning it into canonical bytes plus a version.
|
|
3
|
+
*
|
|
4
|
+
* The declaration is explicit because it has to be - see `types.ts`. What that buys, unexpectedly, is
|
|
5
|
+
* that this file is shorter and clearer than its Python counterpart: there is no annotation
|
|
6
|
+
* resolution, no forward references, no scope in which a name might or might not be visible. The
|
|
7
|
+
* whole class of bugs Python needed `localns` for does not exist here.
|
|
8
|
+
*
|
|
9
|
+
* Two rules from the contract run through everything below:
|
|
10
|
+
*
|
|
11
|
+
* - Arrays are sorted wherever order carries no meaning, **by code point**, not with `.sort()`.
|
|
12
|
+
* - Where order does carry meaning it is an explicit `position` inside each element. Composite keys
|
|
13
|
+
* are the case that matters: `(tenant, id)` and `(id, tenant)` are different keys, and a reader
|
|
14
|
+
* should not have to know which arrays here are load-bearing.
|
|
15
|
+
*/
|
|
16
|
+
import { canonicalBytes, compareCodePoints, digest16 } from './canonical.js';
|
|
17
|
+
import { DeclarationError } from './errors.js';
|
|
18
|
+
import { checkType } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* The **IR's** format version, and the one the conformance vectors are pinned to.
|
|
21
|
+
*
|
|
22
|
+
* Separate from `MAP_CONTRACT` since the placement map gained a key the IR did not. One counter for
|
|
23
|
+
* two artefacts means every artefact's version moves when any one of them changes - and `contract`
|
|
24
|
+
* is *inside* the IR, whose digest is `model_version`, so bumping it for a change to the map format
|
|
25
|
+
* would give every client a new model version and invalidate every issued map. For a key in a
|
|
26
|
+
* different document.
|
|
27
|
+
*/
|
|
28
|
+
export const CONTRACT = 1;
|
|
29
|
+
/** Declare an entity. */
|
|
30
|
+
export function entity(name, declaration) {
|
|
31
|
+
if (!name)
|
|
32
|
+
throw new DeclarationError('an entity needs a name');
|
|
33
|
+
if (Object.keys(declaration.fields).length === 0) {
|
|
34
|
+
throw new DeclarationError(`${name} has no fields. An entity that stores nothing cannot be placed, and a relation-only ` +
|
|
35
|
+
'entity is usually a join table that wants to be one.');
|
|
36
|
+
}
|
|
37
|
+
return { name, ...declaration };
|
|
38
|
+
}
|
|
39
|
+
/** Declare a relation target. `relations: { user: ref('User') }`. */
|
|
40
|
+
export function ref(to) {
|
|
41
|
+
return { to };
|
|
42
|
+
}
|
|
43
|
+
function resolveKey(ent, fieldNames) {
|
|
44
|
+
if (ent.key !== undefined) {
|
|
45
|
+
if (ent.key.length === 0)
|
|
46
|
+
throw new DeclarationError(`${ent.name}: key is empty`);
|
|
47
|
+
const missing = ent.key.filter((k) => !fieldNames.has(k));
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
throw new DeclarationError(`${ent.name}: key names ${JSON.stringify(missing)}, which are not fields of ${ent.name}. A ` +
|
|
50
|
+
'relation cannot be part of a key: the key has to be storable in the entity itself.');
|
|
51
|
+
}
|
|
52
|
+
return ent.key;
|
|
53
|
+
}
|
|
54
|
+
if (fieldNames.has('id'))
|
|
55
|
+
return ['id'];
|
|
56
|
+
throw new DeclarationError(`${ent.name} has no 'id' field and no key. Every entity needs a key: without one there is no ` +
|
|
57
|
+
'way to address a row, no way to migrate it and no way to verify a migration moved it.');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Merge pairwise `atomicWith` declarations into sorted groups.
|
|
61
|
+
*
|
|
62
|
+
* Symmetric even when declared on one side, and transitive: if A is atomic with B and B with C then
|
|
63
|
+
* all three commit together, because nothing else is implementable on one engine's transaction.
|
|
64
|
+
*/
|
|
65
|
+
function normaliseAtomic(entities) {
|
|
66
|
+
const parent = new Map();
|
|
67
|
+
for (const ent of entities)
|
|
68
|
+
parent.set(ent.name, ent.name);
|
|
69
|
+
const find = (x) => {
|
|
70
|
+
let cur = x;
|
|
71
|
+
while (parent.get(cur) !== cur) {
|
|
72
|
+
const next = parent.get(cur);
|
|
73
|
+
parent.set(cur, parent.get(next));
|
|
74
|
+
cur = parent.get(cur);
|
|
75
|
+
}
|
|
76
|
+
return cur;
|
|
77
|
+
};
|
|
78
|
+
const union = (a, b) => {
|
|
79
|
+
const ra = find(a);
|
|
80
|
+
const rb = find(b);
|
|
81
|
+
if (ra === rb)
|
|
82
|
+
return;
|
|
83
|
+
// Attach to the smaller root so the representative never depends on visit order.
|
|
84
|
+
const [small, large] = compareCodePoints(ra, rb) <= 0 ? [ra, rb] : [rb, ra];
|
|
85
|
+
parent.set(large, small);
|
|
86
|
+
};
|
|
87
|
+
const known = new Set(entities.map((e) => e.name));
|
|
88
|
+
const touched = new Set();
|
|
89
|
+
for (const ent of entities) {
|
|
90
|
+
for (const other of ent.atomicWith ?? []) {
|
|
91
|
+
if (!known.has(other)) {
|
|
92
|
+
throw new DeclarationError(`${ent.name}: atomicWith names ${JSON.stringify(other)}, which is not a declared entity`);
|
|
93
|
+
}
|
|
94
|
+
if (other === ent.name) {
|
|
95
|
+
throw new DeclarationError(`${ent.name}: atomicWith names itself, which says nothing`);
|
|
96
|
+
}
|
|
97
|
+
union(ent.name, other);
|
|
98
|
+
touched.add(ent.name);
|
|
99
|
+
touched.add(other);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const buckets = new Map();
|
|
103
|
+
for (const name of [...touched].sort(compareCodePoints)) {
|
|
104
|
+
const root = find(name);
|
|
105
|
+
const bucket = buckets.get(root);
|
|
106
|
+
if (bucket)
|
|
107
|
+
bucket.push(name);
|
|
108
|
+
else
|
|
109
|
+
buckets.set(root, [name]);
|
|
110
|
+
}
|
|
111
|
+
return [...buckets.values()]
|
|
112
|
+
.map((members) => [...members].sort(compareCodePoints))
|
|
113
|
+
.sort((a, b) => compareCodePoints(a[0], b[0]));
|
|
114
|
+
}
|
|
115
|
+
/** Assemble already-resolved specs into a model. Shared by `buildModel` and the vector loader. */
|
|
116
|
+
/**
|
|
117
|
+
* The seven refusals of format-contract §4a, in the order that section writes them.
|
|
118
|
+
*
|
|
119
|
+
* Here rather than at each front door, because there are two - `buildModel` and the neutral-JSON
|
|
120
|
+
* loader the conformance vectors use - and each of them enforced a different subset. The vectors
|
|
121
|
+
* therefore ran a weaker validator than any application does, which is the suite's own failure
|
|
122
|
+
* mode: a vector that passes without reaching the code it describes takes the place of one that
|
|
123
|
+
* would have.
|
|
124
|
+
*/
|
|
125
|
+
function refuseADeclarationThatIsNotAModel(entities, relations) {
|
|
126
|
+
if (entities.length === 0) {
|
|
127
|
+
throw new DeclarationError('no entities declared, so there is no model to build');
|
|
128
|
+
}
|
|
129
|
+
const known = new Set();
|
|
130
|
+
for (const spec of entities) {
|
|
131
|
+
if (spec.fields.length === 0) {
|
|
132
|
+
throw new DeclarationError(`${spec.name} has no fields. An entity that stores nothing cannot be placed, so there is ` +
|
|
133
|
+
'nothing for a map to say about it.');
|
|
134
|
+
}
|
|
135
|
+
if (known.has(spec.name)) {
|
|
136
|
+
throw new DeclarationError(`two entities are called ${JSON.stringify(spec.name)}. Entity names reach the canonical ` +
|
|
137
|
+
"IR and the colocation graph, so a duplicate makes 'which entity is this' unanswerable " +
|
|
138
|
+
'in the document whose job is to answer it.');
|
|
139
|
+
}
|
|
140
|
+
known.add(spec.name);
|
|
141
|
+
}
|
|
142
|
+
const relationsOf = new Map();
|
|
143
|
+
for (const rel of relations) {
|
|
144
|
+
for (const side of [rel.source, rel.target]) {
|
|
145
|
+
if (!known.has(side)) {
|
|
146
|
+
throw new DeclarationError(`relation '${rel.name}' names unknown entity '${side}'`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const named = relationsOf.get(rel.source) ?? new Set();
|
|
150
|
+
if (named.has(rel.name)) {
|
|
151
|
+
throw new DeclarationError(`${rel.source}.${rel.name} is declared twice. Two relations of one name on one entity are ` +
|
|
152
|
+
'two edges the colocation graph cannot tell apart.');
|
|
153
|
+
}
|
|
154
|
+
named.add(rel.name);
|
|
155
|
+
relationsOf.set(rel.source, named);
|
|
156
|
+
}
|
|
157
|
+
for (const spec of entities) {
|
|
158
|
+
const names = spec.fields.map((f) => f.name);
|
|
159
|
+
const duplicates = [...new Set(names.filter((n, i) => names.indexOf(n) !== i))].sort(compareCodePoints);
|
|
160
|
+
if (duplicates.length > 0) {
|
|
161
|
+
throw new DeclarationError(`${spec.name} declares the fields ${JSON.stringify(duplicates)} more than once. The ` +
|
|
162
|
+
"layout would have two columns of one name, and the refusal would arrive from the " +
|
|
163
|
+
'client\'s engine at CREATE TABLE.');
|
|
164
|
+
}
|
|
165
|
+
const known_fields = new Set(names);
|
|
166
|
+
if (spec.key.length === 0) {
|
|
167
|
+
throw new DeclarationError(`${spec.name} declares no key. A key is what makes a row addressable, migratable and ` +
|
|
168
|
+
'verifiable - a backfill compares rows by it - so an entity without one is a group that ' +
|
|
169
|
+
'cannot be moved, and that is worth knowing when the model is declared rather than in ' +
|
|
170
|
+
'the middle of a migration. No key is invented for you.');
|
|
171
|
+
}
|
|
172
|
+
const missing = spec.key.filter((k) => !known_fields.has(k));
|
|
173
|
+
if (missing.length > 0) {
|
|
174
|
+
throw new DeclarationError(`${spec.name}: key names ${JSON.stringify(missing)}, which are not fields of ${spec.name}`);
|
|
175
|
+
}
|
|
176
|
+
const repeated = [
|
|
177
|
+
...new Set(spec.key.filter((k, i) => spec.key.indexOf(k) !== i)),
|
|
178
|
+
].sort(compareCodePoints);
|
|
179
|
+
if (repeated.length > 0) {
|
|
180
|
+
throw new DeclarationError(`${spec.name}: key names ${JSON.stringify(repeated)} more than once, which is a ` +
|
|
181
|
+
'composite key with one column in two positions.');
|
|
182
|
+
}
|
|
183
|
+
const badPii = spec.pii.filter((f) => !known_fields.has(f));
|
|
184
|
+
if (badPii.length > 0) {
|
|
185
|
+
throw new DeclarationError(`${spec.name}: pii names ${JSON.stringify(badPii)}, which are not fields of ` +
|
|
186
|
+
`${spec.name}. A pii entry that is not a field silently protects nothing, and the ` +
|
|
187
|
+
'exclusion of personal data from a derived copy is meant to be readable rather than ' +
|
|
188
|
+
'trusted.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export function assemble(entities, relations, atomic, costCeiling) {
|
|
193
|
+
refuseADeclarationThatIsNotAModel(entities, relations);
|
|
194
|
+
const specs = [...entities].sort((a, b) => compareCodePoints(a.name, b.name));
|
|
195
|
+
const rels = [...relations].sort((a, b) => compareCodePoints(a.source, b.source) ||
|
|
196
|
+
compareCodePoints(a.name, b.name) ||
|
|
197
|
+
compareCodePoints(a.target, b.target));
|
|
198
|
+
const ir = {
|
|
199
|
+
contract: CONTRACT,
|
|
200
|
+
entities: specs.map((s) => ({
|
|
201
|
+
name: s.name,
|
|
202
|
+
fields: [...s.fields]
|
|
203
|
+
.sort((a, b) => compareCodePoints(a.name, b.name))
|
|
204
|
+
.map((f) => ({ name: f.name, type: f.type, nullable: f.nullable })),
|
|
205
|
+
key: s.key.map((field, position) => ({ field, position })),
|
|
206
|
+
pii: [...s.pii].sort(compareCodePoints),
|
|
207
|
+
residency: s.residency,
|
|
208
|
+
})),
|
|
209
|
+
relations: rels.map((r) => ({ name: r.name, from: r.source, to: r.target })),
|
|
210
|
+
atomic: atomic.map((group) => [...group]),
|
|
211
|
+
cost_ceiling: costCeiling === null ? null : { ...costCeiling },
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
entities: specs,
|
|
215
|
+
relations: rels,
|
|
216
|
+
atomic,
|
|
217
|
+
costCeiling,
|
|
218
|
+
ir,
|
|
219
|
+
version: digest16(ir),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** Build a model from declared entities. */
|
|
223
|
+
export function buildModel(entities, options = {}) {
|
|
224
|
+
if (entities.length === 0) {
|
|
225
|
+
throw new DeclarationError('no entities declared, so there is no model to build');
|
|
226
|
+
}
|
|
227
|
+
// Uniqueness, pii and the key are checked in `assemble` now, which both front doors go through.
|
|
228
|
+
// A guarantee that holds at one of two doors holds for one of two callers, and the conformance
|
|
229
|
+
// vectors come through the other one.
|
|
230
|
+
const names = new Set(entities.map((e) => e.name));
|
|
231
|
+
const specs = [];
|
|
232
|
+
const relations = [];
|
|
233
|
+
for (const ent of entities) {
|
|
234
|
+
const fieldNames = new Set(Object.keys(ent.fields));
|
|
235
|
+
const fields = Object.entries(ent.fields).map(([name, ft]) => ({
|
|
236
|
+
name,
|
|
237
|
+
type: checkType(ft.type, `${ent.name}.${name}`),
|
|
238
|
+
nullable: ft.nullable,
|
|
239
|
+
}));
|
|
240
|
+
for (const [name, relation] of Object.entries(ent.relations ?? {})) {
|
|
241
|
+
if (!names.has(relation.to)) {
|
|
242
|
+
throw new DeclarationError(`${ent.name}.${name} points at ${relation.to}, which is not a declared entity. Include ` +
|
|
243
|
+
'it in the model you are building.');
|
|
244
|
+
}
|
|
245
|
+
// No refusal for a field and a relation sharing a name. This check used to be here and it
|
|
246
|
+
// contradicted the contract: §2a says the digest collision between the two is intended, and
|
|
247
|
+
// `hashing/003-reserved-object-keys` declares exactly that shape - so this library refused a
|
|
248
|
+
// model its own conformance vector pins. It is legal because a relation reaches a layout as
|
|
249
|
+
// `<relation>_<target key field>` and never under its own name, so there is no column for it
|
|
250
|
+
// to collide with.
|
|
251
|
+
relations.push({ name, source: ent.name, target: relation.to });
|
|
252
|
+
}
|
|
253
|
+
specs.push({
|
|
254
|
+
name: ent.name,
|
|
255
|
+
fields,
|
|
256
|
+
key: resolveKey(ent, fieldNames),
|
|
257
|
+
pii: ent.pii ?? [],
|
|
258
|
+
residency: ent.residency ?? null,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return assemble(specs, relations, normaliseAtomic(entities), options.costCeiling ?? null);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* The model as the neutral form of format-contract §4a: the inverse of `modelFromNeutral`.
|
|
265
|
+
*
|
|
266
|
+
* It exists because the control plane takes a client's model as *that* document while a client
|
|
267
|
+
* declares it in whichever way their language is comfortable with. Without this, a client has to
|
|
268
|
+
* write their model a second time by hand, and the two copies then drift.
|
|
269
|
+
*
|
|
270
|
+
* **The IR is not this document.** They differ exactly where a key is written; see `checkShape` in
|
|
271
|
+
* the neutral loader.
|
|
272
|
+
*/
|
|
273
|
+
export function neutralDeclaration(model) {
|
|
274
|
+
const entities = model.entities.map((spec) => {
|
|
275
|
+
const entity = {
|
|
276
|
+
name: spec.name,
|
|
277
|
+
fields: spec.fields.map((f) => ({
|
|
278
|
+
name: f.name,
|
|
279
|
+
type: f.type,
|
|
280
|
+
...(f.nullable ? { nullable: true } : {}),
|
|
281
|
+
})),
|
|
282
|
+
key: [...spec.key],
|
|
283
|
+
};
|
|
284
|
+
if (spec.pii.length > 0)
|
|
285
|
+
entity['pii'] = [...spec.pii];
|
|
286
|
+
if (spec.residency !== null)
|
|
287
|
+
entity['residency'] = spec.residency;
|
|
288
|
+
return entity;
|
|
289
|
+
});
|
|
290
|
+
const document = { entities };
|
|
291
|
+
if (model.relations.length > 0) {
|
|
292
|
+
document['relations'] = model.relations.map((r) => ({
|
|
293
|
+
name: r.name,
|
|
294
|
+
from: r.source,
|
|
295
|
+
to: r.target,
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
if (model.atomic.length > 0)
|
|
299
|
+
document['atomic'] = model.atomic.map((g) => [...g]);
|
|
300
|
+
if (model.costCeiling !== null)
|
|
301
|
+
document['cost_ceiling'] = { ...model.costCeiling };
|
|
302
|
+
return document;
|
|
303
|
+
}
|
|
304
|
+
export function irBytes(model) {
|
|
305
|
+
return canonicalBytes(model.ir);
|
|
306
|
+
}
|
|
307
|
+
export function entityOf(model, name) {
|
|
308
|
+
const found = model.entities.find((e) => e.name === name);
|
|
309
|
+
if (!found)
|
|
310
|
+
throw new DeclarationError(`${name} is not in this model`);
|
|
311
|
+
return found;
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The physical design vocabulary a placement map can carry, and the rules each element obeys.
|
|
3
|
+
*
|
|
4
|
+
* Placement map contract 5 lets a layout say three things about storage beyond tables and column
|
|
5
|
+
* types: the physical order of an entity's key, a time partition, and indexes of a named method.
|
|
6
|
+
* They are chosen by the control plane and a library only renders them. Every element is a closed
|
|
7
|
+
* vocabulary rendered by this library; nothing in a map is pasted into DDL.
|
|
8
|
+
*
|
|
9
|
+
* This is the port of the reference's `sde/physical.py`, and it is one module for the reason that
|
|
10
|
+
* one is: the map parser, the DDL renderer and the adapters' verification all read it, because a
|
|
11
|
+
* second copy of "which index methods ClickHouse has" is how a map gets signed that no library can
|
|
12
|
+
* apply. The two data rules - a partition follows the key, and `key_order` only reorders the key -
|
|
13
|
+
* were measured on PostgreSQL 15.19 and ClickHouse 24.8.14.39 before they were written; the
|
|
14
|
+
* reference module carries the measurements.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PHYSICAL_DESIGN_SINCE = 5;
|
|
17
|
+
/** The placement map contract that introduced `key_order`, `partition_by` and index methods. */
|
|
18
|
+
export declare const GRANULARITIES: readonly ["day", "month", "year"];
|
|
19
|
+
/**
|
|
20
|
+
* Time partition sizes. No week: ClickHouse's `toStartOfWeek` depends on a mode, and a closed
|
|
21
|
+
* vocabulary has no modes.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TEMPORAL_TYPES: readonly ["date", "timestamptz"];
|
|
24
|
+
/**
|
|
25
|
+
* Neutral types a partition may be derived from. `timestamp` is absent on purpose: its ClickHouse
|
|
26
|
+
* column carries no zone, so the partition a value falls into follows the server's configured
|
|
27
|
+
* timezone, and a reconfigured server would put one key into two partitions for ever.
|
|
28
|
+
*/
|
|
29
|
+
export declare const POSTGRES_METHODS: readonly ["brin", "btree"];
|
|
30
|
+
export declare const CLICKHOUSE_METHODS: readonly ["bloom_filter", "minmax", "set"];
|
|
31
|
+
/** ClickHouse data-skipping index types. It has no B-tree; its primary index is `ORDER BY`. */
|
|
32
|
+
export declare const INDEX_METHODS: readonly string[];
|
|
33
|
+
export declare const GRANULARITY_RANGE: readonly [1, 1024];
|
|
34
|
+
export declare const SET_ROWS_RANGE: readonly [1, 65536];
|
|
35
|
+
export declare const PARTITION_FUNCTIONS: Readonly<Record<string, string>>;
|
|
36
|
+
/** ClickHouse partition expressions, as its catalogue reports them back. */
|
|
37
|
+
export declare const PARTITIONING_DIALECTS: ReadonlySet<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Dialects that render `partition_by`. PostgreSQL is absent on purpose: declarative partitioning
|
|
40
|
+
* there needs every partition created before a row can arrive, a lifecycle this product does not
|
|
41
|
+
* manage, and an unpartitioned table under a map that says otherwise would be a silent drop.
|
|
42
|
+
*/
|
|
43
|
+
export declare const METHODS_BY_DIALECT: Readonly<Record<string, readonly string[]>>;
|
|
44
|
+
export type PartitionSpec = Readonly<{
|
|
45
|
+
field: string;
|
|
46
|
+
granularity: string;
|
|
47
|
+
}>;
|
|
48
|
+
type ErrorClass = new (message: string) => Error;
|
|
49
|
+
/** An index's method. Absent means `btree`, what every map before contract 5 meant. */
|
|
50
|
+
export declare function indexMethod(index: Readonly<Record<string, unknown>>): string;
|
|
51
|
+
/** `key_order`: entity -> the physical order of that entity's key columns. */
|
|
52
|
+
export declare function parseKeyOrder(raw: unknown, where: string, tables: Readonly<Record<string, string>>): Record<string, readonly string[]>;
|
|
53
|
+
/** `partition_by`: entity -> `{"field": ..., "granularity": ...}`, and nothing else. */
|
|
54
|
+
export declare function parsePartitionBy(raw: unknown, where: string, tables: Readonly<Record<string, string>>): Record<string, PartitionSpec>;
|
|
55
|
+
/**
|
|
56
|
+
* Index definitions, validated where the map arrives rather than when DDL is rendered.
|
|
57
|
+
*
|
|
58
|
+
* The structural half applies to every contract and is a tightening; the method, granularity and
|
|
59
|
+
* `max_rows` keys are contract 5, because an earlier library ignores them and would build a B-tree
|
|
60
|
+
* where a later one builds BRIN from the same document.
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseIndexes(raw: unknown, where: string, tables: Readonly<Record<string, string>>, columns: Readonly<Record<string, Readonly<Record<string, string>>>>, contract: number): Readonly<Record<string, unknown>>[];
|
|
63
|
+
/**
|
|
64
|
+
* The two rules that need the model: a permutation of the key, and a partition on a key column.
|
|
65
|
+
*
|
|
66
|
+
* A renderer without the model repeats the key half against the keys it is given (`effectiveKey`,
|
|
67
|
+
* `partitionExpression`), which keeps the data rule true for a caller that skipped the model.
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkAgainstModel(where: string, entity: string, key: readonly string[], fieldTypes: Readonly<Record<string, string>>, keyOrder: readonly string[] | undefined, partition: PartitionSpec | undefined): void;
|
|
70
|
+
/** The key in physical order: `key_order` when present, the declared order otherwise. */
|
|
71
|
+
export declare function effectiveKey(where: string, entity: string, key: readonly string[], keyOrder: Readonly<Record<string, readonly string[]>>, error?: ErrorClass): string[];
|
|
72
|
+
/** `[function, field]` for a ClickHouse partition, after the key rule is checked again. */
|
|
73
|
+
export declare function partitionExpression(where: string, entity: string, key: readonly string[], partition: PartitionSpec | undefined, error?: ErrorClass): readonly [string, string] | null;
|
|
74
|
+
/**
|
|
75
|
+
* What a dialect renders, for whoever has to propose a physical design for it.
|
|
76
|
+
*
|
|
77
|
+
* The control plane hands the reference's version of this to a model; this port exists so that a
|
|
78
|
+
* TypeScript caller asking the same question gets the same answer, key for key.
|
|
79
|
+
*/
|
|
80
|
+
export declare function capabilities(dialect: string): Record<string, unknown>;
|
|
81
|
+
/** One way an existing table differs from the physical design its layout declares. */
|
|
82
|
+
export interface PhysicalFinding {
|
|
83
|
+
readonly table: string;
|
|
84
|
+
readonly aspect: string;
|
|
85
|
+
readonly declared: string;
|
|
86
|
+
readonly found: string;
|
|
87
|
+
}
|
|
88
|
+
export declare function describeFinding(finding: PhysicalFinding): string;
|
|
89
|
+
export interface DeclaredIndex {
|
|
90
|
+
readonly name: string;
|
|
91
|
+
readonly method: string;
|
|
92
|
+
readonly columns: readonly string[];
|
|
93
|
+
readonly granularity: number | null;
|
|
94
|
+
readonly typeFull: string;
|
|
95
|
+
}
|
|
96
|
+
export interface DeclaredTable {
|
|
97
|
+
readonly table: string;
|
|
98
|
+
readonly key: readonly string[];
|
|
99
|
+
readonly partition: readonly [string, string] | null;
|
|
100
|
+
readonly indexes: readonly DeclaredIndex[];
|
|
101
|
+
}
|
|
102
|
+
export interface PhysicalDesign {
|
|
103
|
+
readonly tables: Readonly<Record<string, string>>;
|
|
104
|
+
readonly indexes: readonly Readonly<Record<string, unknown>>[];
|
|
105
|
+
readonly partitionBy: Readonly<Record<string, PartitionSpec>>;
|
|
106
|
+
readonly keyOrder?: Readonly<Record<string, readonly string[]>>;
|
|
107
|
+
}
|
|
108
|
+
/** The physical expectations of every table a layout names, in table order. */
|
|
109
|
+
export declare function declaredTables(layout: PhysicalDesign, keys: Readonly<Record<string, readonly string[]>>): DeclaredTable[];
|
|
110
|
+
/**
|
|
111
|
+
* ``a, `b c`, d`` -> `["a", "b c", "d"]`: names as ClickHouse's catalogue writes them.
|
|
112
|
+
*
|
|
113
|
+
* A name is bare or backtick-quoted with a backslash escaping the next character - the rule this
|
|
114
|
+
* library writes. Anything else throws: an expression the parser does not understand is not
|
|
115
|
+
* evidence that the table matches.
|
|
116
|
+
*/
|
|
117
|
+
export declare function parseIdentifierList(text: string): string[];
|
|
118
|
+
/** ``toYYYYMM(`at`)`` -> `["toYYYYMM", "at"]`; an empty key -> `null`. */
|
|
119
|
+
export declare function parsePartitionKey(text: string): readonly [string, string] | null;
|
|
120
|
+
/**
|
|
121
|
+
* Throw when a table differs physically. Provisioning calls this; sessions do not.
|
|
122
|
+
*
|
|
123
|
+
* A session only reports (`session.physical`): the difference is performance, and turning it into
|
|
124
|
+
* the application's outage is what requirement 3.6 forbids. Provisioning is where a person applying
|
|
125
|
+
* a map can act on it.
|
|
126
|
+
*/
|
|
127
|
+
export declare function refuseFindings(findings: readonly PhysicalFinding[], error: ErrorClass): void;
|
|
128
|
+
export {};
|