Add explicit CRDT edit contracts and structured query values

This commit is contained in:
Timothy J. Aveni
2026-09-19 22:33:20 -07:00
parent 0555c929b1
commit 4c0c404a39
27 changed files with 3272 additions and 2745 deletions
+47 -12
View File
@@ -154,8 +154,19 @@ export function relationalCompiler(
): QueryExpression => {
requireAvailable(names, available, node);
const meta = names.at(-2) === "_qx";
const { row, path, nullable } = walk(root, names.slice(0, meta ? -2 : -1), uses, node);
const name = names.at(-1)!;
let fieldIndex = names.length - 1;
if (!meta) {
let cursor = root;
for (let index = 0; index < names.length; index++) {
if (cursor.kind === "object" && rowFields(cursor).get(names[index]!)?.kind === "leaf") {
fieldIndex = index;
break;
}
if (index < names.length - 1) cursor = walk(root, names.slice(0, index + 1), uses, node).row;
}
}
const { row, path, nullable } = walk(root, names.slice(0, meta ? -2 : fieldIndex), uses, node);
const name = names[meta ? names.length - 1 : fieldIndex]!;
let type: ValueType, leaf: QueryExpression["leaf"];
if (meta) {
if (name === "ref" && row.kind === "object") {
@@ -185,7 +196,24 @@ export function relationalCompiler(
for (const use of uses) mark(row.interfaceRevisionId, name, use, node);
const member = env.contracts.get(row.interfaceRevisionId)!.members.find((m) => m.displayName === name)!;
type = field.type;
leaf = { kind: "field", interfaceRevisionId: row.interfaceRevisionId, memberId: member.id };
const valuePath = names.slice(fieldIndex + 1);
let valueNullable = false;
for (const component of valuePath) {
if (type.kind === "optional") {
valueNullable = true;
type = type.value;
}
if (type.kind !== "record" || !Object.hasOwn(type.fields, component))
return fail("QUERY_FIELD_NOT_QUERYABLE", `Unknown scalar value path ${names.join(".")}`, node);
type = type.fields[component]!;
}
if (valueNullable && type.kind !== "optional") type = valueType.optional(type);
leaf = {
kind: "field",
interfaceRevisionId: row.interfaceRevisionId,
memberId: member.id,
...(valuePath.length ? { valuePath } : {}),
};
}
return { path, leaf, type: nullable && type.kind !== "optional" ? valueType.optional(type) : type };
};
@@ -356,15 +384,22 @@ export function relationalCompiler(
}
}
} else {
const expr = expression(row, [...base, name], ["predicate"], field, available);
children.push(
...(comparisons(field.value, (operator, value) => ({
kind: "compare",
expression: expr,
operator,
value,
})) as QueryPredicate[]),
);
const valuePredicate = (names: string[], value: ValueNode) => {
const expr = expression(row, names, ["predicate"], field, available);
const type = expr.type.kind === "optional" ? expr.type.value : expr.type;
if (type.kind === "record") {
for (const child of pairs(value)) valuePredicate([...names, child.name.value], child.value);
} else
children.push(
...(comparisons(value, (operator, value) => ({
kind: "compare",
expression: expr,
operator,
value,
})) as QueryPredicate[]),
);
};
valuePredicate([...base, name], field.value);
}
}
};