@distrohelena/canton-typescript-sdk 0.1.35 → 0.1.37
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/README.md +159 -51
- package/dist/cjs/core/types/requests/{submit-command-request.js → submit-commands-request.js} +10 -6
- package/dist/cjs/index.js +10 -8
- package/dist/cjs/query/canonical/in-memory-query-evaluator.js +291 -0
- package/dist/cjs/query/canonical/public-identity.js +41 -0
- package/dist/cjs/query/canonical/query-ast.js +2 -0
- package/dist/cjs/query/canonical/query-dataset.js +351 -0
- package/dist/cjs/query/canonical/query-normalizer.js +655 -0
- package/dist/cjs/query/canonical/query-schema.js +86 -0
- package/dist/cjs/query/canton-manager.js +14 -5
- package/dist/cjs/query/errors/query-snapshot-incomplete-error.js +19 -0
- package/dist/cjs/query/grpc/grpc-contract-cache.js +484 -0
- package/dist/cjs/query/grpc/grpc-package-relation-reader.js +218 -0
- package/dist/cjs/query/grpc/grpc-query-client.js +249 -0
- package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +238 -0
- package/dist/cjs/query/grpc/grpc-query-value-mapper.js +204 -0
- package/dist/cjs/query/grpc/grpc-relation-mapper.js +794 -0
- package/dist/cjs/query/pqs/pqs-query-client.js +151 -582
- package/dist/cjs/query/pqs/pqs-relational-sql-compiler.js +138 -95
- package/dist/cjs/query/pqs/pqs-result-shape.js +28 -0
- package/dist/cjs/query/pqs/pqs-schema-profile.js +49 -48
- package/dist/cjs/query/pqs/pqs-sql-compiler.js +343 -289
- package/dist/cjs/query/pqs/pqs-sql-syntax.js +10 -0
- package/dist/cjs/services/command/command-service-client.js +16 -3
- package/dist/cjs/services/commands/command-payload-builder.js +1 -1
- package/dist/cjs/services/commands/command-submission-pipeline.js +21 -6
- package/dist/cjs/services/state/state-service-client.js +56 -0
- package/dist/cjs/testing/runtime/declarative-action-executor.js +3 -3
- package/dist/cjs/transports/grpc/grpc-transport.js +2 -2
- package/dist/cjs/transports/grpc/mappers/commands-mapper.js +6 -6
- package/dist/cjs/transports/grpc/mappers/interactive-command-mapper.js +1 -1
- package/dist/cjs/transports/json/json-transport.js +1 -1
- package/dist/cjs/transports/json/mappers/commands-mapper.js +3 -3
- package/dist/core/transports/transport.interface.d.ts +4 -4
- package/dist/core/types/prepared-command-submission.d.ts +3 -3
- package/dist/core/types/requests/{submit-command-request.d.ts → submit-commands-request.d.ts} +8 -4
- package/dist/core/types/requests/{submit-command-request.js → submit-commands-request.js} +8 -4
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/query/canonical/in-memory-query-evaluator.d.ts +22 -0
- package/dist/query/canonical/in-memory-query-evaluator.js +287 -0
- package/dist/query/canonical/public-identity.d.ts +14 -0
- package/dist/query/canonical/public-identity.js +35 -0
- package/dist/query/canonical/query-ast.d.ts +104 -0
- package/dist/query/canonical/query-ast.js +1 -0
- package/dist/query/canonical/query-dataset.d.ts +63 -0
- package/dist/query/canonical/query-dataset.js +343 -0
- package/dist/query/canonical/query-normalizer.d.ts +10 -0
- package/dist/query/canonical/query-normalizer.js +646 -0
- package/dist/query/canonical/query-schema.d.ts +25 -0
- package/dist/query/canonical/query-schema.js +83 -0
- package/dist/query/canton-manager.js +14 -5
- package/dist/query/errors/query-snapshot-incomplete-error.d.ts +14 -0
- package/dist/query/errors/query-snapshot-incomplete-error.js +15 -0
- package/dist/query/grpc/grpc-contract-cache.d.ts +27 -0
- package/dist/query/grpc/grpc-contract-cache.js +479 -0
- package/dist/query/grpc/grpc-package-relation-reader.d.ts +43 -0
- package/dist/query/grpc/grpc-package-relation-reader.js +213 -0
- package/dist/query/grpc/grpc-query-client.d.ts +42 -0
- package/dist/query/grpc/grpc-query-client.js +245 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +33 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.js +233 -0
- package/dist/query/grpc/grpc-query-value-mapper.d.ts +15 -0
- package/dist/query/grpc/grpc-query-value-mapper.js +195 -0
- package/dist/query/grpc/grpc-relation-mapper.d.ts +67 -0
- package/dist/query/grpc/grpc-relation-mapper.js +789 -0
- package/dist/query/pqs/pqs-query-client.d.ts +10 -20
- package/dist/query/pqs/pqs-query-client.js +154 -585
- package/dist/query/pqs/pqs-relational-sql-compiler.d.ts +9 -17
- package/dist/query/pqs/pqs-relational-sql-compiler.js +136 -95
- package/dist/query/pqs/pqs-result-shape.d.ts +25 -0
- package/dist/query/pqs/pqs-result-shape.js +25 -0
- package/dist/query/pqs/pqs-schema-profile.js +49 -48
- package/dist/query/pqs/pqs-sql-compiler.d.ts +18 -3
- package/dist/query/pqs/pqs-sql-compiler.js +337 -288
- package/dist/query/pqs/pqs-sql-syntax.d.ts +2 -0
- package/dist/query/pqs/pqs-sql-syntax.js +6 -0
- package/dist/query/query-client.d.ts +15 -0
- package/dist/services/command/command-service-client.d.ts +9 -4
- package/dist/services/command/command-service-client.js +16 -3
- package/dist/services/command-submission/command-submission-service-client.d.ts +2 -2
- package/dist/services/commands/command-payload-builder.d.ts +2 -2
- package/dist/services/commands/command-payload-builder.js +1 -1
- package/dist/services/commands/command-submission-pipeline.d.ts +5 -4
- package/dist/services/commands/command-submission-pipeline.js +21 -6
- package/dist/services/state/state-service-client.d.ts +4 -0
- package/dist/services/state/state-service-client.js +56 -0
- package/dist/testing/runtime/declarative-action-executor.js +3 -3
- package/dist/transports/grpc/grpc-transport.d.ts +4 -4
- package/dist/transports/grpc/grpc-transport.js +3 -3
- package/dist/transports/grpc/mappers/commands-mapper.d.ts +3 -3
- package/dist/transports/grpc/mappers/commands-mapper.js +4 -4
- package/dist/transports/grpc/mappers/interactive-command-mapper.d.ts +3 -3
- package/dist/transports/grpc/mappers/interactive-command-mapper.js +1 -1
- package/dist/transports/json/json-transport.d.ts +2 -2
- package/dist/transports/json/json-transport.js +2 -2
- package/dist/transports/json/mappers/commands-mapper.d.ts +2 -2
- package/dist/transports/json/mappers/commands-mapper.js +2 -2
- package/package.json +8 -3
- package/dist/cjs/query/grpc/grpc-contract-query-client.js +0 -178
- package/dist/query/grpc/grpc-contract-query-client.d.ts +0 -33
- package/dist/query/grpc/grpc-contract-query-client.js +0 -174
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
import { PqsRelation, PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
|
|
2
|
+
import type { NormalizedAggregateQuery, NormalizedCountQuery, NormalizedFindManyQuery, NormalizedGroupByQuery } from "../canonical/query-ast.js";
|
|
3
|
+
import { type PqsRelationResultShape } from "./pqs-result-shape.js";
|
|
4
|
+
export type { PqsIncludedResultShape, PqsJsonResultProjection, PqsRelationResultShape, PqsSelectedScalarField } from "./pqs-result-shape.js";
|
|
2
5
|
export interface CompiledPqsRelationQuery {
|
|
3
6
|
readonly text: string;
|
|
4
7
|
readonly values: readonly unknown[];
|
|
5
8
|
}
|
|
6
|
-
interface
|
|
7
|
-
readonly
|
|
8
|
-
readonly select?: Readonly<Record<string, boolean>>;
|
|
9
|
-
readonly orderBy?: readonly Readonly<Record<string, "asc" | "desc">>[];
|
|
10
|
-
readonly take?: number;
|
|
11
|
-
readonly skip?: number;
|
|
9
|
+
export interface CompiledPqsRelationFindManyQuery extends CompiledPqsRelationQuery {
|
|
10
|
+
readonly resultShape: PqsRelationResultShape;
|
|
12
11
|
}
|
|
13
|
-
export declare function compilePqsRelationFindMany(relation: PqsRelation,
|
|
14
|
-
export declare function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
readonly count?: true;
|
|
18
|
-
readonly min?: readonly string[];
|
|
19
|
-
readonly max?: readonly string[];
|
|
20
|
-
readonly sum?: readonly string[];
|
|
21
|
-
};
|
|
22
|
-
}, profile: PqsSchemaProfileV1, parameterOffset?: number): CompiledPqsRelationQuery;
|
|
23
|
-
export {};
|
|
12
|
+
export declare function compilePqsRelationFindMany(relation: PqsRelation, query: NormalizedFindManyQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationFindManyQuery;
|
|
13
|
+
export declare function compilePqsRelationCount(relation: PqsRelation, query: NormalizedCountQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
|
|
14
|
+
export declare function compilePqsRelationAggregate(relation: PqsRelation, query: NormalizedAggregateQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
|
|
15
|
+
export declare function compilePqsRelationGroupBy(relation: PqsRelation, query: NormalizedGroupByQuery, profile: PqsSchemaProfileV1): CompiledPqsRelationQuery;
|
|
@@ -1,137 +1,166 @@
|
|
|
1
1
|
import { PqsSchemaProfileV1, pqsRelationMetadata } from "./pqs-schema-profile.js";
|
|
2
2
|
import { pqsRelationEdges } from "./pqs-schema-profile.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { compileCanonicalPhysicalField, compileCanonicalPhysicalIncludes, compileCanonicalPhysicalPredicate } from "./pqs-sql-compiler.js";
|
|
4
|
+
import { quotePqsIdentifier } from "./pqs-sql-syntax.js";
|
|
5
|
+
import { compilePqsResultShape } from "./pqs-result-shape.js";
|
|
6
|
+
export function compilePqsRelationFindMany(relation, query, profile) {
|
|
7
|
+
assertCanonicalPhysicalFindMany(relation, query);
|
|
5
8
|
const metadata = pqsRelationMetadata[relation];
|
|
9
|
+
const source = logicalTypeRootSource(relation, profile);
|
|
6
10
|
const values = [];
|
|
7
11
|
const add = (value) => {
|
|
8
12
|
values.push(value);
|
|
9
13
|
return `$${values.length}`;
|
|
10
14
|
};
|
|
11
|
-
const fields =
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
15
|
+
const fields = selectedCanonicalFields(relation, metadata, query.select?.fields, (query.select?.json.length ?? 0) > 0);
|
|
16
|
+
const predicate = query.predicate === undefined ? "" : compileCanonicalPhysicalPredicate(relation, query.predicate, "", profile, add);
|
|
17
|
+
const where = predicate.length === 0 ? "" : ` where ${predicate}`;
|
|
18
|
+
const orderBy = compileCanonicalOrderBy(relation, metadata, query.orderBy, profile);
|
|
19
|
+
const limit = query.take === undefined ? "" : ` limit ${add(query.take)}`;
|
|
20
|
+
const offset = query.skip === 0 ? "" : ` offset ${add(query.skip)}`;
|
|
21
|
+
const included = compileCanonicalPhysicalIncludes(relation, source.reference, query.includes, profile, add);
|
|
22
|
+
const json = compileCanonicalJsonSelections(relation, query.select, profile, add);
|
|
23
|
+
const shape = compilePqsResultShape(relation, "many", query.select, query.includes);
|
|
16
24
|
return {
|
|
17
|
-
text:
|
|
25
|
+
text: `${source.withClause}select ${[...fields.map(([field]) => `${compileCanonicalPhysicalField(relation, field, "", profile)} as "${field}"`), ...json, ...included.map((include) => include.selection)].join(", ")} from ${source.relation}${where}${orderBy}${limit}${offset}`,
|
|
18
26
|
values,
|
|
27
|
+
resultShape: shape,
|
|
19
28
|
};
|
|
20
29
|
}
|
|
21
|
-
export function
|
|
22
|
-
|
|
30
|
+
export function compilePqsRelationCount(relation, query, profile) {
|
|
31
|
+
assertCanonicalPhysicalQuery(relation, query, "count");
|
|
32
|
+
const source = logicalTypeRootSource(relation, profile);
|
|
33
|
+
const values = [];
|
|
34
|
+
const add = (value) => {
|
|
35
|
+
values.push(value);
|
|
36
|
+
return `$${values.length}`;
|
|
37
|
+
};
|
|
38
|
+
const predicate = query.predicate === undefined ? "" : ` where ${compileCanonicalPhysicalPredicate(relation, query.predicate, "", profile, add)}`;
|
|
39
|
+
return { text: `${source.withClause}select count(*)::text as count from ${source.relation}${predicate}`, values };
|
|
40
|
+
}
|
|
41
|
+
export function compilePqsRelationAggregate(relation, query, profile) {
|
|
42
|
+
assertCanonicalPhysicalQuery(relation, query, "aggregate");
|
|
43
|
+
const metadata = pqsRelationMetadata[relation];
|
|
44
|
+
const source = logicalTypeRootSource(relation, profile);
|
|
45
|
+
const values = [];
|
|
46
|
+
const add = (value) => {
|
|
47
|
+
values.push(value);
|
|
48
|
+
return `$${values.length}`;
|
|
49
|
+
};
|
|
50
|
+
const selected = [];
|
|
51
|
+
if (query.aggregates.count)
|
|
52
|
+
selected.push("count(*)::text as count");
|
|
53
|
+
for (const [operation, fields] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]]) {
|
|
54
|
+
for (const name of fields) {
|
|
55
|
+
if (!metadata.numericFields.includes(name))
|
|
56
|
+
throw new Error(`${name} is not a numeric aggregate field of ${relation}`);
|
|
57
|
+
selected.push(`${operation}(${compileCanonicalPhysicalField(relation, name, "", profile)})::text as "${operation}_${name}"`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (selected.length === 0)
|
|
61
|
+
throw new Error("aggregate must request at least one result");
|
|
62
|
+
const predicate = query.predicate === undefined ? "" : ` where ${compileCanonicalPhysicalPredicate(relation, query.predicate, "", profile, add)}`;
|
|
63
|
+
return { text: `${source.withClause}select ${selected.join(", ")} from ${source.relation}${predicate}`, values };
|
|
64
|
+
}
|
|
65
|
+
export function compilePqsRelationGroupBy(relation, query, profile) {
|
|
66
|
+
assertCanonicalPhysicalQuery(relation, query, "groupBy");
|
|
67
|
+
if (query.by.length === 0 || (!query.aggregates.count && query.aggregates.min.length === 0 && query.aggregates.max.length === 0 && query.aggregates.sum.length === 0))
|
|
23
68
|
throw new Error("groupBy requires a key and aggregate");
|
|
24
69
|
const metadata = pqsRelationMetadata[relation];
|
|
70
|
+
const source = logicalTypeRootSource(relation, profile);
|
|
25
71
|
const root = relation === "__events" ? "event" : "root";
|
|
26
72
|
const joins = [];
|
|
27
73
|
const expressions = [];
|
|
28
74
|
const selected = [];
|
|
29
75
|
const values = [];
|
|
30
|
-
const add = (value) => { values.push(value); return `$${
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
76
|
+
const add = (value) => { values.push(value); return `$${values.length}`; };
|
|
77
|
+
const predicate = query.predicate === undefined ? "" : ` where ${compileCanonicalPhysicalPredicate(relation, query.predicate, `"${root}"`, profile, add)}`;
|
|
78
|
+
for (const key of query.by) {
|
|
79
|
+
if (key.kind === "field") {
|
|
80
|
+
const fieldName = key.path[0];
|
|
81
|
+
const column = field(relation, metadata, fieldName);
|
|
82
|
+
const expression = metadata.arrayFields.includes(fieldName)
|
|
83
|
+
? `"${fieldName}".value`
|
|
84
|
+
: compileCanonicalPhysicalField(relation, fieldName, `"${root}"`, profile);
|
|
85
|
+
if (metadata.arrayFields.includes(fieldName))
|
|
86
|
+
joins.push(`cross join lateral unnest("${root}"."${column}") as "${fieldName}"(value)`);
|
|
39
87
|
expressions.push(expression);
|
|
40
|
-
selected.push(`${expression} as "${
|
|
88
|
+
selected.push(`${expression} as "${fieldName}"`);
|
|
41
89
|
continue;
|
|
42
90
|
}
|
|
43
|
-
if (key ===
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const projection = nested;
|
|
48
|
-
if (typeof projection.name !== "string" || !Array.isArray(projection.path) || !projection.path.every((segment) => typeof segment === "string" && segment.length > 0) || !["text", "numeric", "boolean", "timestamp"].includes(String(projection.as)) || !PqsSchemaProfileV1.jsonField(relation, edgeName))
|
|
49
|
-
throw new Error("invalid profiled JSON group key");
|
|
50
|
-
const column = field(relation, metadata, edgeName);
|
|
51
|
-
const text = `"${root}"."${column}" #>> ${add(projection.path)}::text[]`;
|
|
52
|
-
const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
|
|
91
|
+
if (key.kind === "json") {
|
|
92
|
+
field(relation, metadata, key.field);
|
|
93
|
+
const text = `${compileCanonicalPhysicalField(relation, key.field, `"${root}"`, profile)} #>> ${add(key.path)}::text[]`;
|
|
94
|
+
const expression = key.as === "text" ? text : key.as === "numeric" ? `(${text})::numeric` : key.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
|
|
53
95
|
expressions.push(expression);
|
|
54
|
-
selected.push(`${expression} as
|
|
96
|
+
selected.push(`${expression} as ${quotePqsIdentifier(key.name)}`);
|
|
55
97
|
continue;
|
|
56
98
|
}
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
throw new Error("invalid profiled time bucket");
|
|
61
|
-
const column = field(relation, metadata, edgeName);
|
|
62
|
-
const expression = `date_trunc('${bucket}', "${root}"."${column}")`;
|
|
99
|
+
if (key.kind === "bucket" && key.path.length === 1) {
|
|
100
|
+
field(relation, metadata, key.path[0]);
|
|
101
|
+
const expression = `date_trunc('${key.bucket}', ${compileCanonicalPhysicalField(relation, key.path[0], `"${root}"`, profile)})`;
|
|
63
102
|
expressions.push(expression);
|
|
64
|
-
selected.push(`${expression} as "${
|
|
103
|
+
selected.push(`${expression} as "${key.path[0]}_${key.bucket}"`);
|
|
65
104
|
continue;
|
|
66
105
|
}
|
|
106
|
+
const edgeName = key.path[0];
|
|
107
|
+
const fieldName = key.path[1];
|
|
67
108
|
const edge = pqsRelationEdges[relation]?.[edgeName];
|
|
68
|
-
if (edge === undefined || edge.cardinality !== "one"
|
|
109
|
+
if (edge === undefined || edge.cardinality !== "one")
|
|
69
110
|
throw new Error("group key must follow a profiled to-one edge");
|
|
70
|
-
|
|
71
|
-
const bucket = bucketValue !== null && typeof bucketValue === "object" ? bucketValue.bucket : undefined;
|
|
72
|
-
if (typeof bucket !== "string" || !["hour", "day", "week", "month"].includes(bucket) || !PqsSchemaProfileV1.bucketField(edge.target, fieldName))
|
|
73
|
-
throw new Error("invalid profiled time bucket");
|
|
74
|
-
const targetColumn = field(edge.target, pqsRelationMetadata[edge.target], fieldName);
|
|
111
|
+
field(edge.target, pqsRelationMetadata[edge.target], fieldName);
|
|
75
112
|
joins.push(`join ${profile.relation(edge.target)} "${edgeName}" on "${edgeName}"."${edge.targetColumn}" = "${root}"."${edge.sourceColumn}"`);
|
|
76
|
-
const expression = `date_trunc('${bucket}',
|
|
113
|
+
const expression = `date_trunc('${key.bucket}', ${compileCanonicalPhysicalField(edge.target, fieldName, `"${edgeName}"`, profile)})`;
|
|
77
114
|
expressions.push(expression);
|
|
78
|
-
selected.push(`${expression} as "${edgeName}_${fieldName}_${bucket}"`);
|
|
115
|
+
selected.push(`${expression} as "${edgeName}_${fieldName}_${key.bucket}"`);
|
|
79
116
|
}
|
|
80
|
-
if (
|
|
117
|
+
if (query.aggregates.count)
|
|
81
118
|
selected.push("count(*)::text as count");
|
|
82
|
-
for (const [operation, fields] of [["min",
|
|
83
|
-
for (const name of fields
|
|
119
|
+
for (const [operation, fields] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]])
|
|
120
|
+
for (const name of fields) {
|
|
84
121
|
if (!metadata.numericFields.includes(name))
|
|
85
122
|
throw new Error(`${name} is not a numeric aggregate field of ${relation}`);
|
|
86
|
-
|
|
123
|
+
field(relation, metadata, name);
|
|
124
|
+
selected.push(`${operation}(${compileCanonicalPhysicalField(relation, name, `"${root}"`, profile)})::text as "${operation}_${name}"`);
|
|
87
125
|
}
|
|
88
|
-
return { text:
|
|
126
|
+
return { text: `${source.withClause}select ${selected.join(", ")} from ${source.relation} "${root}"${joins.length === 0 ? "" : ` ${joins.join(" ")}`}${predicate} group by ${expressions.join(", ")}`, values };
|
|
127
|
+
}
|
|
128
|
+
function logicalTypeRootSource(relation, profile) {
|
|
129
|
+
if (relation !== "__contract_tpe" && relation !== "__exercise_tpe") {
|
|
130
|
+
const physical = profile.relation(relation);
|
|
131
|
+
return { withClause: "", relation: physical, reference: physical };
|
|
132
|
+
}
|
|
133
|
+
const publicKey = compileCanonicalPhysicalField(relation, "pk", '"physical_type"', profile);
|
|
134
|
+
const logicalRelation = '"logical_type_root"';
|
|
135
|
+
return {
|
|
136
|
+
withClause: `with ${logicalRelation} as (select distinct on (${publicKey}) "physical_type".* from ${profile.relation(relation)} "physical_type" order by ${publicKey}, "physical_type"."pk" asc) `,
|
|
137
|
+
relation: logicalRelation,
|
|
138
|
+
reference: logicalRelation,
|
|
139
|
+
};
|
|
89
140
|
}
|
|
90
|
-
function
|
|
91
|
-
const fields =
|
|
92
|
-
|
|
93
|
-
: Object.entries(select).filter(([, enabled]) => enabled).map(([name]) => [name, field(relation, metadata, name)]);
|
|
94
|
-
if (fields.length === 0)
|
|
141
|
+
function selectedCanonicalFields(relation, metadata, selected, allowEmpty = false) {
|
|
142
|
+
const fields = selected === undefined ? Object.entries(metadata.fields) : selected.map((name) => [name, field(relation, metadata, name)]);
|
|
143
|
+
if (fields.length === 0 && !allowEmpty)
|
|
95
144
|
throw new Error("select must include at least one field");
|
|
96
145
|
return fields;
|
|
97
146
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
conditions.push(`"${column}" = ${add(operand)}`);
|
|
108
|
-
else if (operator === "in" && Array.isArray(operand))
|
|
109
|
-
conditions.push(operand.length === 0 ? "false" : `"${column}" = any(${add(operand)})`);
|
|
110
|
-
else if (operator === "is" && operand === null)
|
|
111
|
-
conditions.push(`"${column}" is null`);
|
|
112
|
-
else if (operator === "isNot" && operand === null)
|
|
113
|
-
conditions.push(`"${column}" is not null`);
|
|
114
|
-
else if (["lt", "lte", "gt", "gte", "like", "ilike"].includes(operator)) {
|
|
115
|
-
const sql = { lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[operator];
|
|
116
|
-
conditions.push(`"${column}" ${sql} ${add(operand)}`);
|
|
117
|
-
}
|
|
118
|
-
else
|
|
119
|
-
throw new Error(`${operator} is not supported for ${name}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return conditions.length === 0 ? "" : ` where ${conditions.join(" and ")}`;
|
|
147
|
+
function compileCanonicalJsonSelections(relation, selection, profile, add) {
|
|
148
|
+
return (selection?.json ?? []).map((projection) => {
|
|
149
|
+
if (!PqsSchemaProfileV1.jsonField(relation, projection.field))
|
|
150
|
+
throw new Error(`${projection.field} is not a JSON field of ${relation}`);
|
|
151
|
+
field(relation, pqsRelationMetadata[relation], projection.field);
|
|
152
|
+
const text = `${compileCanonicalPhysicalField(relation, projection.field, "", profile)} #>> ${add(projection.path)}::text[]`;
|
|
153
|
+
const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
|
|
154
|
+
return `${expression} as ${quotePqsIdentifier(projection.name)}`;
|
|
155
|
+
});
|
|
123
156
|
}
|
|
124
|
-
function
|
|
125
|
-
if (orderBy ===
|
|
157
|
+
function compileCanonicalOrderBy(relation, metadata, orderBy, profile) {
|
|
158
|
+
if (orderBy.length === 0)
|
|
126
159
|
return "";
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const stableKey = metadata.uniqueKeys[0]?.[0];
|
|
132
|
-
if (stableKey !== undefined && !ordered.some(([column]) => column === metadata.fields[stableKey]))
|
|
133
|
-
ordered.push([metadata.fields[stableKey], "asc"]);
|
|
134
|
-
return ` order by ${ordered.map(([column, direction]) => `"${column}" ${direction}`).join(", ")}`;
|
|
160
|
+
return ` order by ${orderBy.map((order) => {
|
|
161
|
+
field(relation, metadata, order.path[0]);
|
|
162
|
+
return `${compileCanonicalPhysicalField(relation, order.path[0], "", profile)} ${order.direction}`;
|
|
163
|
+
}).join(", ")}`;
|
|
135
164
|
}
|
|
136
165
|
function field(relation, metadata, name) {
|
|
137
166
|
const column = metadata.fields[name];
|
|
@@ -139,8 +168,20 @@ function field(relation, metadata, name) {
|
|
|
139
168
|
throw new Error(`${name} is not a field of ${relation}`);
|
|
140
169
|
return column;
|
|
141
170
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
171
|
+
function assertCanonicalPhysicalFindMany(relation, query) {
|
|
172
|
+
if (query === null || typeof query !== "object" || query.kind !== "findMany" || !Array.isArray(query.includes) || !Array.isArray(query.orderBy)) {
|
|
173
|
+
throw new Error("compilePqsRelationFindMany requires a canonical findMany query");
|
|
174
|
+
}
|
|
175
|
+
if (relationForCanonical(query.relation) !== relation)
|
|
176
|
+
throw new Error(`Canonical relation ${query.relation} does not match ${relation}`);
|
|
177
|
+
}
|
|
178
|
+
function assertCanonicalPhysicalQuery(relation, query, kind) {
|
|
179
|
+
if (query === null || typeof query !== "object" || query.kind !== kind) {
|
|
180
|
+
throw new Error(`compilePqsRelation${kind[0].toUpperCase()}${kind.slice(1)} requires a canonical ${kind} query`);
|
|
181
|
+
}
|
|
182
|
+
if (relationForCanonical(query.relation) !== relation)
|
|
183
|
+
throw new Error(`Canonical relation ${query.relation} does not match ${relation}`);
|
|
184
|
+
}
|
|
185
|
+
function relationForCanonical(relation) {
|
|
186
|
+
return { contracts: "__contracts", contractTypes: "__contract_tpe", events: "__events", exercises: "__exercises", exerciseTypes: "__exercise_tpe", packages: "__packages", transactions: "__transactions", watermark: "__watermark" }[relation];
|
|
146
187
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NormalizedInclude, NormalizedSelection } from "../canonical/query-ast.js";
|
|
2
|
+
import { PqsRelation } from "./pqs-schema-profile.js";
|
|
3
|
+
export interface PqsSelectedScalarField {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PqsJsonResultProjection {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly field: string;
|
|
9
|
+
readonly path: readonly string[];
|
|
10
|
+
readonly as: "text" | "numeric" | "boolean" | "timestamp";
|
|
11
|
+
}
|
|
12
|
+
export interface PqsIncludedResultShape {
|
|
13
|
+
readonly edge: string;
|
|
14
|
+
readonly target: PqsRelation;
|
|
15
|
+
readonly cardinality: "one" | "many";
|
|
16
|
+
readonly shape: PqsRelationResultShape;
|
|
17
|
+
}
|
|
18
|
+
export interface PqsRelationResultShape {
|
|
19
|
+
readonly relation: PqsRelation;
|
|
20
|
+
readonly cardinality: "one" | "many";
|
|
21
|
+
readonly fields: readonly PqsSelectedScalarField[];
|
|
22
|
+
readonly json: readonly PqsJsonResultProjection[];
|
|
23
|
+
readonly includes: readonly PqsIncludedResultShape[];
|
|
24
|
+
}
|
|
25
|
+
export declare function compilePqsResultShape(relation: PqsRelation, cardinality: "one" | "many", selection: NormalizedSelection | undefined, includes: readonly NormalizedInclude[]): PqsRelationResultShape;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { pqsRelationEdges, pqsRelationMetadata } from "./pqs-schema-profile.js";
|
|
2
|
+
export function compilePqsResultShape(relation, cardinality, selection, includes) {
|
|
3
|
+
const fields = relation === "__contracts"
|
|
4
|
+
? selection?.fields ?? ["contractId", "templateId", "packageId", "payload", "witnesses", "createdEventOffset", "createdAt", "archivedEventOffset", "archivedAt", "active"]
|
|
5
|
+
: selection?.fields ?? Object.keys(pqsRelationMetadata[relation].fields);
|
|
6
|
+
const shape = {
|
|
7
|
+
relation,
|
|
8
|
+
cardinality,
|
|
9
|
+
fields: fields.map((name) => Object.freeze({ name })),
|
|
10
|
+
json: (selection?.json ?? []).map((projection) => Object.freeze({ ...projection, path: Object.freeze([...projection.path]) })),
|
|
11
|
+
includes: includes.map((include) => {
|
|
12
|
+
const edge = pqsRelationEdges[relation]?.[include.edge];
|
|
13
|
+
if (edge === undefined || edge.target !== relationForCanonical(include.relation) || edge.cardinality !== include.cardinality)
|
|
14
|
+
throw new Error(`Invalid canonical include ${include.edge}`);
|
|
15
|
+
return Object.freeze({ edge: include.edge, target: edge.target, cardinality: include.cardinality, shape: compilePqsResultShape(edge.target, include.cardinality, include.select, include.includes) });
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
Object.freeze(shape.fields);
|
|
19
|
+
Object.freeze(shape.json);
|
|
20
|
+
Object.freeze(shape.includes);
|
|
21
|
+
return Object.freeze(shape);
|
|
22
|
+
}
|
|
23
|
+
function relationForCanonical(relation) {
|
|
24
|
+
return { contracts: "__contracts", contractTypes: "__contract_tpe", events: "__events", exercises: "__exercises", exerciseTypes: "__exercise_tpe", packages: "__packages", transactions: "__transactions", watermark: "__watermark" }[relation];
|
|
25
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PqsSchemaProfileError } from "../errors/pqs-schema-profile-error.js";
|
|
2
|
+
import { queryRelationEdges, queryRelationMetadata, } from "../canonical/query-schema.js";
|
|
2
3
|
const schemaIdentifier = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
4
|
export const requiredPqsRelations = [
|
|
4
5
|
"__contracts",
|
|
@@ -10,56 +11,56 @@ export const requiredPqsRelations = [
|
|
|
10
11
|
"__transactions",
|
|
11
12
|
"__watermark",
|
|
12
13
|
];
|
|
13
|
-
|
|
14
|
-
__contracts:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "contract_tpe_pk", cardinality: "many", nullable: false },
|
|
23
|
-
},
|
|
24
|
-
__events: {
|
|
25
|
-
transaction: { target: "__transactions", sourceColumn: "tx_ix", targetColumn: "ix", cardinality: "one", nullable: false },
|
|
26
|
-
exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "exercise_event_pk", cardinality: "many", nullable: false },
|
|
27
|
-
},
|
|
28
|
-
__exercises: {
|
|
29
|
-
exerciseType: { target: "__exercise_tpe", sourceColumn: "tpe_pk", targetColumn: "pk", cardinality: "one", nullable: false },
|
|
30
|
-
contractType: { target: "__contract_tpe", sourceColumn: "contract_tpe_pk", targetColumn: "pk", cardinality: "one", nullable: false },
|
|
31
|
-
event: { target: "__events", sourceColumn: "exercise_event_pk", targetColumn: "pk", cardinality: "one", nullable: true },
|
|
32
|
-
transaction: { target: "__transactions", sourceColumn: "exercised_at_ix", targetColumn: "ix", cardinality: "one", nullable: true },
|
|
33
|
-
package: { target: "__packages", sourceColumn: "package_pk", targetColumn: "pk", cardinality: "one", nullable: false },
|
|
34
|
-
contract: { target: "__contracts", sourceColumn: "contract_id", targetColumn: "contract_id", cardinality: "one", nullable: false },
|
|
35
|
-
},
|
|
36
|
-
__exercise_tpe: { exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "tpe_pk", cardinality: "many", nullable: false } },
|
|
37
|
-
__packages: { exercises: { target: "__exercises", sourceColumn: "pk", targetColumn: "package_pk", cardinality: "many", nullable: false } },
|
|
38
|
-
__transactions: {
|
|
39
|
-
events: { target: "__events", sourceColumn: "ix", targetColumn: "tx_ix", cardinality: "many", nullable: false },
|
|
40
|
-
createdContracts: { target: "__contracts", sourceColumn: "ix", targetColumn: "created_at_ix", cardinality: "many", nullable: false },
|
|
41
|
-
archivedContracts: { target: "__contracts", sourceColumn: "ix", targetColumn: "archived_at_ix", cardinality: "many", nullable: false },
|
|
42
|
-
exercises: { target: "__exercises", sourceColumn: "ix", targetColumn: "exercised_at_ix", cardinality: "many", nullable: false },
|
|
43
|
-
},
|
|
14
|
+
const queryRelationByPqsRelation = {
|
|
15
|
+
__contracts: "contracts",
|
|
16
|
+
__contract_tpe: "contractTypes",
|
|
17
|
+
__events: "events",
|
|
18
|
+
__exercises: "exercises",
|
|
19
|
+
__exercise_tpe: "exerciseTypes",
|
|
20
|
+
__packages: "packages",
|
|
21
|
+
__transactions: "transactions",
|
|
22
|
+
__watermark: "watermark",
|
|
44
23
|
};
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
24
|
+
const pqsRelationByQueryRelation = {
|
|
25
|
+
contracts: "__contracts",
|
|
26
|
+
contractTypes: "__contract_tpe",
|
|
27
|
+
events: "__events",
|
|
28
|
+
exercises: "__exercises",
|
|
29
|
+
exerciseTypes: "__exercise_tpe",
|
|
30
|
+
packages: "__packages",
|
|
31
|
+
transactions: "__transactions",
|
|
32
|
+
watermark: "__watermark",
|
|
49
33
|
};
|
|
50
|
-
const
|
|
51
|
-
|
|
34
|
+
const pqsEdgeColumns = {
|
|
35
|
+
__contracts: { contractType: { sourceColumn: "tpe_pk", targetColumn: "pk" }, createdTransaction: { sourceColumn: "created_at_ix", targetColumn: "ix" }, archivedTransaction: { sourceColumn: "archived_at_ix", targetColumn: "ix" }, exercises: { sourceColumn: "contract_id", targetColumn: "contract_id" } },
|
|
36
|
+
__contract_tpe: { contracts: { sourceColumn: "pk", targetColumn: "tpe_pk" }, exercises: { sourceColumn: "pk", targetColumn: "contract_tpe_pk" } },
|
|
37
|
+
__events: { transaction: { sourceColumn: "tx_ix", targetColumn: "ix" }, exercises: { sourceColumn: "pk", targetColumn: "exercise_event_pk" } },
|
|
38
|
+
__exercises: { exerciseType: { sourceColumn: "tpe_pk", targetColumn: "pk" }, contractType: { sourceColumn: "contract_tpe_pk", targetColumn: "pk" }, event: { sourceColumn: "exercise_event_pk", targetColumn: "pk" }, transaction: { sourceColumn: "exercised_at_ix", targetColumn: "ix" }, package: { sourceColumn: "package_pk", targetColumn: "pk" }, contract: { sourceColumn: "contract_id", targetColumn: "contract_id" } },
|
|
39
|
+
__exercise_tpe: { exercises: { sourceColumn: "pk", targetColumn: "tpe_pk" } },
|
|
40
|
+
__packages: { exercises: { sourceColumn: "pk", targetColumn: "package_pk" } },
|
|
41
|
+
__transactions: { events: { sourceColumn: "ix", targetColumn: "tx_ix" }, createdContracts: { sourceColumn: "ix", targetColumn: "created_at_ix" }, archivedContracts: { sourceColumn: "ix", targetColumn: "archived_at_ix" }, exercises: { sourceColumn: "ix", targetColumn: "exercised_at_ix" } },
|
|
52
42
|
};
|
|
53
|
-
export const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
43
|
+
export const pqsRelationEdges = Object.fromEntries(requiredPqsRelations.map((relation) => [relation, Object.fromEntries(Object.entries(queryRelationEdges[queryRelationByPqsRelation[relation]] ?? {}).map(([name, edge]) => {
|
|
44
|
+
const columns = pqsEdgeColumns[relation]?.[name];
|
|
45
|
+
if (columns === undefined) {
|
|
46
|
+
throw new Error(`Missing physical PQS edge mapping for ${relation}.${name}`);
|
|
47
|
+
}
|
|
48
|
+
return [name, { target: pqsRelationByQueryRelation[edge.target], ...columns, cardinality: edge.cardinality, nullable: edge.nullable }];
|
|
49
|
+
}))]));
|
|
50
|
+
const pqsFields = {
|
|
51
|
+
__contracts: {},
|
|
52
|
+
__contract_tpe: { pk: "pk", payloadType: "payload_type", aliases: "aliases", packageName: "package_name", moduleName: "module_name", entityName: "entity_name", templateFqn: "template_fqn" },
|
|
53
|
+
__events: { pk: "pk", txIx: "tx_ix", eventId: "event_id", type: "type" },
|
|
54
|
+
__exercises: { tpePk: "tpe_pk", contractTpePk: "contract_tpe_pk", exerciseEventPk: "exercise_event_pk", exercisedAtIx: "exercised_at_ix", contractId: "contract_id", argument: "argument", result: "result", redactionId: "redaction_id", packagePk: "package_pk", controllers: "controllers", lastDescendantNodeId: "last_descendant_node_id", witnesses: "witnesses" },
|
|
55
|
+
__exercise_tpe: { pk: "pk", choice: "choice", consuming: "consuming", aliases: "aliases", packageName: "package_name", moduleName: "module_name", entityName: "entity_name", templateFqn: "template_fqn", choiceFqn: "choice_fqn" },
|
|
56
|
+
__packages: { pk: "pk", name: "name", version: "version", id: "id" },
|
|
57
|
+
__transactions: { ix: "ix", offset: "offset", transactionId: "transaction_id", effectiveAt: "effective_at", workflowId: "workflow_id", domainId: "domain_id", traceContext: "trace_context", externalTransactionHash: "external_transaction_hash", paidTrafficCost: "paid_traffic_cost" },
|
|
58
|
+
__watermark: { singleton: "singleton", ix: "ix", offset: "offset", instanceId: "instance_id" },
|
|
62
59
|
};
|
|
60
|
+
export const pqsRelationMetadata = Object.fromEntries(requiredPqsRelations.map((relation) => {
|
|
61
|
+
const logical = queryRelationMetadata[queryRelationByPqsRelation[relation]];
|
|
62
|
+
return [relation, { fields: pqsFields[relation], uniqueKeys: logical.uniqueKeys, numericFields: logical.numericFields, arrayFields: logical.arrayFields, dateFields: logical.dateFields, binaryFields: logical.binaryFields, stringFields: logical.stringFields }];
|
|
63
|
+
}));
|
|
63
64
|
export const requiredPqsColumns = {
|
|
64
65
|
__contracts: [
|
|
65
66
|
"tpe_pk",
|
|
@@ -140,10 +141,10 @@ export class PqsSchemaProfileV1 {
|
|
|
140
141
|
return `${quoteIdentifier(this.schema)}.${quoteIdentifier(relation)}`;
|
|
141
142
|
}
|
|
142
143
|
static jsonField(relation, field) {
|
|
143
|
-
return
|
|
144
|
+
return queryRelationMetadata[queryRelationByPqsRelation[relation]].jsonFields.includes(field);
|
|
144
145
|
}
|
|
145
146
|
static bucketField(relation, field) {
|
|
146
|
-
return
|
|
147
|
+
return queryRelationMetadata[queryRelationByPqsRelation[relation]].bucketFields.includes(field);
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
export async function validatePqsSchemaAsync(client, profile) {
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { NormalizedAggregateQuery, NormalizedCountQuery, NormalizedFindManyQuery, NormalizedGroupByQuery, NormalizedInclude, QueryPredicate } from "../canonical/query-ast.js";
|
|
2
2
|
import { PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
|
|
3
|
+
import { PqsRelation } from "./pqs-schema-profile.js";
|
|
4
|
+
import { type PqsRelationResultShape } from "./pqs-result-shape.js";
|
|
3
5
|
export interface CompiledPqsQuery {
|
|
4
6
|
readonly text: string;
|
|
5
7
|
readonly values: readonly unknown[];
|
|
6
8
|
}
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
+
export interface CompiledPqsInclude {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly expression: string;
|
|
12
|
+
readonly selection: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CompiledContractFindManyQuery extends CompiledPqsQuery {
|
|
15
|
+
readonly resultShape: PqsRelationResultShape;
|
|
16
|
+
}
|
|
17
|
+
export declare function compileCanonicalPhysicalField(relation: PqsRelation, field: string, alias: string, profile: PqsSchemaProfileV1): string;
|
|
18
|
+
export declare function compileContractFindMany(query: NormalizedFindManyQuery, profile: PqsSchemaProfileV1): CompiledContractFindManyQuery;
|
|
19
|
+
export declare function compileContractCount(query: NormalizedCountQuery, profile: PqsSchemaProfileV1): CompiledPqsQuery;
|
|
20
|
+
export declare function compileContractAggregate(query: NormalizedAggregateQuery, profile: PqsSchemaProfileV1): CompiledPqsQuery;
|
|
21
|
+
export declare function compileCanonicalPhysicalIncludes(source: PqsRelation, parentAlias: string, includes: readonly NormalizedInclude[], profile: PqsSchemaProfileV1, add: (value: unknown) => string): readonly CompiledPqsInclude[];
|
|
22
|
+
export declare function compileCanonicalPhysicalPredicate(relation: PqsRelation, predicate: QueryPredicate, alias: string, profile: PqsSchemaProfileV1, add: (value: unknown) => string): string;
|
|
23
|
+
export declare function compileContractGroupBy(query: NormalizedGroupByQuery, profile: PqsSchemaProfileV1): CompiledPqsQuery;
|