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