@bjornpagen/bumbledb 1.3.1 → 2.0.0-rc.1

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.
Files changed (290) hide show
  1. package/COOKBOOK.md +153 -77
  2. package/README.md +107 -41
  3. package/dist/alternatives.d.ts +5 -6
  4. package/dist/alternatives.d.ts.map +1 -1
  5. package/dist/alternatives.js +5 -6
  6. package/dist/alternatives.js.map +1 -1
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +85 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/capacity.d.ts +14 -17
  12. package/dist/capacity.d.ts.map +1 -1
  13. package/dist/capacity.js +7 -7
  14. package/dist/capacity.js.map +1 -1
  15. package/dist/changes.d.ts +32 -40
  16. package/dist/changes.d.ts.map +1 -1
  17. package/dist/changes.js +86 -104
  18. package/dist/changes.js.map +1 -1
  19. package/dist/closed.d.ts +2 -2
  20. package/dist/closed.d.ts.map +1 -1
  21. package/dist/closed.js +5 -5
  22. package/dist/closed.js.map +1 -1
  23. package/dist/codec.d.ts +13 -27
  24. package/dist/codec.d.ts.map +1 -1
  25. package/dist/codec.js +20 -21
  26. package/dist/codec.js.map +1 -1
  27. package/dist/compile.d.ts +26 -50
  28. package/dist/compile.d.ts.map +1 -1
  29. package/dist/compile.js +75 -64
  30. package/dist/compile.js.map +1 -1
  31. package/dist/database/database.d.ts +98 -0
  32. package/dist/database/database.d.ts.map +1 -0
  33. package/dist/database/database.js +249 -0
  34. package/dist/database/database.js.map +1 -0
  35. package/dist/database/driver.d.ts +40 -0
  36. package/dist/database/driver.d.ts.map +1 -0
  37. package/dist/database/driver.js +68 -0
  38. package/dist/database/driver.js.map +1 -0
  39. package/dist/database/fs.d.ts +12 -0
  40. package/dist/database/fs.d.ts.map +1 -0
  41. package/dist/database/fs.js +120 -0
  42. package/dist/database/fs.js.map +1 -0
  43. package/dist/database/generate.d.ts +26 -0
  44. package/dist/database/generate.d.ts.map +1 -0
  45. package/dist/database/generate.js +102 -0
  46. package/dist/database/generate.js.map +1 -0
  47. package/dist/database/hosted.d.ts +32 -0
  48. package/dist/database/hosted.d.ts.map +1 -0
  49. package/dist/database/hosted.js +44 -0
  50. package/dist/database/hosted.js.map +1 -0
  51. package/dist/database/io.d.ts +94 -0
  52. package/dist/database/io.d.ts.map +1 -0
  53. package/dist/database/io.js +35 -0
  54. package/dist/database/io.js.map +1 -0
  55. package/dist/database/mem.d.ts +26 -0
  56. package/dist/database/mem.d.ts.map +1 -0
  57. package/dist/database/mem.js +67 -0
  58. package/dist/database/mem.js.map +1 -0
  59. package/dist/database/migration.d.ts +55 -0
  60. package/dist/database/migration.d.ts.map +1 -0
  61. package/dist/database/migration.js +46 -0
  62. package/dist/database/migration.js.map +1 -0
  63. package/dist/database/s3.d.ts +36 -0
  64. package/dist/database/s3.d.ts.map +1 -0
  65. package/dist/database/s3.js +141 -0
  66. package/dist/database/s3.js.map +1 -0
  67. package/dist/db.d.ts +44 -128
  68. package/dist/db.d.ts.map +1 -1
  69. package/dist/db.js +163 -287
  70. package/dist/db.js.map +1 -1
  71. package/dist/engine.d.ts +7 -0
  72. package/dist/engine.d.ts.map +1 -0
  73. package/dist/engine.js +2 -0
  74. package/dist/engine.js.map +1 -0
  75. package/dist/errors.d.ts +152 -46
  76. package/dist/errors.d.ts.map +1 -1
  77. package/dist/errors.js +107 -19
  78. package/dist/errors.js.map +1 -1
  79. package/dist/face.d.ts +5 -5
  80. package/dist/face.d.ts.map +1 -1
  81. package/dist/face.js +6 -6
  82. package/dist/face.js.map +1 -1
  83. package/dist/fields.d.ts +4 -5
  84. package/dist/fields.d.ts.map +1 -1
  85. package/dist/fields.js +3 -3
  86. package/dist/fields.js.map +1 -1
  87. package/dist/index.d.ts +60 -57
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +27 -28
  90. package/dist/index.js.map +1 -1
  91. package/dist/judgment.d.ts +1 -1
  92. package/dist/judgment.js +1 -1
  93. package/dist/law.d.ts +16 -68
  94. package/dist/law.d.ts.map +1 -1
  95. package/dist/law.js +3 -3
  96. package/dist/law.js.map +1 -1
  97. package/dist/lower.d.ts +3 -3
  98. package/dist/lower.d.ts.map +1 -1
  99. package/dist/lower.js +35 -39
  100. package/dist/lower.js.map +1 -1
  101. package/dist/native/addon.d.ts +23 -0
  102. package/dist/native/addon.d.ts.map +1 -0
  103. package/dist/native/addon.js +15 -0
  104. package/dist/native/addon.js.map +1 -0
  105. package/dist/native/binding.d.ts +996 -0
  106. package/dist/native/json.d.ts +14 -0
  107. package/dist/native/json.d.ts.map +1 -0
  108. package/dist/native/json.js +73 -0
  109. package/dist/native/json.js.map +1 -0
  110. package/dist/native/load.d.ts +9 -0
  111. package/dist/native/load.d.ts.map +1 -0
  112. package/dist/native/load.js +30 -0
  113. package/dist/native/load.js.map +1 -0
  114. package/dist/native/op.d.ts +29 -0
  115. package/dist/native/op.d.ts.map +1 -0
  116. package/dist/native/op.js +50 -0
  117. package/dist/native/op.js.map +1 -0
  118. package/dist/query/atom.d.ts +13 -14
  119. package/dist/query/atom.d.ts.map +1 -1
  120. package/dist/query/atom.js +2 -2
  121. package/dist/query/atom.js.map +1 -1
  122. package/dist/query/compute.d.ts +4 -4
  123. package/dist/query/compute.d.ts.map +1 -1
  124. package/dist/query/compute.js +4 -4
  125. package/dist/query/compute.js.map +1 -1
  126. package/dist/query/find.d.ts +8 -8
  127. package/dist/query/find.d.ts.map +1 -1
  128. package/dist/query/find.js +1 -1
  129. package/dist/query/lower.d.ts +22 -17
  130. package/dist/query/lower.d.ts.map +1 -1
  131. package/dist/query/lower.js +230 -343
  132. package/dist/query/lower.js.map +1 -1
  133. package/dist/query/run.d.ts +5 -5
  134. package/dist/query/run.d.ts.map +1 -1
  135. package/dist/query/run.js +32 -15
  136. package/dist/query/run.js.map +1 -1
  137. package/dist/query/scope.d.ts +20 -28
  138. package/dist/query/scope.d.ts.map +1 -1
  139. package/dist/query/scope.js +7 -7
  140. package/dist/query/scope.js.map +1 -1
  141. package/dist/query/segments.d.ts +2 -2
  142. package/dist/query/segments.d.ts.map +1 -1
  143. package/dist/query/segments.js +3 -3
  144. package/dist/query/segments.js.map +1 -1
  145. package/dist/relation.d.ts +1 -1
  146. package/dist/relation.d.ts.map +1 -1
  147. package/dist/relation.js +4 -4
  148. package/dist/relation.js.map +1 -1
  149. package/dist/result.d.ts +15 -22
  150. package/dist/result.d.ts.map +1 -1
  151. package/dist/result.js +30 -39
  152. package/dist/result.js.map +1 -1
  153. package/dist/rows.d.ts +9 -33
  154. package/dist/rows.d.ts.map +1 -1
  155. package/dist/rows.js +8 -12
  156. package/dist/rows.js.map +1 -1
  157. package/dist/runtime.d.ts +17 -20
  158. package/dist/runtime.d.ts.map +1 -1
  159. package/dist/runtime.js +41 -205
  160. package/dist/runtime.js.map +1 -1
  161. package/dist/scalar.d.ts +4 -4
  162. package/dist/scalar.d.ts.map +1 -1
  163. package/dist/scalar.js +24 -8
  164. package/dist/scalar.js.map +1 -1
  165. package/dist/schema.d.ts +7 -18
  166. package/dist/schema.d.ts.map +1 -1
  167. package/dist/schema.js +18 -228
  168. package/dist/schema.js.map +1 -1
  169. package/dist/selection.d.ts +4 -4
  170. package/dist/selection.d.ts.map +1 -1
  171. package/dist/selection.js +5 -5
  172. package/dist/selection.js.map +1 -1
  173. package/dist/shape.d.ts +5 -5
  174. package/dist/shape.d.ts.map +1 -1
  175. package/dist/spec.d.ts +7 -79
  176. package/dist/spec.d.ts.map +1 -1
  177. package/dist/spec.js.map +1 -1
  178. package/dist/statements.d.ts +3 -3
  179. package/dist/statements.d.ts.map +1 -1
  180. package/dist/statements.js +10 -10
  181. package/dist/statements.js.map +1 -1
  182. package/dist/uuid.d.ts +1 -1
  183. package/dist/uuid.d.ts.map +1 -1
  184. package/dist/uuid.js +1 -1
  185. package/dist/uuid.js.map +1 -1
  186. package/dist/values.d.ts +2 -2
  187. package/dist/values.d.ts.map +1 -1
  188. package/dist/values.js +2 -2
  189. package/dist/values.js.map +1 -1
  190. package/package.json +18 -18
  191. package/src/alternatives.ts +7 -8
  192. package/src/bin.ts +82 -0
  193. package/src/capacity.ts +20 -23
  194. package/src/changes.ts +134 -191
  195. package/src/closed.ts +5 -5
  196. package/src/codec.ts +35 -49
  197. package/src/compile.ts +91 -113
  198. package/src/database/database.ts +413 -0
  199. package/src/database/driver.ts +134 -0
  200. package/src/database/fs.ts +119 -0
  201. package/src/database/generate.ts +116 -0
  202. package/src/database/hosted.ts +102 -0
  203. package/src/database/io.ts +134 -0
  204. package/src/database/mem.ts +113 -0
  205. package/src/database/migration.ts +107 -0
  206. package/src/database/s3.ts +231 -0
  207. package/src/db.ts +290 -547
  208. package/src/engine.ts +14 -0
  209. package/src/errors.ts +118 -39
  210. package/src/face.ts +9 -9
  211. package/src/fields.ts +6 -7
  212. package/src/index.ts +92 -105
  213. package/src/judgment.ts +1 -1
  214. package/src/law.ts +18 -70
  215. package/src/lower.ts +61 -72
  216. package/src/native/addon.ts +54 -0
  217. package/src/native/binding.d.ts +996 -0
  218. package/src/native/json.ts +100 -0
  219. package/src/native/load.ts +31 -0
  220. package/src/native/op.ts +73 -0
  221. package/src/query/atom.ts +15 -16
  222. package/src/query/compute.ts +7 -7
  223. package/src/query/find.ts +8 -8
  224. package/src/query/lower.ts +270 -392
  225. package/src/query/run.ts +42 -21
  226. package/src/query/scope.ts +29 -37
  227. package/src/query/segments.ts +4 -4
  228. package/src/relation.ts +4 -4
  229. package/src/result.ts +53 -80
  230. package/src/rows.ts +18 -46
  231. package/src/runtime.ts +50 -277
  232. package/src/scalar.ts +32 -12
  233. package/src/schema.ts +23 -284
  234. package/src/selection.ts +6 -6
  235. package/src/shape.ts +5 -5
  236. package/src/spec.ts +7 -85
  237. package/src/statements.ts +11 -11
  238. package/src/uuid.ts +1 -1
  239. package/src/values.ts +4 -4
  240. package/dist/close.d.ts +0 -24
  241. package/dist/close.d.ts.map +0 -1
  242. package/dist/close.js +0 -34
  243. package/dist/close.js.map +0 -1
  244. package/dist/db-native.d.ts +0 -189
  245. package/dist/db-native.d.ts.map +0 -1
  246. package/dist/db-native.js +0 -13
  247. package/dist/db-native.js.map +0 -1
  248. package/dist/internal/log.d.ts +0 -26
  249. package/dist/internal/log.d.ts.map +0 -1
  250. package/dist/internal/log.js +0 -12
  251. package/dist/internal/log.js.map +0 -1
  252. package/dist/native.d.ts +0 -562
  253. package/dist/native.d.ts.map +0 -1
  254. package/dist/native.js +0 -123
  255. package/dist/native.js.map +0 -1
  256. package/dist/query/description.d.ts +0 -41
  257. package/dist/query/description.d.ts.map +0 -1
  258. package/dist/query/description.js +0 -311
  259. package/dist/query/description.js.map +0 -1
  260. package/dist/query/parse-ir.d.ts +0 -10
  261. package/dist/query/parse-ir.d.ts.map +0 -1
  262. package/dist/query/parse-ir.js +0 -280
  263. package/dist/query/parse-ir.js.map +0 -1
  264. package/dist/runtime-codes.d.ts +0 -2
  265. package/dist/runtime-codes.d.ts.map +0 -1
  266. package/dist/runtime-codes.js +0 -19
  267. package/dist/runtime-codes.js.map +0 -1
  268. package/dist/runtime-errors.d.ts +0 -138
  269. package/dist/runtime-errors.d.ts.map +0 -1
  270. package/dist/runtime-errors.js +0 -111
  271. package/dist/runtime-errors.js.map +0 -1
  272. package/dist/runtime-native.d.ts +0 -136
  273. package/dist/runtime-native.d.ts.map +0 -1
  274. package/dist/runtime-native.js +0 -5
  275. package/dist/runtime-native.js.map +0 -1
  276. package/dist/schema-file.d.ts +0 -7
  277. package/dist/schema-file.d.ts.map +0 -1
  278. package/dist/schema-file.js +0 -24
  279. package/dist/schema-file.js.map +0 -1
  280. package/pack-provenance.json +0 -7
  281. package/src/close.ts +0 -53
  282. package/src/db-native.ts +0 -235
  283. package/src/internal/log.ts +0 -46
  284. package/src/native.ts +0 -736
  285. package/src/query/description.ts +0 -424
  286. package/src/query/parse-ir.ts +0 -293
  287. package/src/runtime-codes.ts +0 -18
  288. package/src/runtime-errors.ts +0 -122
  289. package/src/runtime-native.ts +0 -181
  290. package/src/schema-file.ts +0 -38
@@ -1,424 +0,0 @@
1
- import { isDeepStrictEqual } from "node:util"
2
- import { sealedFieldsOf } from "#closed.ts"
3
- import { AuthoringError } from "#errors.ts"
4
- import { type AnyField, fieldDescriptor, type Infer, rosterOf, signaturesAgree } from "#fields.ts"
5
- import type { SchemaClasses } from "#law.ts"
6
- import type { AtomIr, ConditionTreeIr, QueryIr, RuleIr, ScalarExprIr, TaggedValue, TermIr } from "#native.ts"
7
- import type { AnyTreeChild, DerivedTable, FindColumn, InteriorData, RecData, RuleData } from "#query/atom.ts"
8
- import type { QueryNode } from "#query/compute.ts"
9
- import type { AnyQuery, ChainContext, Query } from "#query/lower.ts"
10
- import { alignedHeadOf, EMPTY_RULE, lowerQuery, makeRawChain, makeRawQuery, taggedCmpLiteral } from "#query/lower.ts"
11
- import { parseQueryIr } from "#query/parse-ir.ts"
12
- import { type AnyVar, type MatchOwner, makeParam, makeSetParam, type ParamsRecord, v } from "#query/scope.ts"
13
- import { difference, type IntervalVar, intersection } from "#query/segments.ts"
14
- import type { FieldsShape } from "#relation.ts"
15
- import { handleOf } from "#rows.ts"
16
- import {
17
- queryVarLeaf,
18
- scalarBinary,
19
- scalarCast,
20
- scalarFloatPredicate,
21
- scalarLiteral,
22
- scalarMeasure,
23
- scalarMulDiv,
24
- scalarNegate
25
- } from "#scalar.ts"
26
- import type { Schema, SchemaRelations } from "#schema.ts"
27
- import { schemaDescriptor } from "#schema.ts"
28
- import { arrayValue, recordValue, valueDescriptor } from "#values.ts"
29
-
30
- /** Logical ordinals are scoped to this description and the schema's ordered declarations. */
31
- interface QueryDescription {
32
- readonly ir: QueryIr
33
- readonly columns: readonly string[]
34
- readonly interiors: readonly DescriptionTable[]
35
- readonly recursion: DescriptionTable | undefined
36
- readonly parameters: readonly DescriptionParameter[]
37
- }
38
-
39
- interface DescriptionTable {
40
- readonly name: string
41
- readonly columns: readonly string[]
42
- }
43
-
44
- interface DescriptionParameter {
45
- readonly name: string
46
- /** Closed-membership literals generated by the typed builder; absent for supplied parameters. */
47
- readonly members: readonly TaggedValue[] | undefined
48
- }
49
-
50
- type DescriptionRow<F extends FieldsShape> = { readonly [K in keyof F]: Infer<F[K]> }
51
-
52
- /** Checked result fields remain exact through v(imported), just as at runtime.
53
- * Carrier classes come from the replayed head and remain runtime-checked. */
54
- type DescriptionHead<F extends FieldsShape> = {
55
- readonly [K in keyof F]: { readonly field: F[K]; readonly class: string | undefined }
56
- }
57
-
58
- function refused(context: string, expected: string): never {
59
- throw new AuthoringError({
60
- message: `${context}: ${expected}`,
61
- diagnostic: { code: "InvalidQuery", context, expected }
62
- })
63
- }
64
-
65
- function name(context: string, input: unknown): string {
66
- if (typeof input !== "string" || !input.isWellFormed()) return refused(context, "expected a well-formed string name")
67
- return input
68
- }
69
-
70
- function names(context: string, input: unknown): readonly string[] {
71
- const values = arrayValue(context, input, name)
72
- if (new Set(values).size !== values.length) refused(context, "names must be unique")
73
- return values
74
- }
75
-
76
- function table(context: string, input: unknown): DescriptionTable {
77
- const raw = recordValue(context, input, ["name", "columns"])
78
- return Object.freeze({ name: name(`${context}.name`, raw.name), columns: names(`${context}.columns`, raw.columns) })
79
- }
80
-
81
- function checkedDescription(input: unknown): QueryDescription {
82
- const raw = recordValue("query description", input, ["ir", "columns", "interiors", "recursion", "parameters"])
83
- const ir = parseQueryIr(raw.ir)
84
- const columns = names("query columns", raw.columns)
85
- if (columns.length !== ir.head.length) refused("query columns", "column names must match the query head width")
86
- const interiors = arrayValue("query interiors", raw.interiors, table)
87
- if (interiors.length !== ir.interiors.length) refused("query interiors", "names must match the IR interiors")
88
- for (const [ordinal, interior] of interiors.entries()) {
89
- if (interior.columns.length !== ir.interiors[ordinal]?.head.length)
90
- refused(`query interiors[${ordinal}]`, "column names must match the interior head width")
91
- }
92
- const recursion = raw.recursion === undefined ? undefined : table("query recursion", raw.recursion)
93
- if (
94
- ir.kind === "cq"
95
- ? recursion !== undefined
96
- : recursion === undefined || recursion.columns.length !== ir.rec.head.length
97
- )
98
- refused("query recursion", "recursion names must match the IR recursion head")
99
- const tableNames = [...interiors.map((entry) => entry.name), ...(recursion === undefined ? [] : [recursion.name])]
100
- if (new Set(tableNames).size !== tableNames.length) refused("query tables", "names must be unique")
101
- const parameters = arrayValue("query parameters", raw.parameters, (context, value): DescriptionParameter => {
102
- const record = recordValue(context, value, ["name", "members"])
103
- return Object.freeze({
104
- name: name(`${context}.name`, record.name),
105
- members:
106
- record.members === undefined ? undefined : arrayValue(`${context}.members`, record.members, valueDescriptor)
107
- })
108
- })
109
- if (new Set(parameters.map((entry) => entry.name)).size !== parameters.length)
110
- refused("query parameters", "names must be unique")
111
- return Object.freeze({ ir, columns, interiors, recursion, parameters })
112
- }
113
-
114
- /** Return an owned logical description; no builder variables or native resource handles escape. */
115
- function describeQuery(query: AnyQuery): QueryDescription {
116
- return checkedDescription({
117
- ir: lowerQuery(query),
118
- columns: query.data.finds.map((column) => column.name),
119
- interiors: query.data.interiors.map((entry, ordinal) => ({
120
- name: `interior${ordinal}`,
121
- columns: entry.finds.map((column) => column.name)
122
- })),
123
- recursion:
124
- query.data.kind === "cq"
125
- ? undefined
126
- : { name: "recursion", columns: query.data.rec.finds.map((column) => column.name) },
127
- parameters: query.data.params.map((entry) => ({
128
- name: entry.name,
129
- members: entry.membership?.kind === "set" ? entry.membership.values : undefined
130
- }))
131
- })
132
- }
133
-
134
- function hostLiteral(
135
- field: AnyField,
136
- value: TaggedValue,
137
- op: "binding" | "eq" | "ne" | "lt" | "le" | "gt" | "ge" | "pointIn" | "allen"
138
- ): unknown {
139
- const roster = rosterOf(field)
140
- let host: unknown = "value" in value ? value.value : { start: value.start, end: value.end }
141
- if (roster !== undefined) host = handleOf("query literal", roster, host)
142
- if (!isDeepStrictEqual(taggedCmpLiteral("query literal", field, host, op), value))
143
- refused("query literal", "literal tag must match the field domain")
144
- return host
145
- }
146
-
147
- /** Rehydrate into the existing checked rule builder, then use its ordinary lowering and execution. */
148
- function replayRule(
149
- context: ChainContext,
150
- rule: RuleIr,
151
- columns: readonly string[],
152
- parameters: readonly DescriptionParameter[],
153
- usedParameters: Set<number>
154
- ): RuleData {
155
- const relations = Object.values(context.theory.relations)
156
- const derived: readonly DerivedTable[] = [
157
- ...context.interiors,
158
- ...("rec" in context && "finds" in context.rec ? [context.rec] : [])
159
- ]
160
- const variables = new Map<number, AnyVar>()
161
- const relationAt = (ordinal: number): MatchOwner =>
162
- relations[ordinal] ?? refused("query atom", `unknown relation ordinal ${ordinal}`)
163
- const derivedAt = (ordinal: number): DerivedTable =>
164
- derived[ordinal] ?? refused("query atom", `unavailable interior ordinal ${ordinal}`)
165
- const variableAt = (ordinal: number): AnyVar =>
166
- variables.get(ordinal) ?? refused("query variable", `variable ${ordinal} has no positive binding`)
167
- const fieldsAt = (atom: AtomIr): readonly { readonly name: string; readonly field: AnyField }[] => {
168
- if (atom.source.kind === "edb") return sealedFieldsOf(relationAt(atom.source.relation))
169
- return derivedAt(atom.source.interior).finds.map((column) => ({
170
- name: column.name,
171
- field: column.slot?.field ?? refused("query interior", "column has no field")
172
- }))
173
- }
174
- // Establish each variable's source domain from a positive occurrence. The
175
- // shared builder verifies every other occurrence and all provenance joins.
176
- for (const atom of rule.atoms) {
177
- const fields = fieldsAt(atom)
178
- const minted: Readonly<Record<string, AnyVar>> =
179
- atom.source.kind === "edb"
180
- ? v(relationAt(atom.source.relation))
181
- : v({ schema: context.theory, data: { kind: "cq", finds: derivedAt(atom.source.interior).finds } })
182
- for (const [ordinal, term] of atom.bindings) {
183
- const field = fields[ordinal] ?? refused("query atom", `unknown field ordinal ${ordinal}`)
184
- if (term.kind === "var" && !variables.has(term.var)) {
185
- const variable = minted[field.name] ?? refused("query atom", "variable mint missing")
186
- variables.set(term.var, variable)
187
- }
188
- }
189
- }
190
- let chain = makeRawChain(context, EMPTY_RULE)
191
- const termValue = (term: TermIr, field: AnyField, op: Parameters<typeof hostLiteral>[2]): unknown => {
192
- if (term.kind === "var") return variableAt(term.var)
193
- if (term.kind === "literal") return hostLiteral(field, term.value, op)
194
- const parameter = parameters[term.param] ?? refused("query parameter", `unknown parameter ordinal ${term.param}`)
195
- usedParameters.add(term.param)
196
- if (parameter.members !== undefined) {
197
- if (term.kind !== "paramSet" || op !== "binding")
198
- refused("query parameter", "fixed membership belongs in a set binding")
199
- return parameter.members.map((value) => hostLiteral(field, value, "binding"))
200
- }
201
- return term.kind === "param" ? makeParam(parameter.name) : makeSetParam(parameter.name)
202
- }
203
- const bindingsOf = (atom: AtomIr): Readonly<Record<string, unknown>> => {
204
- const fields = fieldsAt(atom)
205
- return Object.fromEntries(
206
- atom.bindings.map(([ordinal, term]) => {
207
- const field = fields[ordinal] ?? refused("query atom", `unknown field ordinal ${ordinal}`)
208
- if (atom.source.kind === "interior" && term.kind !== "var")
209
- refused("query interior", "interior bindings require variables")
210
- return [field.name, termValue(term, field.field, "binding")]
211
- })
212
- )
213
- }
214
- for (const atom of rule.atoms) {
215
- chain =
216
- atom.source.kind === "edb"
217
- ? chain.match(relationAt(atom.source.relation), bindingsOf(atom))
218
- : chain.interior(derivedAt(atom.source.interior).name, bindingsOf(atom))
219
- }
220
- for (const atom of rule.negated) {
221
- chain = chain.where(
222
- atom.source.kind === "edb"
223
- ? { cond: "not", relation: relationAt(atom.source.relation), bindings: bindingsOf(atom) }
224
- : { cond: "notInterior", name: derivedAt(atom.source.interior).name, bindings: bindingsOf(atom) }
225
- )
226
- }
227
- const condition = (input: ConditionTreeIr): AnyTreeChild => {
228
- if (input.kind !== "leaf") {
229
- const children = input.children.map(condition)
230
- // This grammar contains comparisons and trees only, never negated atoms.
231
- return {
232
- cond: "tree",
233
- op: input.kind,
234
- children
235
- }
236
- }
237
- const { lhs, rhs, op } = input.cmp
238
- let anchor: AnyField
239
- if (lhs.kind === "var") anchor = variableAt(lhs.var).field
240
- else if (rhs.kind === "var") anchor = variableAt(rhs.var).field
241
- else return refused("query comparison", "requires a variable operand")
242
- return {
243
- cond: "cmp",
244
- op: op.kind,
245
- lhs: termValue(lhs, anchor, op.kind),
246
- rhs: termValue(rhs, anchor, op.kind),
247
- mask: op.kind === "allen" ? op.mask : undefined
248
- }
249
- }
250
- for (const input of rule.conditions) chain = chain.where(condition(input))
251
- const scalar = (input: ScalarExprIr): QueryNode => {
252
- switch (input.kind) {
253
- case "var": {
254
- const variable = variableAt(input.var)
255
- const kind = variable.field.kind
256
- if (variable.field.kind === "interval")
257
- return queryVarLeaf(
258
- variable,
259
- ({ u64: "intervalU64", i64: "intervalI64", f64: "intervalF64" } as const)[variable.field.element]
260
- )
261
- if (
262
- rosterOf(variable.field) !== undefined ||
263
- (kind !== "bool" && kind !== "u64" && kind !== "i64" && kind !== "f64")
264
- )
265
- refused("query compute", "requires a scalar variable")
266
- return queryVarLeaf(variable, kind)
267
- }
268
- case "literal": {
269
- const value = input.value
270
- switch (value.kind) {
271
- case "u64":
272
- return scalarLiteral({ u64: value.value })
273
- case "i64":
274
- return scalarLiteral({ i64: value.value })
275
- case "f64":
276
- return scalarLiteral({ f64: value.value })
277
- case "bool":
278
- return scalarLiteral({ bool: value.value })
279
- default:
280
- return refused("query compute", "requires a scalar literal")
281
- }
282
- }
283
- case "measure":
284
- return scalarMeasure("query compute", scalar(input.expr))
285
- case "mulDiv":
286
- return scalarMulDiv("query compute", scalar(input.a), scalar(input.b), scalar(input.divisor), input.rounding)
287
- case "negate":
288
- return scalarNegate("query compute", scalar(input.expr))
289
- case "isNaN":
290
- case "isFinite":
291
- return scalarFloatPredicate("query compute", input.kind, scalar(input.expr))
292
- case "cast":
293
- return scalarCast(
294
- "query compute",
295
- input.cast,
296
- ({ toI64Exact: "i64", toU64Exact: "u64", toF64: "f64", toF64Exact: "f64" } as const)[input.cast],
297
- scalar(input.expr)
298
- )
299
- default:
300
- return scalarBinary("query compute", input.kind, scalar(input.left), scalar(input.right))
301
- }
302
- }
303
- const finds = Object.fromEntries(
304
- rule.finds.map((find, ordinal) => {
305
- const column = columns[ordinal] ?? refused("query find", "column name missing")
306
- let value: unknown
307
- if (find.kind === "var") value = variableAt(find.var)
308
- else if (find.kind === "compute") value = scalar(find.expr)
309
- else if (find.kind === "segments")
310
- value = (find.op === "intersection" ? intersection : difference)(
311
- variableAt(find.left) as IntervalVar,
312
- variableAt(find.right) as IntervalVar
313
- )
314
- else if (find.kind === "count") value = { agg: "count" }
315
- else value = { agg: find.kind === "pack" ? "pack" : find.op.kind, over: variableAt(find.over) }
316
- return [column, value]
317
- })
318
- )
319
- return chain.find(finds).rule
320
- }
321
-
322
- function queryFromDescription<Rels extends SchemaRelations, Classes extends SchemaClasses, const F extends FieldsShape>(
323
- schema: Schema<Rels, Classes>,
324
- input: unknown,
325
- result: F
326
- ): Query<Rels, DescriptionRow<F>, ParamsRecord, Classes, DescriptionHead<F>> {
327
- const theory = schemaDescriptor(schema)
328
- const description = checkedDescription(input)
329
- const resultRecord = recordValue(
330
- "query result",
331
- result,
332
- typeof result === "object" && result !== null ? Object.keys(result) : []
333
- )
334
- const fields = Object.fromEntries(
335
- Object.entries(resultRecord).map(([name, field]) => [name, fieldDescriptor(`query result.${name}`, field)])
336
- )
337
- const interiors: InteriorData[] = []
338
- const usedParameters = new Set<number>()
339
- for (const [ordinal, item] of description.ir.interiors.entries()) {
340
- const names = description.interiors[ordinal] ?? refused("query interior", "names missing")
341
- const rules = item.rules.map((rule) =>
342
- replayRule(
343
- { theory, classes: theory.classes, kind: "interior", self: names.name, interiors },
344
- rule,
345
- names.columns,
346
- description.parameters,
347
- usedParameters
348
- )
349
- )
350
- interiors.push(
351
- Object.freeze({
352
- name: names.name,
353
- finds: alignedHeadOf(`interior ${names.name}`, rules),
354
- rules: Object.freeze(rules)
355
- })
356
- )
357
- }
358
- let rec: RecData | undefined
359
- if (description.ir.kind === "reach") {
360
- const names = description.recursion ?? refused("query recursion", "names missing")
361
- const self = { name: names.name }
362
- const base = description.ir.rec.base.map((rule) =>
363
- replayRule(
364
- { theory, classes: theory.classes, kind: "rec-base", self, rec: self, interiors },
365
- rule,
366
- names.columns,
367
- description.parameters,
368
- usedParameters
369
- )
370
- )
371
- const baseHead = alignedHeadOf("recursion base", base)
372
- const first = baseHead[0] ?? refused("query recursion", "requires a nonempty head")
373
- const head: { readonly name: string; readonly finds: readonly [FindColumn, ...FindColumn[]] } = {
374
- name: names.name,
375
- finds: [first, ...baseHead.slice(1)]
376
- }
377
- const steps = description.ir.rec.rec.map((rule) =>
378
- replayRule(
379
- { theory, classes: theory.classes, kind: "rec-arm", self: head, rec: head, interiors },
380
- rule,
381
- names.columns,
382
- description.parameters,
383
- usedParameters
384
- )
385
- )
386
- const finds = alignedHeadOf("recursion", [...base, ...steps])
387
- const firstFind = finds[0] ?? refused("query recursion", "requires a nonempty head")
388
- const firstBase = base[0] ?? refused("query recursion", "requires a base rule")
389
- const firstStep = steps[0] ?? refused("query recursion", "requires a recursive rule")
390
- rec = Object.freeze({
391
- name: names.name,
392
- finds: Object.freeze([firstFind, ...finds.slice(1)] as const),
393
- base: Object.freeze([firstBase, ...base.slice(1)] as const),
394
- rec: Object.freeze([firstStep, ...steps.slice(1)] as const)
395
- })
396
- }
397
- const context: ChainContext = {
398
- theory,
399
- classes: theory.classes,
400
- kind: "query",
401
- interiors,
402
- ...(rec === undefined ? {} : { rec })
403
- }
404
- const rules = description.ir.rules.map((rule) =>
405
- replayRule(context, rule, description.columns, description.parameters, usedParameters)
406
- )
407
- const value = makeRawQuery(theory, interiors, rec, rules)
408
- const expected = Object.entries(fields)
409
- if (
410
- expected.length !== value.data.finds.length ||
411
- expected.some(([name, field], ordinal) => {
412
- const column = value.data.finds[ordinal]
413
- return column?.name !== name || column.slot === undefined || !signaturesAgree(column.slot.field, field)
414
- })
415
- )
416
- refused("query result", "result names and field domains must match the derived query head in order")
417
- if (usedParameters.size !== description.parameters.length) refused("query parameters", "declared parameter is unused")
418
- // Complete shared lowering also checks tagged literals and ordinal bounds.
419
- lowerQuery(value)
420
- return value as unknown as Query<Rels, DescriptionRow<F>, ParamsRecord, Classes, DescriptionHead<F>>
421
- }
422
-
423
- export type { DescriptionHead, DescriptionParameter, DescriptionRow, DescriptionTable, QueryDescription }
424
- export { describeQuery, queryFromDescription }