@distrohelena/canton-typescript-sdk 0.1.36 → 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 +104 -34
- 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/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/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 +4 -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,363 +1,412 @@
|
|
|
1
|
-
import { assertQueryPageArgs, } from "../model-types.js";
|
|
2
|
-
import { PqsSchemaProfileV1 } from "./pqs-schema-profile.js";
|
|
3
1
|
import { pqsRelationEdges, pqsRelationMetadata } from "./pqs-schema-profile.js";
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { quotePqsIdentifier, quotePqsString } from "./pqs-sql-syntax.js";
|
|
3
|
+
import { compilePqsResultShape } from "./pqs-result-shape.js";
|
|
4
|
+
import { compileCanonicalPublicNumericIdentityPartsSql, compileCanonicalPublicNumericIdentitySql } from "../canonical/public-identity.js";
|
|
5
|
+
export function compileCanonicalPhysicalField(relation, field, alias, profile) {
|
|
6
|
+
const column = pqsRelationMetadata[relation].fields[field];
|
|
7
|
+
if (column === undefined)
|
|
8
|
+
throw new Error(`${field} is not a field of ${relation}`);
|
|
9
|
+
const physical = qualified(alias, column);
|
|
10
|
+
if (relation === "__events" && field === "eventId") {
|
|
11
|
+
return eventIdExpression(physical);
|
|
12
|
+
}
|
|
13
|
+
if (relation === "__events" && field === "pk") {
|
|
14
|
+
return compileCanonicalPublicNumericIdentitySql(eventIdExpression(qualified(alias, "event_id")));
|
|
15
|
+
}
|
|
16
|
+
if (relation === "__events" && field === "txIx") {
|
|
17
|
+
return transactionOffsetExpression(profile, physical);
|
|
18
|
+
}
|
|
19
|
+
if (relation === "__events" && field === "type") {
|
|
20
|
+
return `case ${physical}::text when 'create' then 'created' when 'exercise' then 'exercised' else ${physical}::text end`;
|
|
21
|
+
}
|
|
22
|
+
if (relation === "__transactions" && field === "workflowId") {
|
|
23
|
+
return `nullif(${physical}, '')`;
|
|
24
|
+
}
|
|
25
|
+
if (relation === "__transactions" && field === "traceContext") {
|
|
26
|
+
return `case when ${physical} is null then null else jsonb_strip_nulls(jsonb_build_object('traceparent', nullif(to_jsonb(${physical})->>'trace_parent', ''), 'tracestate', nullif(to_jsonb(${physical})->>'trace_state', ''))) end`;
|
|
27
|
+
}
|
|
28
|
+
if ((relation === "__transactions" || relation === "__watermark") && (field === "ix" || field === "offset")) {
|
|
29
|
+
return qualified(alias, "offset");
|
|
30
|
+
}
|
|
31
|
+
if (relation === "__packages" && field === "pk") {
|
|
32
|
+
return compileCanonicalPublicNumericIdentitySql(qualified(alias, "id"));
|
|
33
|
+
}
|
|
34
|
+
if (relation === "__contract_tpe" && field === "pk") {
|
|
35
|
+
return compileCanonicalPublicNumericIdentitySql(compileCanonicalPublicNumericIdentityPartsSql([qualified(alias, "payload_type"), qualified(alias, "template_fqn")]));
|
|
36
|
+
}
|
|
37
|
+
if (relation === "__exercise_tpe" && field === "pk") {
|
|
38
|
+
return compileCanonicalPublicNumericIdentitySql(qualified(alias, "choice_fqn"));
|
|
39
|
+
}
|
|
40
|
+
if (relation === "__exercises" && field === "tpePk") {
|
|
41
|
+
return exerciseTypePublicKeyExpression(profile, physical);
|
|
42
|
+
}
|
|
43
|
+
if (relation === "__exercises" && field === "contractTpePk") {
|
|
44
|
+
return contractTypePublicKeyExpression(profile, physical);
|
|
45
|
+
}
|
|
46
|
+
if (relation === "__exercises" && field === "exerciseEventPk") {
|
|
47
|
+
return eventPublicKeyExpression(profile, physical);
|
|
48
|
+
}
|
|
49
|
+
if (relation === "__exercises" && field === "exercisedAtIx") {
|
|
50
|
+
return transactionOffsetExpression(profile, physical);
|
|
51
|
+
}
|
|
52
|
+
if (relation === "__exercises" && field === "packagePk") {
|
|
53
|
+
return packagePublicKeyExpression(profile, physical);
|
|
54
|
+
}
|
|
55
|
+
return physical;
|
|
56
|
+
}
|
|
57
|
+
function eventIdExpression(physical) {
|
|
58
|
+
return `(to_jsonb(${physical})->>'offset') || ':' || (to_jsonb(${physical})->>'node_id')`;
|
|
59
|
+
}
|
|
60
|
+
function transactionOffsetExpression(profile, physicalIx) {
|
|
61
|
+
return `(select "canonical_transaction"."offset" from ${profile.relation("__transactions")} "canonical_transaction" where "canonical_transaction"."ix" = ${physicalIx})`;
|
|
62
|
+
}
|
|
63
|
+
function eventPublicKeyExpression(profile, physicalPk) {
|
|
64
|
+
const eventId = eventIdExpression('"canonical_event"."event_id"');
|
|
65
|
+
return `(select ${compileCanonicalPublicNumericIdentitySql(eventId)} from ${profile.relation("__events")} "canonical_event" where "canonical_event"."pk" = ${physicalPk})`;
|
|
66
|
+
}
|
|
67
|
+
function packagePublicKeyExpression(profile, physicalPk) {
|
|
68
|
+
return `(select ${compileCanonicalPublicNumericIdentitySql('"canonical_package"."id"')} from ${profile.relation("__packages")} "canonical_package" where "canonical_package"."pk" = ${physicalPk})`;
|
|
69
|
+
}
|
|
70
|
+
function contractTypePublicKeyExpression(profile, physicalPk) {
|
|
71
|
+
const identity = compileCanonicalPublicNumericIdentityPartsSql(['"canonical_contract_type"."payload_type"', '"canonical_contract_type"."template_fqn"']);
|
|
72
|
+
return `(select ${compileCanonicalPublicNumericIdentitySql(identity)} from ${profile.relation("__contract_tpe")} "canonical_contract_type" where "canonical_contract_type"."pk" = ${physicalPk})`;
|
|
73
|
+
}
|
|
74
|
+
function exerciseTypePublicKeyExpression(profile, physicalPk) {
|
|
75
|
+
return `(select ${compileCanonicalPublicNumericIdentitySql('"canonical_exercise_type"."choice_fqn"')} from ${profile.relation("__exercise_tpe")} "canonical_exercise_type" where "canonical_exercise_type"."pk" = ${physicalPk})`;
|
|
76
|
+
}
|
|
77
|
+
export function compileContractFindMany(query, profile) {
|
|
78
|
+
if (query.relation !== "contracts")
|
|
79
|
+
throw new Error("compileContractFindMany requires a contracts query");
|
|
6
80
|
const values = [];
|
|
7
81
|
const addValue = (value) => {
|
|
8
82
|
values.push(value);
|
|
9
83
|
return `$${values.length}`;
|
|
10
84
|
};
|
|
11
85
|
const conditions = [];
|
|
12
|
-
const where =
|
|
86
|
+
const where = query.predicate === undefined ? undefined : compileCanonicalContractPredicate(query.predicate, addValue, profile, "contract_row");
|
|
13
87
|
if (where !== undefined)
|
|
14
88
|
conditions.push(where);
|
|
15
|
-
if (
|
|
16
|
-
conditions.push(`contract_row.witnesses && ${addValue(
|
|
89
|
+
if (query.parties !== undefined) {
|
|
90
|
+
conditions.push(`contract_row.witnesses && ${addValue(query.parties)}::text[]`);
|
|
17
91
|
}
|
|
18
|
-
const orderBy =
|
|
92
|
+
const orderBy = compileCanonicalContractOrderBy(query.orderBy);
|
|
19
93
|
const whereSql = conditions.length === 0 ? "" : `where ${conditions.join(" and ")}`;
|
|
20
|
-
const limitSql =
|
|
21
|
-
const offsetSql =
|
|
22
|
-
const included =
|
|
23
|
-
const jsonProjection =
|
|
24
|
-
if (projection.field !== "payload")
|
|
25
|
-
throw new Error(`${projection.field} is not a JSON field of contracts`);
|
|
26
|
-
if (projection.path.length === 0 || projection.path.some((segment) => segment.length === 0))
|
|
27
|
-
throw new Error(`${name}.path must be a non-empty JSON path`);
|
|
94
|
+
const limitSql = query.take === undefined ? "" : `limit ${addValue(query.take)}`;
|
|
95
|
+
const offsetSql = query.skip === 0 ? "" : `offset ${addValue(query.skip)}`;
|
|
96
|
+
const included = compileCanonicalPhysicalIncludes("__contracts", "contract_row", query.includes, profile, addValue);
|
|
97
|
+
const jsonProjection = (query.select?.json ?? []).map((projection) => {
|
|
28
98
|
const text = `contract_row.payload #>> ${addValue(projection.path)}::text[]`;
|
|
29
99
|
const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
|
|
30
|
-
return `${expression} as
|
|
100
|
+
return `${expression} as ${quotePqsIdentifier(projection.name)}`;
|
|
31
101
|
});
|
|
32
102
|
return {
|
|
33
103
|
text: `select
|
|
34
104
|
contract_row.contract_id as contract_id,
|
|
35
|
-
contract_row
|
|
105
|
+
${contractPackageIdExpression("contract_row", profile)} as package_id,
|
|
36
106
|
contract_row.payload,
|
|
37
107
|
contract_row.witnesses,
|
|
38
|
-
|
|
108
|
+
created_tx.offset::text as created_event_offset,
|
|
39
109
|
created_tx.effective_at as created_at,
|
|
40
|
-
|
|
110
|
+
archived_tx.offset::text as archived_event_offset,
|
|
41
111
|
archived_tx.effective_at as archived_at,
|
|
42
112
|
contract_row.archived_at_ix is null as active,
|
|
43
|
-
contract_row
|
|
113
|
+
${contractPackageIdExpression("contract_row", profile)} as template_package_id,
|
|
44
114
|
contract_tpe_row.module_name as template_module_name,
|
|
45
|
-
contract_tpe_row.entity_name as template_entity_name${[...included, ...jsonProjection].length === 0 ? "" : `,\n ${[...included, ...jsonProjection].join(",\n ")}`}
|
|
115
|
+
contract_tpe_row.entity_name as template_entity_name${[...included.map((include) => include.selection), ...jsonProjection].length === 0 ? "" : `,\n ${[...included.map((include) => include.selection), ...jsonProjection].join(",\n ")}`}
|
|
46
116
|
from ${profile.relation("__contracts")} contract_row
|
|
47
117
|
join ${profile.relation("__contract_tpe")} contract_tpe_row on contract_tpe_row.pk = contract_row.tpe_pk
|
|
48
118
|
left join ${profile.relation("__transactions")} created_tx on created_tx.ix = contract_row.created_at_ix
|
|
49
119
|
left join ${profile.relation("__transactions")} archived_tx on archived_tx.ix = contract_row.archived_at_ix
|
|
120
|
+
left join ${profile.relation("__packages")} contract_package on contract_package.pk = contract_row.package_pk
|
|
50
121
|
${whereSql}
|
|
51
122
|
${orderBy}
|
|
52
123
|
${limitSql}
|
|
53
124
|
${offsetSql}`,
|
|
54
125
|
values,
|
|
126
|
+
resultShape: compilePqsResultShape("__contracts", "many", query.select, query.includes),
|
|
55
127
|
};
|
|
56
128
|
}
|
|
57
|
-
function
|
|
129
|
+
export function compileContractCount(query, profile) {
|
|
130
|
+
if (query.relation !== "contracts")
|
|
131
|
+
throw new Error("compileContractCount requires a contracts query");
|
|
132
|
+
const values = [];
|
|
133
|
+
const add = (value) => { values.push(value); return `$${values.length}`; };
|
|
134
|
+
const where = compileContractWhere(query.predicate, query.parties, profile, add);
|
|
135
|
+
return { text: `select count(*)::text as count ${contractFrom(profile)}${where}`, values };
|
|
136
|
+
}
|
|
137
|
+
export function compileContractAggregate(query, profile) {
|
|
138
|
+
if (query.relation !== "contracts")
|
|
139
|
+
throw new Error("compileContractAggregate requires a contracts query");
|
|
140
|
+
const values = [];
|
|
141
|
+
const add = (value) => { values.push(value); return `$${values.length}`; };
|
|
58
142
|
const selected = [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
143
|
+
if (query.aggregates.count)
|
|
144
|
+
selected.push("count(*)::text as count");
|
|
145
|
+
const fields = { createdEventOffset: "created_tx.offset", archivedEventOffset: "archived_tx.offset" };
|
|
146
|
+
for (const [operation, requested] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]]) {
|
|
147
|
+
for (const field of requested) {
|
|
148
|
+
const expression = fields[field];
|
|
149
|
+
if (expression === undefined)
|
|
150
|
+
throw new Error(`${field} is not a numeric aggregate field of contracts`);
|
|
151
|
+
selected.push(`${operation}(${expression})::text as ${quotePqsIdentifier(`${operation}_${field}`)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (selected.length === 0)
|
|
155
|
+
throw new Error("aggregate must request at least one result");
|
|
156
|
+
return { text: `select ${selected.join(", ")} ${contractFrom(profile)}${compileContractWhere(query.predicate, undefined, profile, add)}`, values };
|
|
157
|
+
}
|
|
158
|
+
function contractFrom(profile) {
|
|
159
|
+
return `from ${profile.relation("__contracts")} contract_row join ${profile.relation("__contract_tpe")} contract_tpe_row on contract_tpe_row.pk = contract_row.tpe_pk left join ${profile.relation("__transactions")} created_tx on created_tx.ix = contract_row.created_at_ix left join ${profile.relation("__transactions")} archived_tx on archived_tx.ix = contract_row.archived_at_ix left join ${profile.relation("__packages")} contract_package on contract_package.pk = contract_row.package_pk`;
|
|
160
|
+
}
|
|
161
|
+
function compileContractWhere(predicate, parties, profile, add) {
|
|
162
|
+
const conditions = [
|
|
163
|
+
...(predicate === undefined ? [] : [compileCanonicalContractPredicate(predicate, add, profile, "contract_row")]),
|
|
164
|
+
...(parties === undefined ? [] : [`contract_row.witnesses && ${add(parties)}::text[]`]),
|
|
165
|
+
];
|
|
166
|
+
return conditions.length === 0 ? "" : ` where ${conditions.join(" and ")}`;
|
|
167
|
+
}
|
|
168
|
+
export function compileCanonicalPhysicalIncludes(source, parentAlias, includes, profile, add) {
|
|
169
|
+
return includes.map((include) => {
|
|
170
|
+
const edge = pqsRelationEdges[source]?.[include.edge];
|
|
171
|
+
if (edge === undefined || edge.target !== pqsRelation(include.relation))
|
|
172
|
+
throw new Error(`Invalid canonical include ${include.edge}`);
|
|
173
|
+
const alias = quotePqsIdentifier(include.edge);
|
|
174
|
+
const fields = compileCanonicalIncludedFields(edge.target, include.select, alias, profile);
|
|
175
|
+
const json = (include.select?.json ?? []).flatMap((projection) => {
|
|
176
|
+
const base = edge.target === "__contracts"
|
|
177
|
+
? projection.field === "payload" ? `${alias}."payload"` : undefined
|
|
178
|
+
: pqsRelationMetadata[edge.target].fields[projection.field] === undefined ? undefined : compileCanonicalPhysicalField(edge.target, projection.field, alias, profile);
|
|
179
|
+
if (base === undefined)
|
|
180
|
+
throw new Error(`Invalid canonical JSON projection ${projection.field}`);
|
|
181
|
+
const text = `${base} #>> ${add(projection.path)}::text[]`;
|
|
85
182
|
const expression = projection.as === "text" ? text : projection.as === "numeric" ? `(${text})::numeric::text` : projection.as === "boolean" ? `(${text})::boolean` : `(${text})::timestamptz`;
|
|
86
|
-
return [
|
|
183
|
+
return [quotePqsString(projection.name), expression];
|
|
87
184
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const condition = `${alias}."${edge.targetColumn}" = ${parentAlias}."${edge.sourceColumn}" and (${relationFilter})`;
|
|
98
|
-
const orderBy = compilePhysicalOrderBy(edge.target, options.orderBy, alias);
|
|
99
|
-
const expression = edge.cardinality === "one"
|
|
185
|
+
const nested = compileCanonicalPhysicalIncludes(edge.target, alias, include.includes, profile, add);
|
|
186
|
+
const nestedFields = nested.flatMap((selection) => [quotePqsString(selection.key), selection.expression]);
|
|
187
|
+
const object = `jsonb_build_object(${[...fields.flatMap(([field, expression]) => [`'${field}'`, expression]), ...json.flat(), ...nestedFields].join(", ")})`;
|
|
188
|
+
const filter = include.predicate === undefined ? "true" : compileCanonicalPhysicalPredicate(edge.target, include.predicate, alias, profile, add);
|
|
189
|
+
const condition = `${compileCanonicalPhysicalEdgeJoin(source, include.edge, parentAlias, alias, profile)} and (${filter})`;
|
|
190
|
+
const orderBy = compileCanonicalPhysicalOrderBy(edge.target, include.orderBy, alias, profile);
|
|
191
|
+
const limit = include.cardinality === "many" ? ` limit ${add(include.take)}` : "";
|
|
192
|
+
const offset = include.cardinality === "many" && include.skip > 0 ? ` offset ${add(include.skip)}` : "";
|
|
193
|
+
const expression = include.cardinality === "one"
|
|
100
194
|
? `(select ${object} from ${profile.relation(edge.target)} ${alias} where ${condition})`
|
|
101
|
-
: `(select coalesce(jsonb_agg("${
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
return selected;
|
|
195
|
+
: `(select coalesce(jsonb_agg("${include.edge}_limited".value), '[]'::jsonb) from (select ${object} as value from ${profile.relation(edge.target)} ${alias} where ${condition}${orderBy}${limit}${offset}) "${include.edge}_limited")`;
|
|
196
|
+
return { key: include.edge, expression, selection: `${expression} as ${quotePqsIdentifier(include.edge)}` };
|
|
197
|
+
});
|
|
105
198
|
}
|
|
106
|
-
function
|
|
199
|
+
function compileCanonicalIncludedFields(target, select, alias, profile) {
|
|
107
200
|
if (target !== "__contracts") {
|
|
108
201
|
const metadata = pqsRelationMetadata[target];
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return selected.map(([field, column]) => [field, `${alias}."${column}"`]);
|
|
202
|
+
const fields = select === undefined ? Object.keys(metadata.fields) : select.fields;
|
|
203
|
+
return fields.map((field) => {
|
|
204
|
+
const expression = compileCanonicalPhysicalField(target, field, alias, profile);
|
|
205
|
+
return [field, metadata.numericFields.includes(field) ? `${expression}::text` : expression];
|
|
206
|
+
});
|
|
115
207
|
}
|
|
116
|
-
const
|
|
117
|
-
? ["contractId", "templateId", "packageId", "payload", "witnesses", "createdEventOffset", "createdAt", "archivedEventOffset", "archivedAt", "active"]
|
|
118
|
-
: Object.entries(select).filter(([field, enabled]) => field !== "json" && enabled === true).map(([field]) => field);
|
|
208
|
+
const fields = select === undefined ? ["contractId", "templateId", "packageId", "payload", "witnesses", "createdEventOffset", "createdAt", "archivedEventOffset", "archivedAt", "active"] : select.fields;
|
|
119
209
|
const expressions = {
|
|
120
|
-
contractId: `${alias}."contract_id"`,
|
|
121
|
-
templateId: `jsonb_build_object('packageId', ${alias}."creation_package_id", 'moduleName', (select contract_type."module_name" from ${profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${alias}."tpe_pk"), 'entityName', (select contract_type."entity_name" from ${profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${alias}."tpe_pk"))`,
|
|
122
|
-
packageId: `${alias}."creation_package_id"`,
|
|
123
|
-
payload: `${alias}."payload"`,
|
|
124
|
-
witnesses: `${alias}."witnesses"`,
|
|
125
|
-
createdEventOffset: `${alias}."created_at_ix"::text`,
|
|
126
|
-
createdAt: `(select created_transaction."effective_at" from ${profile.relation("__transactions")} created_transaction where created_transaction."ix" = ${alias}."created_at_ix")`,
|
|
127
|
-
archivedEventOffset: `${alias}."archived_at_ix"::text`,
|
|
128
|
-
archivedAt: `(select archived_transaction."effective_at" from ${profile.relation("__transactions")} archived_transaction where archived_transaction."ix" = ${alias}."archived_at_ix")`,
|
|
129
|
-
active: `${alias}."archived_at_ix" is null`,
|
|
210
|
+
contractId: `${alias}."contract_id"`, templateId: `jsonb_build_object('packageId', ${contractPackageIdExpression(alias, profile)}, 'moduleName', ${contractTypeFieldExpression(alias, "module_name", profile)}, 'entityName', ${contractTypeFieldExpression(alias, "entity_name", profile)})`, packageId: contractPackageIdExpression(alias, profile), payload: `${alias}."payload"`, witnesses: `${alias}."witnesses"`, createdEventOffset: `${contractTransactionFieldExpression(alias, "created", "offset", profile)}::text`, createdAt: contractTransactionFieldExpression(alias, "created", "effective_at", profile), archivedEventOffset: `${contractTransactionFieldExpression(alias, "archived", "offset", profile)}::text`, archivedAt: contractTransactionFieldExpression(alias, "archived", "effective_at", profile), active: `${alias}."archived_at_ix" is null`,
|
|
130
211
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return
|
|
212
|
+
return fields.map((field) => [field, expressions[field]]);
|
|
213
|
+
}
|
|
214
|
+
function compileCanonicalContractPredicate(predicate, add, profile, alias) {
|
|
215
|
+
return compileCanonicalPredicate("__contracts", predicate, alias, profile, add, true);
|
|
216
|
+
}
|
|
217
|
+
export function compileCanonicalPhysicalPredicate(relation, predicate, alias, profile, add) {
|
|
218
|
+
return compileCanonicalPredicate(relation, predicate, alias, profile, add, relation === "__contracts");
|
|
219
|
+
}
|
|
220
|
+
function compileCanonicalPredicate(relation, predicate, alias, profile, add, logicalContracts) {
|
|
221
|
+
if (predicate.kind === "and" || predicate.kind === "or")
|
|
222
|
+
return predicate.children.length === 0 ? predicate.kind === "and" ? "true" : "false" : `(${predicate.children.map((child) => compileCanonicalPredicate(relation, child, alias, profile, add, logicalContracts)).join(` ${predicate.kind} `)})`;
|
|
223
|
+
if (predicate.kind === "not")
|
|
224
|
+
return `not (${compileCanonicalPredicate(relation, predicate.child, alias, profile, add, logicalContracts)})`;
|
|
225
|
+
if (predicate.kind === "relation") {
|
|
226
|
+
const edge = pqsRelationEdges[relation]?.[predicate.edge];
|
|
227
|
+
if (edge === undefined)
|
|
228
|
+
throw new Error(`Invalid canonical edge ${predicate.edge}`);
|
|
229
|
+
const relatedAlias = `"${predicate.edge}"`;
|
|
230
|
+
const join = compileCanonicalPhysicalEdgeJoin(relation, predicate.edge, alias, relatedAlias, profile);
|
|
231
|
+
const condition = compileCanonicalPredicate(edge.target, predicate.predicate, relatedAlias, profile, add, edge.target === "__contracts");
|
|
232
|
+
const base = `select 1 from ${profile.relation(edge.target)} ${relatedAlias} where ${join} and (${condition})`;
|
|
233
|
+
return predicate.quantifier === "one" || predicate.quantifier === "some" ? `exists (${base})` : predicate.quantifier === "none" ? `not exists (${base})` : `not exists (select 1 from ${profile.relation(edge.target)} ${relatedAlias} where ${join} and not (${condition}))`;
|
|
234
|
+
}
|
|
235
|
+
if (predicate.kind !== "scalar")
|
|
236
|
+
throw new Error("Unknown canonical predicate");
|
|
237
|
+
const [field, ...path] = predicate.path;
|
|
238
|
+
if (logicalContracts && field === "active")
|
|
239
|
+
return `${logicalQualified(alias, "archived_at_ix")} is ${predicate.value === true ? "null" : "not null"}`;
|
|
240
|
+
if (logicalContracts && field === "witnesses")
|
|
241
|
+
return `${add(predicate.value)} = any(${logicalQualified(alias, "witnesses")})`;
|
|
242
|
+
const column = logicalContracts ? undefined : pqsRelationMetadata[relation].fields[field];
|
|
243
|
+
if (!logicalContracts && column === undefined)
|
|
244
|
+
throw new Error(`${field} is not a field of ${relation}`);
|
|
245
|
+
const canonicalExpression = logicalContracts
|
|
246
|
+
? contractPredicateExpression(alias, field, path, profile, add)
|
|
247
|
+
: path.length > 0
|
|
248
|
+
? `${compileCanonicalPhysicalField(relation, field, alias, profile)} #>> ${add(path)}::text[]`
|
|
249
|
+
: compileCanonicalPhysicalField(relation, field, alias, profile);
|
|
250
|
+
const inverseEventType = relation === "__events" && field === "type" && (predicate.operator === "equals" || predicate.operator === "in");
|
|
251
|
+
const expression = inverseEventType ? qualified(alias, column) : canonicalExpression;
|
|
252
|
+
const value = inverseEventType ? physicalEventType(predicate.value) : predicate.value;
|
|
253
|
+
const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[predicate.operator];
|
|
254
|
+
if (predicate.operator === "is")
|
|
255
|
+
return `${expression} is null`;
|
|
256
|
+
if (predicate.operator === "isNot")
|
|
257
|
+
return `${expression} is not null`;
|
|
258
|
+
if (predicate.operator === "in")
|
|
259
|
+
return value.length === 0 ? "false" : `${expression} = any(${add(value)})`;
|
|
260
|
+
if (predicate.operator === "has")
|
|
261
|
+
return `${add(value)} = any(${qualified(alias, column)})`;
|
|
262
|
+
if (sql === undefined)
|
|
263
|
+
throw new Error(`Unsupported canonical operator ${predicate.operator}`);
|
|
264
|
+
return `${expression} ${sql} ${add(value)}`;
|
|
265
|
+
}
|
|
266
|
+
function compileCanonicalPhysicalEdgeJoin(source, edgeName, parentAlias, targetAlias, profile) {
|
|
267
|
+
const edge = pqsRelationEdges[source]?.[edgeName];
|
|
268
|
+
if (edge === undefined)
|
|
269
|
+
throw new Error(`Invalid canonical edge ${edgeName}`);
|
|
270
|
+
if (source === "__contract_tpe" && edgeName === "contracts") {
|
|
271
|
+
return `${contractTypePublicKeyExpression(profile, qualified(targetAlias, "tpe_pk"))} = ${compileCanonicalPhysicalField(source, "pk", parentAlias, profile)}`;
|
|
272
|
+
}
|
|
273
|
+
if (source === "__contract_tpe" && edgeName === "exercises") {
|
|
274
|
+
return `${compileCanonicalPhysicalField("__exercises", "contractTpePk", targetAlias, profile)} = ${compileCanonicalPhysicalField(source, "pk", parentAlias, profile)}`;
|
|
275
|
+
}
|
|
276
|
+
if (source === "__exercise_tpe" && edgeName === "exercises") {
|
|
277
|
+
return `${compileCanonicalPhysicalField("__exercises", "tpePk", targetAlias, profile)} = ${compileCanonicalPhysicalField(source, "pk", parentAlias, profile)}`;
|
|
278
|
+
}
|
|
279
|
+
const parent = parentAlias.length === 0
|
|
280
|
+
? `${profile.relation(source)}."${edge.sourceColumn}"`
|
|
281
|
+
: qualified(parentAlias, edge.sourceColumn);
|
|
282
|
+
return `${targetAlias}."${edge.targetColumn}" = ${parent}`;
|
|
283
|
+
}
|
|
284
|
+
function contractPredicateExpression(alias, field, path, profile, add) {
|
|
285
|
+
if (field === "payload")
|
|
286
|
+
return `${logicalQualified(alias, "payload")} #>> ${add(path)}::text[]`;
|
|
287
|
+
if (field === "contractId")
|
|
288
|
+
return logicalQualified(alias, "contract_id");
|
|
289
|
+
if (field === "packageId")
|
|
290
|
+
return contractPackageIdExpression(alias, profile);
|
|
291
|
+
if (field === "createdEventOffset")
|
|
292
|
+
return contractTransactionFieldExpression(alias, "created", "offset", profile);
|
|
293
|
+
if (field === "createdAt")
|
|
294
|
+
return contractTransactionFieldExpression(alias, "created", "effective_at", profile);
|
|
295
|
+
if (field === "archivedEventOffset")
|
|
296
|
+
return contractTransactionFieldExpression(alias, "archived", "offset", profile);
|
|
297
|
+
if (field === "archivedAt")
|
|
298
|
+
return contractTransactionFieldExpression(alias, "archived", "effective_at", profile);
|
|
299
|
+
if (field === "templateId" && path[0] === "packageId")
|
|
300
|
+
return contractPackageIdExpression(alias, profile);
|
|
301
|
+
if (field === "templateId" && path[0] === "moduleName")
|
|
302
|
+
return contractTypeFieldExpression(alias, "module_name", profile);
|
|
303
|
+
if (field === "templateId" && path[0] === "entityName")
|
|
304
|
+
return contractTypeFieldExpression(alias, "entity_name", profile);
|
|
305
|
+
throw new Error(`${field} is not a field of contracts`);
|
|
306
|
+
}
|
|
307
|
+
function physicalEventType(value) {
|
|
308
|
+
if (Array.isArray(value))
|
|
309
|
+
return value.map(physicalEventType);
|
|
310
|
+
if (value === "created")
|
|
311
|
+
return "create";
|
|
312
|
+
if (value === "exercised")
|
|
313
|
+
return "exercise";
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
function qualified(alias, column) {
|
|
317
|
+
return alias.length === 0 ? `"${column}"` : `${alias}."${column}"`;
|
|
318
|
+
}
|
|
319
|
+
function logicalQualified(alias, column) {
|
|
320
|
+
return alias === "contract_row" ? `${alias}.${column}` : qualified(alias, column);
|
|
135
321
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
322
|
+
function contractPackageIdExpression(alias, profile) {
|
|
323
|
+
const packageId = alias === "contract_row"
|
|
324
|
+
? "contract_package.id"
|
|
325
|
+
: `(select contract_package."id" from ${profile.relation("__packages")} contract_package where contract_package."pk" = ${qualified(alias, "package_pk")})`;
|
|
326
|
+
return `coalesce(${logicalQualified(alias, "creation_package_id")}, ${packageId})`;
|
|
327
|
+
}
|
|
328
|
+
function contractTransactionFieldExpression(alias, lifecycle, field, profile) {
|
|
329
|
+
if (alias === "contract_row")
|
|
330
|
+
return `${lifecycle}_tx.${field}`;
|
|
331
|
+
const transaction = `${lifecycle}_transaction`;
|
|
332
|
+
return `(select ${transaction}."${field}" from ${profile.relation("__transactions")} ${transaction} where ${transaction}."ix" = ${qualified(alias, `${lifecycle}_at_ix`)})`;
|
|
333
|
+
}
|
|
334
|
+
function contractTypeFieldExpression(alias, field, profile) {
|
|
335
|
+
if (alias === "contract_row")
|
|
336
|
+
return `contract_tpe_row.${field}`;
|
|
337
|
+
return `(select contract_type."${field}" from ${profile.relation("__contract_tpe")} contract_type where contract_type."pk" = ${qualified(alias, "tpe_pk")})`;
|
|
338
|
+
}
|
|
339
|
+
function compileCanonicalContractOrderBy(orderBy) {
|
|
340
|
+
return compileCanonicalOrderBy(orderBy, { contractId: "contract_row.contract_id", createdEventOffset: "created_tx.offset", createdAt: "created_tx.effective_at", archivedEventOffset: "archived_tx.offset", archivedAt: "archived_tx.effective_at" });
|
|
341
|
+
}
|
|
342
|
+
function compileCanonicalPhysicalOrderBy(relation, orderBy, alias, profile) {
|
|
343
|
+
if (relation === "__contracts") {
|
|
344
|
+
return compileCanonicalOrderBy(orderBy, {
|
|
345
|
+
contractId: qualified(alias, "contract_id"),
|
|
346
|
+
createdEventOffset: contractTransactionFieldExpression(alias, "created", "offset", profile),
|
|
347
|
+
createdAt: contractTransactionFieldExpression(alias, "created", "effective_at", profile),
|
|
348
|
+
archivedEventOffset: contractTransactionFieldExpression(alias, "archived", "offset", profile),
|
|
349
|
+
archivedAt: contractTransactionFieldExpression(alias, "archived", "effective_at", profile),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return compileCanonicalOrderBy(orderBy, Object.fromEntries(Object.keys(pqsRelationMetadata[relation].fields).map((field) => [field, compileCanonicalPhysicalField(relation, field, alias, profile)])));
|
|
151
353
|
}
|
|
152
|
-
|
|
153
|
-
|
|
354
|
+
function compileCanonicalOrderBy(orderBy, fields) {
|
|
355
|
+
return orderBy.length === 0 ? "" : ` order by ${orderBy.map((order) => `${fields[order.path[0]]} ${order.direction}`).join(", ")}`;
|
|
356
|
+
}
|
|
357
|
+
function pqsRelation(relation) {
|
|
358
|
+
return { contracts: "__contracts", contractTypes: "__contract_tpe", events: "__events", exercises: "__exercises", exerciseTypes: "__exercise_tpe", packages: "__packages", transactions: "__transactions", watermark: "__watermark" }[relation];
|
|
359
|
+
}
|
|
360
|
+
export function compileContractGroupBy(query, profile) {
|
|
361
|
+
if (query.relation !== "contracts")
|
|
362
|
+
throw new Error("compileContractGroupBy requires a contracts query");
|
|
363
|
+
if (query.by.length === 0 || (!query.aggregates.count && query.aggregates.min.length === 0 && query.aggregates.max.length === 0 && query.aggregates.sum.length === 0))
|
|
154
364
|
throw new Error("groupBy requires keys and an aggregate");
|
|
155
365
|
const values = [];
|
|
156
366
|
const add = (value) => { values.push(value); return `$${values.length}`; };
|
|
157
|
-
const where =
|
|
367
|
+
const where = query.predicate === undefined ? undefined : compileCanonicalContractPredicate(query.predicate, add, profile, "contract_row");
|
|
158
368
|
const fields = {
|
|
159
369
|
contractId: "contract_row.contract_id",
|
|
160
|
-
createdEventOffset: "
|
|
161
|
-
|
|
370
|
+
createdEventOffset: "created_tx.offset",
|
|
371
|
+
createdAt: "created_tx.effective_at",
|
|
372
|
+
archivedEventOffset: "archived_tx.offset",
|
|
373
|
+
archivedAt: "archived_tx.effective_at",
|
|
162
374
|
};
|
|
163
375
|
const expressions = [];
|
|
164
376
|
const select = [];
|
|
165
377
|
let unnestWitnesses = false;
|
|
166
|
-
for (const key of
|
|
167
|
-
if (key === "witnesses") {
|
|
378
|
+
for (const key of query.by) {
|
|
379
|
+
if (key.kind === "field" && key.path[0] === "witnesses") {
|
|
168
380
|
unnestWitnesses = true;
|
|
169
381
|
expressions.push("witness.value");
|
|
170
382
|
select.push("witness.value as \"witnesses\"");
|
|
171
383
|
}
|
|
172
|
-
else if (
|
|
173
|
-
const expression = fields[key];
|
|
384
|
+
else if (key.kind === "field") {
|
|
385
|
+
const expression = fields[key.path[0]];
|
|
174
386
|
if (expression === undefined)
|
|
175
|
-
throw new Error(`${key} is not a contract group key`);
|
|
387
|
+
throw new Error(`${key.path[0]} is not a contract group key`);
|
|
176
388
|
expressions.push(expression);
|
|
177
|
-
select.push(`${expression} as "${key}"`);
|
|
389
|
+
select.push(`${expression} as "${key.path[0]}"`);
|
|
178
390
|
}
|
|
179
391
|
else {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const cast = payload.as === "text" ? base : `(${base})::${payload.as === "numeric" ? "numeric" : payload.as === "boolean" ? "boolean" : "timestamptz"}`;
|
|
392
|
+
if (key.kind !== "json" || key.field !== "payload")
|
|
393
|
+
throw new Error("invalid contract group key");
|
|
394
|
+
const base = `contract_row.payload #>> ${add(key.path)}::text[]`;
|
|
395
|
+
const cast = key.as === "text" ? base : `(${base})::${key.as === "numeric" ? "numeric" : key.as === "boolean" ? "boolean" : "timestamptz"}`;
|
|
185
396
|
expressions.push(cast);
|
|
186
|
-
select.push(`${cast} as
|
|
397
|
+
select.push(`${cast} as ${quotePqsIdentifier(key.name)}`);
|
|
187
398
|
}
|
|
188
399
|
}
|
|
189
|
-
if (
|
|
400
|
+
if (query.aggregates.count)
|
|
190
401
|
select.push("count(*)::text as count");
|
|
191
|
-
for (const [operation, requested] of [["min",
|
|
192
|
-
for (const name of requested
|
|
402
|
+
for (const [operation, requested] of [["min", query.aggregates.min], ["max", query.aggregates.max], ["sum", query.aggregates.sum]]) {
|
|
403
|
+
for (const name of requested) {
|
|
193
404
|
const expression = fields[name];
|
|
194
405
|
select.push(`${operation}(${expression})::text as "${operation}_${name}"`);
|
|
195
406
|
}
|
|
196
407
|
}
|
|
197
408
|
return {
|
|
198
|
-
text: `select ${select.join(", ")}
|
|
409
|
+
text: `select ${select.join(", ")} ${contractFrom(profile)}${unnestWitnesses ? " cross join lateral unnest(contract_row.witnesses) as witness(value)" : ""}${where === undefined ? "" : ` where ${where}`} group by ${expressions.join(", ")}`,
|
|
199
410
|
values,
|
|
200
411
|
};
|
|
201
412
|
}
|
|
202
|
-
function compileWhere(where, addValue, profile) {
|
|
203
|
-
const columns = { contractId: "contract_row.contract_id", packageId: "contract_row.creation_package_id", createdEventOffset: "contract_row.created_at_ix", createdAt: "created_tx.effective_at", archivedEventOffset: "contract_row.archived_at_ix", archivedAt: "archived_tx.effective_at" };
|
|
204
|
-
const parts = [];
|
|
205
|
-
for (const [key, value] of Object.entries(where)) {
|
|
206
|
-
if (key === "and" || key === "or") {
|
|
207
|
-
if (!Array.isArray(value))
|
|
208
|
-
throw new Error(`${key} must be an array`);
|
|
209
|
-
parts.push(value.length === 0 ? key === "and" ? "true" : "false" : `(${value.map((child) => compileWhere(child, addValue, profile)).join(` ${key} `)})`);
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (key === "not") {
|
|
213
|
-
parts.push(`not (${compileWhere(value, addValue, profile)})`);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (key === "active") {
|
|
217
|
-
const active = typeof value === "boolean" ? value : value.equals;
|
|
218
|
-
if (typeof active !== "boolean")
|
|
219
|
-
throw new Error("active supports only equals");
|
|
220
|
-
parts.push(active ? "contract_row.archived_at_ix is null" : "contract_row.archived_at_ix is not null");
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
if (key === "witnesses") {
|
|
224
|
-
const has = value.has;
|
|
225
|
-
if (has === undefined)
|
|
226
|
-
throw new Error("witnesses supports only has");
|
|
227
|
-
parts.push(`${addValue(has)} = any(contract_row.witnesses)`);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
if (key === "payload") {
|
|
231
|
-
const payload = value;
|
|
232
|
-
const compilePayload = (path, filter) => { const ops = ["equals", "lt", "lte", "gt", "gte", "like", "ilike"].filter((op) => filter[op] !== undefined); if (ops.length === 1) {
|
|
233
|
-
const op = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[ops[0]];
|
|
234
|
-
parts.push(`contract_row.payload #>> ${addValue(path)}::text[] ${op} ${addValue(filter[ops[0]])}`);
|
|
235
|
-
return;
|
|
236
|
-
} for (const [name, child] of Object.entries(filter))
|
|
237
|
-
compilePayload([...path, name], child); };
|
|
238
|
-
if (payload.match === undefined)
|
|
239
|
-
throw new Error("payload requires match");
|
|
240
|
-
compilePayload([], payload.match);
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (key === "templateId") {
|
|
244
|
-
const fields = { packageId: "contract_row.creation_package_id", moduleName: "contract_tpe_row.module_name", entityName: "contract_tpe_row.entity_name" };
|
|
245
|
-
for (const [name, filter] of Object.entries(value))
|
|
246
|
-
for (const [op, operand] of Object.entries(filter)) {
|
|
247
|
-
const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[op];
|
|
248
|
-
if (fields[name] === undefined || sql === undefined)
|
|
249
|
-
throw new Error("invalid templateId filter");
|
|
250
|
-
parts.push(`${fields[name]} ${sql} ${addValue(operand)}`);
|
|
251
|
-
}
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
const edge = pqsRelationEdges.__contracts?.[key];
|
|
255
|
-
if (edge !== undefined) {
|
|
256
|
-
parts.push(compilePhysicalRelationPredicate(edge.target, key, edge.targetColumn, edge.sourceColumn, edge.cardinality, value, "contract_row", profile, addValue));
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
const column = columns[key];
|
|
260
|
-
if (column === undefined || value === null || typeof value !== "object")
|
|
261
|
-
throw new Error(`${key} is not a supported contract filter`);
|
|
262
|
-
for (const [op, operand] of Object.entries(value)) {
|
|
263
|
-
const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[op];
|
|
264
|
-
if (sql !== undefined)
|
|
265
|
-
parts.push(`${column} ${sql} ${addValue(operand)}`);
|
|
266
|
-
else if (op === "is" && operand === null)
|
|
267
|
-
parts.push(`${column} is null`);
|
|
268
|
-
else if (op === "isNot" && operand === null)
|
|
269
|
-
parts.push(`${column} is not null`);
|
|
270
|
-
else if (op === "in" && Array.isArray(operand))
|
|
271
|
-
parts.push(operand.length ? `${column} = any(${addValue(operand)})` : "false");
|
|
272
|
-
else
|
|
273
|
-
throw new Error(`${op} is not supported for ${key}`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return parts.length === 0 ? "true" : parts.join(" and ");
|
|
277
|
-
}
|
|
278
|
-
function compilePhysicalRelationPredicate(target, edgeName, targetColumn, sourceColumn, cardinality, value, parentAlias, profile, addValue) {
|
|
279
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
280
|
-
throw new Error(`${edgeName} must be a relation filter`);
|
|
281
|
-
const relation = value;
|
|
282
|
-
const alias = `"${edgeName}"`;
|
|
283
|
-
const join = `${alias}."${targetColumn}" = ${parentAlias}."${sourceColumn}"`;
|
|
284
|
-
if (cardinality === "one") {
|
|
285
|
-
const predicate = compilePhysicalWhere(target, relation, alias, profile, addValue);
|
|
286
|
-
return `exists (select 1 from ${profile.relation(target)} ${alias} where ${join} and (${predicate}))`;
|
|
287
|
-
}
|
|
288
|
-
const operators = ["some", "none", "every"].filter((name) => relation[name] !== undefined);
|
|
289
|
-
if (operators.length !== 1)
|
|
290
|
-
throw new Error(`${edgeName} requires exactly one of some, none, or every`);
|
|
291
|
-
const predicate = compilePhysicalWhere(target, relation[operators[0]], alias, profile, addValue);
|
|
292
|
-
const base = `select 1 from ${profile.relation(target)} ${alias} where ${join} and (${predicate})`;
|
|
293
|
-
return operators[0] === "some" ? `exists (${base})` : operators[0] === "none" ? `not exists (${base})` : `not exists (select 1 from ${profile.relation(target)} ${alias} where ${join} and not (${predicate}))`;
|
|
294
|
-
}
|
|
295
|
-
function compilePhysicalWhere(relation, expression, alias, profile, addValue) {
|
|
296
|
-
if (expression === null || typeof expression !== "object" || Array.isArray(expression))
|
|
297
|
-
throw new Error(`${relation} relation predicate must be an expression`);
|
|
298
|
-
const metadata = pqsRelationMetadata[relation];
|
|
299
|
-
const conditions = [];
|
|
300
|
-
for (const [field, value] of Object.entries(expression)) {
|
|
301
|
-
if (field === "and" || field === "or") {
|
|
302
|
-
if (!Array.isArray(value))
|
|
303
|
-
throw new Error(`${field} must be an array`);
|
|
304
|
-
conditions.push(value.length === 0 ? field === "and" ? "true" : "false" : `(${value.map((child) => compilePhysicalWhere(relation, child, alias, profile, addValue)).join(` ${field} `)})`);
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
if (field === "not") {
|
|
308
|
-
conditions.push(`not (${compilePhysicalWhere(relation, value, alias, profile, addValue)})`);
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
const edge = pqsRelationEdges[relation]?.[field];
|
|
312
|
-
if (edge !== undefined) {
|
|
313
|
-
conditions.push(compilePhysicalRelationPredicate(edge.target, field, edge.targetColumn, edge.sourceColumn, edge.cardinality, value, alias, profile, addValue));
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
const column = metadata.fields[field];
|
|
317
|
-
if (column === undefined || value === null || typeof value !== "object" || Array.isArray(value))
|
|
318
|
-
throw new Error(`${field} is not a field of ${relation}`);
|
|
319
|
-
const filter = value;
|
|
320
|
-
const text = PqsSchemaProfileV1.jsonField(relation, field) && Array.isArray(filter.path) ? `${alias}."${column}" #>> ${addValue(filter.path)}::text[]` : `${alias}."${column}"`;
|
|
321
|
-
if (Array.isArray(filter.path) && (filter.path.length === 0 || filter.path.some((segment) => typeof segment !== "string" || segment.length === 0)))
|
|
322
|
-
throw new Error(`${field}.path must be a non-empty JSON path`);
|
|
323
|
-
if (filter.is === null)
|
|
324
|
-
conditions.push(`${text} is null`);
|
|
325
|
-
if (filter.isNot === null)
|
|
326
|
-
conditions.push(`${text} is not null`);
|
|
327
|
-
if (filter.equals !== undefined)
|
|
328
|
-
conditions.push(`${text} = ${addValue(filter.equals)}`);
|
|
329
|
-
if (filter.in !== undefined) {
|
|
330
|
-
if (!Array.isArray(filter.in))
|
|
331
|
-
throw new Error(`${field}.in must be an array`);
|
|
332
|
-
conditions.push(filter.in.length === 0 ? "false" : `${text} = any(${addValue(filter.in)})`);
|
|
333
|
-
}
|
|
334
|
-
if (filter.has !== undefined) {
|
|
335
|
-
if (!metadata.arrayFields.includes(field))
|
|
336
|
-
throw new Error(`${field} is not an array field of ${relation}`);
|
|
337
|
-
conditions.push(`${addValue(filter.has)} = any(${alias}."${column}")`);
|
|
338
|
-
}
|
|
339
|
-
for (const [operator, operand] of Object.entries(filter)) {
|
|
340
|
-
const sql = { lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[operator];
|
|
341
|
-
if (sql !== undefined)
|
|
342
|
-
conditions.push(`${text} ${sql} ${addValue(operand)}`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return conditions.length === 0 ? "true" : conditions.join(" and ");
|
|
346
|
-
}
|
|
347
|
-
function compileOrderBy(orderBy) {
|
|
348
|
-
if (orderBy === undefined) {
|
|
349
|
-
return "order by contract_row.contract_id asc";
|
|
350
|
-
}
|
|
351
|
-
const fields = {
|
|
352
|
-
contractId: "contract_row.contract_id",
|
|
353
|
-
createdEventOffset: "contract_row.created_at_ix",
|
|
354
|
-
createdAt: "created_tx.effective_at",
|
|
355
|
-
archivedEventOffset: "contract_row.archived_at_ix",
|
|
356
|
-
archivedAt: "archived_tx.effective_at",
|
|
357
|
-
};
|
|
358
|
-
const entries = orderBy.flatMap((entry) => Object.entries(entry));
|
|
359
|
-
if (entries.some(([field, direction]) => fields[field] === undefined || (direction !== "asc" && direction !== "desc"))) {
|
|
360
|
-
throw new Error("orderBy must contain valid one-field entries");
|
|
361
|
-
}
|
|
362
|
-
return `order by ${entries.map(([field, direction]) => `${fields[field]} ${direction}`).join(", ")}`;
|
|
363
|
-
}
|