import type { InterfaceRevisionId, MemberId, ValueType } from "../capability-model/types.js"; import { valueType } from "../capability-model/types.js"; import { QueryCompileError, type QueryArgument, type QueryUse } from "./types.js"; /** A row is an input membership, not a newly allocated Camino object. */ export type QueryRow = | { kind: "object"; interfaceRevisionId: InterfaceRevisionId; membership?: { keyType?: "string" | "boolean" | "int64" }; } | { kind: "pair"; source: QueryRow; target: QueryRow }; export type AggregateOperator = "count" | "countPresent" | "sum" | "avg" | "min" | "max"; export const aggregateOperators: readonly AggregateOperator[] = ["count", "countPresent", "sum", "avg", "min", "max"]; export function aggregateType(operator: AggregateOperator, input?: ValueType): ValueType { if (operator === "count") return valueType.uint64; const base = input?.kind === "optional" ? input.value : input; if (operator === "countPresent" && (base?.kind === "scalar" || base?.kind === "object-ref")) return valueType.uint64; if (base?.kind !== "scalar") throw new QueryCompileError("QUERY_AGGREGATE_TYPE", `${operator} requires a supported scalar operand`); const numeric = ["int32", "uint32", "int64", "uint64", "double"].includes(base.name); if ((operator === "min" || operator === "max") && (numeric || base.name === "string")) return valueType.optional(base); if (numeric && operator === "avg") return valueType.optional(valueType.double); if (numeric && operator === "sum") return valueType.optional( base.name === "double" ? valueType.double : base.name.startsWith("u") ? valueType.uint64 : valueType.int64, ); throw new QueryCompileError("QUERY_AGGREGATE_TYPE", `${operator} does not accept ${base.name}`); } export function queryKeyType(type: ValueType): boolean { if (type.kind === "optional") return queryKeyType(type.value); return type.kind === "object-ref" || (type.kind === "scalar" && type.name !== "bytes"); } /** Paths are resolved once by the compiler. Runtime never resolves authored names. */ export type QueryPathStep = | { kind: "source" | "target" } | { kind: "relation"; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; targetInterfaceRevisionId: InterfaceRevisionId; optional: boolean; }; export interface QueryExpression { path: QueryPathStep[]; leaf: | { kind: "field"; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId } | { kind: "ref"; interfaceRevisionId: InterfaceRevisionId } | { kind: "entry" | "mapKey" }; type: ValueType; } export interface QueryReduction { operator: AggregateOperator; operand?: QueryExpression; type: ValueType; } export type QueryPredicate = | { kind: "and" | "or"; children: QueryPredicate[] } | { kind: "not"; child: QueryPredicate } | { kind: "compare"; expression: QueryExpression; operator: "eq" | "in" | "isNull" | "lt" | "lte" | "gt" | "gte"; value: QueryArgument; } | { kind: "relation"; path: QueryPathStep[]; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; targetInterfaceRevisionId: InterfaceRevisionId; operator: "some" | "none" | "is" | "isNull"; predicate?: QueryPredicate; value?: QueryArgument; } | { kind: "reduce"; path: QueryPathStep[]; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; targetInterfaceRevisionId: InterfaceRevisionId; where?: QueryPredicate; having: QueryAggregatePredicate; }; export type QueryAggregatePredicate = | { kind: "and" | "or"; children: QueryAggregatePredicate[] } | { kind: "not"; child: QueryAggregatePredicate } | { kind: "compare"; expression: QueryExpression | QueryReduction; operator: "eq" | "in" | "isNull" | "lt" | "lte" | "gt" | "gte"; value: QueryArgument; }; export interface QueryKey { path: string[]; expression: QueryExpression; } export interface QueryRelationalStage { id: number; input?: number; row: QueryRow; operation: | { kind: "source"; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; targetInterfaceRevisionId: InterfaceRevisionId; } | { kind: "filter"; predicate: QueryPredicate } | { kind: "expand"; path: QueryPathStep[]; interfaceRevisionId: InterfaceRevisionId; memberId: MemberId; targetInterfaceRevisionId: InterfaceRevisionId; } | { kind: "distinct"; keys: QueryKey[] }; } export interface QueryRelationalTerminal { stage: number; path: string[]; kind: "aggregate" | "groups"; keys: QueryKey[]; reductions: { path: string[]; reduction: QueryReduction }[]; where?: QueryPredicate; having?: QueryAggregatePredicate; order: { expression: QueryExpression | QueryReduction; descending: boolean }[]; first?: QueryArgument; all?: QueryArgument; after?: QueryArgument; /** Projection tree remains separate from the operator plan. */ selection: import("./types.js").QuerySelection[]; residual: boolean; } export interface QueryRelationalPlan { stages: QueryRelationalStage[]; terminals: QueryRelationalTerminal[]; } export type QueryEffectRecorder = ( id: InterfaceRevisionId, name: string, use: QueryUse, node: import("graphql").ASTNode, ) => void;