@cavelang/query 0.27.14 → 0.29.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.
- package/Authors.md +5 -0
- package/License.md +6 -0
- package/README.md +76 -11
- package/bench/transitive.ts +42 -0
- package/dist/bench/transitive.d.ts +3 -0
- package/dist/bench/transitive.d.ts.map +1 -0
- package/dist/bench/transitive.js +38 -0
- package/dist/bench/transitive.js.map +1 -0
- package/dist/src/bounded.d.ts +3 -8
- package/dist/src/bounded.d.ts.map +1 -1
- package/dist/src/bounded.js +13 -11
- package/dist/src/bounded.js.map +1 -1
- package/dist/src/compile.d.ts +33 -4
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +153 -103
- package/dist/src/compile.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/page.d.ts +23 -0
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js +131 -0
- package/dist/src/page.js.map +1 -0
- package/dist/src/record.d.ts +21 -0
- package/dist/src/record.d.ts.map +1 -0
- package/dist/src/record.js +41 -0
- package/dist/src/record.js.map +1 -0
- package/package.json +21 -8
- package/src/bounded.ts +26 -13
- package/src/compile.ts +151 -78
- package/src/index.ts +4 -0
- package/src/page.ts +149 -0
- package/src/record.ts +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Versioned JSON projection of CAVE-Q matches. */
|
|
2
|
+
import { Record as ClaimRecord } from '@cavelang/store/adapter';
|
|
3
|
+
import type { Store } from '@cavelang/store/adapter';
|
|
4
|
+
import type { Match, Options } from './compile.ts';
|
|
5
|
+
export declare const format: "cave.query-match";
|
|
6
|
+
export declare const version: 1;
|
|
7
|
+
export type t = {
|
|
8
|
+
readonly format: typeof format;
|
|
9
|
+
readonly version: typeof version;
|
|
10
|
+
readonly bindings: Readonly<Record<string, string>>;
|
|
11
|
+
readonly claim?: ClaimRecord.t;
|
|
12
|
+
readonly support?: readonly ClaimRecord.t[];
|
|
13
|
+
readonly at?: Match['at'];
|
|
14
|
+
};
|
|
15
|
+
export declare const of: (store: Store, match: Match) => t;
|
|
16
|
+
/** Decode a persisted query-match record and every nested claim record. */
|
|
17
|
+
export declare const decode: (input: string | unknown) => t;
|
|
18
|
+
export declare const encode: (record: t, space?: number) => string;
|
|
19
|
+
/** Stable library/JSON query surface; raw `query()` remains storage-oriented. */
|
|
20
|
+
export declare const queryRecords: (store: Store, input: string, options?: Options) => t[];
|
|
21
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/record.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAEnD,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGlD,eAAO,MAAM,MAAM,EAAG,kBAA2B,CAAA;AACjD,eAAO,MAAM,OAAO,EAAG,CAAU,CAAA;AAEjC,MAAM,MAAM,CAAC,GAAG;IACd,QAAQ,CAAC,MAAM,EAAE,OAAO,MAAM,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,OAAO,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,WAAW,CAAC,CAAC,EAAE,CAAA;IAC3C,QAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,EAAE,GAAI,OAAO,KAAK,EAAE,OAAO,KAAK,KAAG,CAO9C,CAAA;AAKF,2EAA2E;AAC3E,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,GAAG,OAAO,KAAG,CAqBhD,CAAA;AAED,eAAO,MAAM,MAAM,GAAI,QAAQ,CAAC,EAAE,QAAQ,MAAM,KAAG,MACT,CAAA;AAE1C,iFAAiF;AACjF,eAAO,MAAM,YAAY,GACvB,OAAO,KAAK,EACZ,OAAO,MAAM,EACb,UAAS,OAAY,KACpB,CAAC,EAAiE,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Versioned JSON projection of CAVE-Q matches. */
|
|
2
|
+
import { Record as ClaimRecord } from '@cavelang/store/adapter';
|
|
3
|
+
import { query } from "./bounded.js";
|
|
4
|
+
export const format = 'cave.query-match';
|
|
5
|
+
export const version = 1;
|
|
6
|
+
export const of = (store, match) => ({
|
|
7
|
+
format,
|
|
8
|
+
version,
|
|
9
|
+
bindings: match.bindings,
|
|
10
|
+
...(match.row === undefined ? {} : { claim: store.recordOf(match.row) }),
|
|
11
|
+
...(match.rows === undefined ? {} : { support: match.rows.map(store.recordOf) }),
|
|
12
|
+
...(match.at === undefined ? {} : { at: match.at }),
|
|
13
|
+
});
|
|
14
|
+
const object = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
15
|
+
/** Decode a persisted query-match record and every nested claim record. */
|
|
16
|
+
export const decode = (input) => {
|
|
17
|
+
const value = typeof input === 'string' ? JSON.parse(input) : input;
|
|
18
|
+
if (!object(value) || value['format'] !== format) {
|
|
19
|
+
throw new Error(`CAVE query record: expected format ${JSON.stringify(format)}`);
|
|
20
|
+
}
|
|
21
|
+
if (value['version'] !== version) {
|
|
22
|
+
throw new Error(`CAVE query record: unsupported ${format} version ${JSON.stringify(value['version'])}`);
|
|
23
|
+
}
|
|
24
|
+
if (!object(value['bindings']) ||
|
|
25
|
+
!Object.values(value['bindings']).every(binding => typeof binding === 'string') ||
|
|
26
|
+
(value['support'] !== undefined && !Array.isArray(value['support']))) {
|
|
27
|
+
throw new Error(`CAVE query record: malformed ${format}/v${version}`);
|
|
28
|
+
}
|
|
29
|
+
const decoded = value;
|
|
30
|
+
return {
|
|
31
|
+
...decoded,
|
|
32
|
+
...(decoded.claim === undefined ? {} : { claim: ClaimRecord.decode(decoded.claim) }),
|
|
33
|
+
...(decoded.support === undefined ? {} : {
|
|
34
|
+
support: decoded.support.map(record => ClaimRecord.decode(record))
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export const encode = (record, space) => JSON.stringify(record, undefined, space);
|
|
39
|
+
/** Stable library/JSON query surface; raw `query()` remains storage-oriented. */
|
|
40
|
+
export const queryRecords = (store, input, options = {}) => query(store, input, options).map(match => of(store, match));
|
|
41
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/record.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavelang/query",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "CAVE-Q — graph-pattern queries
|
|
5
|
+
"description": "CAVE-Q — graph-pattern queries with variables, lifecycle and inverse verbs, transitive hops, and filters.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"cave",
|
|
8
|
+
"knowledge-graph",
|
|
9
|
+
"knowledge-representation",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/mirek/cave#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/mirek/cave/issues"
|
|
15
|
+
},
|
|
6
16
|
"license": "CC0-1.0",
|
|
7
17
|
"author": "Mirek Rusin (https://github.com/mirek)",
|
|
8
18
|
"repository": {
|
|
@@ -20,26 +30,29 @@
|
|
|
20
30
|
}
|
|
21
31
|
},
|
|
22
32
|
"files": [
|
|
33
|
+
"bench",
|
|
23
34
|
"dist",
|
|
24
35
|
"!dist/test",
|
|
25
36
|
"!dist/tsconfig.tsbuildinfo",
|
|
26
37
|
"src",
|
|
27
38
|
"!src/**/*.test.ts",
|
|
28
39
|
"README.md",
|
|
29
|
-
"License.md"
|
|
40
|
+
"License.md",
|
|
41
|
+
"Authors.md"
|
|
30
42
|
],
|
|
31
43
|
"publishConfig": {
|
|
32
44
|
"access": "public"
|
|
33
45
|
},
|
|
34
46
|
"dependencies": {
|
|
35
|
-
"@cavelang/canonical": "0.
|
|
36
|
-
"@cavelang/parser": "0.
|
|
37
|
-
"@cavelang/
|
|
38
|
-
"@cavelang/
|
|
47
|
+
"@cavelang/canonical": "0.29.1",
|
|
48
|
+
"@cavelang/parser": "0.29.1",
|
|
49
|
+
"@cavelang/store": "0.29.1",
|
|
50
|
+
"@cavelang/core": "0.29.1"
|
|
39
51
|
},
|
|
40
52
|
"scripts": {
|
|
53
|
+
"bench:transitive": "node --disable-warning=ExperimentalWarning bench/transitive.ts",
|
|
41
54
|
"build": "tsc -b",
|
|
42
|
-
"test": "node --disable-warning=ExperimentalWarning --test
|
|
55
|
+
"test": "node --disable-warning=ExperimentalWarning --test \"test/*.test.ts\"",
|
|
43
56
|
"typecheck": "tsc -b"
|
|
44
57
|
}
|
|
45
58
|
}
|
package/src/bounded.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Value } from '@cavelang/core'
|
|
2
|
-
import type { Store } from '@cavelang/store'
|
|
2
|
+
import type { Store } from '@cavelang/store/adapter'
|
|
3
3
|
import * as Compile from './compile.ts'
|
|
4
4
|
import * as Pattern from './pattern.ts'
|
|
5
5
|
|
|
6
6
|
export type { Match, Options } from './compile.ts'
|
|
7
|
+
export type { Window } from './compile.ts'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Compile historical queries against vocabulary reconstructed at the same
|
|
@@ -25,17 +26,17 @@ const storeAtBoundary = (store: Store, options: Compile.Options): Store => {
|
|
|
25
26
|
* value filter pushes the normalized comparison into SQL; the final check
|
|
26
27
|
* preserves exact unit and approximation semantics.
|
|
27
28
|
*/
|
|
28
|
-
|
|
29
|
+
const execute = (
|
|
29
30
|
store: Store,
|
|
30
31
|
pattern: Pattern.t,
|
|
31
32
|
options: Compile.Options = {}
|
|
32
|
-
): Compile.
|
|
33
|
+
): Compile.Window => {
|
|
33
34
|
if (pattern.payload.kind !== 'attribute' || pattern.payload.value.kind !== 'term') {
|
|
34
|
-
return Compile.
|
|
35
|
+
return Compile.window(storeAtBoundary(store, options), pattern, options)
|
|
35
36
|
}
|
|
36
37
|
const value = Value.parse(pattern.payload.value.text)
|
|
37
38
|
if (value.kind !== 'number' || value.num === undefined) {
|
|
38
|
-
return Compile.
|
|
39
|
+
return Compile.window(storeAtBoundary(store, options), pattern, options)
|
|
39
40
|
}
|
|
40
41
|
const expectedNum = value.num
|
|
41
42
|
const filter: Pattern.Filter = {
|
|
@@ -53,16 +54,28 @@ export const match = (
|
|
|
53
54
|
}
|
|
54
55
|
const expectedUnit = value.unit ?? null
|
|
55
56
|
const expectedApprox = value.approx ? 1 : 0
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
const result = Compile.window(storeAtBoundary(store, options), normalized, options)
|
|
58
|
+
return { ...result, matches: result.matches.filter(match => {
|
|
59
|
+
const row = match.row
|
|
60
|
+
return row !== undefined &&
|
|
61
|
+
row.value_num === expectedNum &&
|
|
62
|
+
row.value_unit === expectedUnit &&
|
|
63
|
+
row.value_approx === expectedApprox
|
|
64
|
+
}) }
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
export const window = (
|
|
68
|
+
store: Store,
|
|
69
|
+
pattern: Pattern.t,
|
|
70
|
+
options: Compile.Options = {}
|
|
71
|
+
): Compile.Window => execute(store, pattern, options)
|
|
72
|
+
|
|
73
|
+
export const match = (
|
|
74
|
+
store: Store,
|
|
75
|
+
pattern: Pattern.t,
|
|
76
|
+
options: Compile.Options = {}
|
|
77
|
+
): Compile.Match[] => execute(store, pattern, options).matches
|
|
78
|
+
|
|
66
79
|
export const query = (
|
|
67
80
|
store: Store,
|
|
68
81
|
input: string,
|
package/src/compile.ts
CHANGED
|
@@ -14,16 +14,22 @@
|
|
|
14
14
|
* on the object side.
|
|
15
15
|
*
|
|
16
16
|
* Transitive patterns (`terrier EXTENDS+ animal`) compile to a recursive
|
|
17
|
-
* CTE over current, positive, non-retracted edges
|
|
17
|
+
* CTE over current, positive, non-retracted edges. Recursive state is the
|
|
18
|
+
* reachable endpoint pair itself, so SQL `UNION` deduplication terminates
|
|
19
|
+
* cycles without an arbitrary semantic depth cap.
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
|
-
import { Time,
|
|
22
|
+
import { Time, Value } from '@cavelang/core'
|
|
21
23
|
import { Registry } from '@cavelang/canonical'
|
|
22
|
-
import { Resolve, Row } from '@cavelang/store'
|
|
23
|
-
import type { Store } from '@cavelang/store'
|
|
24
|
+
import { QuerySql, Resolve, Row } from '@cavelang/store/adapter'
|
|
25
|
+
import type { Store } from '@cavelang/store/adapter'
|
|
24
26
|
import * as Pattern from './pattern.ts'
|
|
25
27
|
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* One storage-oriented query solution. `row`/`rows` expose SQLite-shaped
|
|
30
|
+
* data for in-process reasoning engines; use `queryRecords()` for versioned
|
|
31
|
+
* public JSON and cross-process integrations.
|
|
32
|
+
*/
|
|
27
33
|
export type Match = {
|
|
28
34
|
readonly bindings: Readonly<Record<string, string>>
|
|
29
35
|
readonly row?: Row.t
|
|
@@ -43,6 +49,10 @@ export type Match = {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
export type Options = {
|
|
52
|
+
/** Maximum rows SQLite may return. Prefer the snapshot-aware page API for continuations. */
|
|
53
|
+
readonly limit?: number
|
|
54
|
+
/** Internal SQL offset used by the snapshot-aware page API. */
|
|
55
|
+
readonly offset?: number
|
|
46
56
|
/** Match all appended rows, not only current beliefs. */
|
|
47
57
|
readonly all?: boolean
|
|
48
58
|
/**
|
|
@@ -100,47 +110,44 @@ export type Options = {
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
/**
|
|
103
|
-
* UUIDv7 interval `[lo, hi)` for a tx filter value: a
|
|
104
|
-
* whole UTC
|
|
105
|
-
* adjacent operators distinguishable (`<=` includes the boundary
|
|
106
|
-
* `<` excludes) and make `WHERE tx = 2026-01-01` mean "recorded that day".
|
|
113
|
+
* UUIDv7 interval `[lo, hi)` for a tx filter value: a date-like value covers
|
|
114
|
+
* its whole UTC period and a timestamp covers one second. Interval semantics
|
|
115
|
+
* keep adjacent operators distinguishable (`<=` includes the boundary period
|
|
116
|
+
* that `<` excludes) and make `WHERE tx = 2026-01-01` mean "recorded that day".
|
|
107
117
|
*/
|
|
108
118
|
const txBounds = (text: string, label = 'tx date'): { lo: string, hi: string } => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
if (Number.isNaN(start)) {
|
|
119
|
+
const bounds = QuerySql.transactionBounds(text)
|
|
120
|
+
if (bounds === undefined) {
|
|
112
121
|
throw new Error(`CAVE-Q: cannot parse ${label} ${JSON.stringify(text)}`)
|
|
113
122
|
}
|
|
114
|
-
|
|
115
|
-
return { lo: Uuidv7.at(start, 0, new Uint8Array(8)), hi: Uuidv7.at(end, 0, new Uint8Array(8)) }
|
|
123
|
+
return bounds
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
/**
|
|
119
127
|
* SQL tx condition for an as-of boundary (spec §12.3). A UUIDv7 names an
|
|
120
|
-
* exact transaction, included — belief as of that append; a date
|
|
121
|
-
* timestamp is inclusive of the whole named
|
|
128
|
+
* exact transaction, included — belief as of that append; a date-like value
|
|
129
|
+
* or timestamp is inclusive of the whole named period/second, the same interval
|
|
122
130
|
* semantics as `WHERE tx <=` (§12.2). Both forms inline as literals: the
|
|
123
|
-
* id is shape-validated hex-and-dashes and the interval bound
|
|
124
|
-
*
|
|
131
|
+
* id is shape-validated hex-and-dashes and the interval bound is a generated
|
|
132
|
+
* UUIDv7 — no free-form text reaches the SQL, and a literal keeps
|
|
125
133
|
* the fragment embeddable in CTEs that positional parameters would
|
|
126
134
|
* complicate.
|
|
127
135
|
*/
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
136
|
+
const asOfBoundary = (asOf: string): QuerySql.AsOfBoundary => {
|
|
137
|
+
const boundary = QuerySql.asOfBoundary(asOf)
|
|
138
|
+
if (boundary === undefined) {
|
|
139
|
+
throw new Error(`CAVE-Q: cannot parse as-of boundary ${JSON.stringify(asOf)}`)
|
|
140
|
+
}
|
|
141
|
+
return boundary
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
/** Row universe under resolution: every appended row, or only rows recorded up to the as-of boundary. */
|
|
134
145
|
const claimsSql = (asOf: undefined | string): string =>
|
|
135
|
-
asOf === undefined ?
|
|
146
|
+
asOf === undefined ? QuerySql.claims() :
|
|
147
|
+
QuerySql.claims(asOfBoundary(asOf))
|
|
136
148
|
|
|
137
|
-
const currentSql = (asOf: undefined | string): string =>
|
|
138
|
-
|
|
139
|
-
JOIN (
|
|
140
|
-
SELECT claim_key, MAX(tx) AS max_tx
|
|
141
|
-
FROM ${claimsSql(asOf)} GROUP BY claim_key
|
|
142
|
-
) latest ON c.claim_key = latest.claim_key AND c.tx = latest.max_tx
|
|
143
|
-
`
|
|
149
|
+
const currentSql = (asOf: undefined | string): string =>
|
|
150
|
+
QuerySql.current(claimsSql(asOf))
|
|
144
151
|
|
|
145
152
|
/**
|
|
146
153
|
* Row universe a pattern matches over: the full history under `all`,
|
|
@@ -173,23 +180,12 @@ const baseSql = (options: Options, policy: undefined | readonly Resolve.Entry[])
|
|
|
173
180
|
* and the closure reconstructs entity resolution as believed then
|
|
174
181
|
* (spec §12.3).
|
|
175
182
|
*/
|
|
176
|
-
const aliasPairSql = (asOf: undefined | string): string =>
|
|
177
|
-
|
|
178
|
-
WHERE c.verb = 'ALIAS' AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
|
|
179
|
-
AND ${Row.entityTermSql('c.subject')} AND ${Row.entityTermSql('c.object')}
|
|
180
|
-
UNION
|
|
181
|
-
SELECT c.object, c.subject FROM (${currentSql(asOf)}) c
|
|
182
|
-
WHERE c.verb = 'ALIAS' AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
|
|
183
|
-
AND ${Row.entityTermSql('c.subject')} AND ${Row.entityTermSql('c.object')}
|
|
184
|
-
), alias_pair(a, b) AS (
|
|
185
|
-
SELECT a, b FROM alias_edge
|
|
186
|
-
UNION
|
|
187
|
-
SELECT p.a, e.b FROM alias_pair p JOIN alias_edge e ON e.a = p.b
|
|
188
|
-
)`
|
|
183
|
+
const aliasPairSql = (asOf: undefined | string): string =>
|
|
184
|
+
QuerySql.aliasPairs(currentSql(asOf))
|
|
189
185
|
|
|
190
186
|
/** SQL for "these two expressions name the same entity" under the closure. */
|
|
191
187
|
const aliasSame = (left: string, right: string): string =>
|
|
192
|
-
|
|
188
|
+
QuerySql.aliasSame(left, right)
|
|
193
189
|
|
|
194
190
|
type Compiled = {
|
|
195
191
|
readonly sql: string
|
|
@@ -200,7 +196,20 @@ type Compiled = {
|
|
|
200
196
|
readonly valueVars: readonly string[]
|
|
201
197
|
}
|
|
202
198
|
|
|
203
|
-
const
|
|
199
|
+
const bounded = (compiled: Compiled, options: Options): Compiled => {
|
|
200
|
+
if (options.limit === undefined) return compiled
|
|
201
|
+
if (!Number.isInteger(options.limit) || options.limit < 1 ||
|
|
202
|
+
!Number.isInteger(options.offset ?? 0) || (options.offset ?? 0) < 0) {
|
|
203
|
+
throw new Error('CAVE-Q: limit must be positive and offset must be non-negative')
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
...compiled,
|
|
207
|
+
sql: `${compiled.sql}\nLIMIT ? OFFSET ?`,
|
|
208
|
+
params: [...compiled.params, options.limit, options.offset ?? 0]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, policy?: readonly Resolve.Entry[]): Compiled => {
|
|
204
213
|
// Inverse resolution (spec §12.1): swap the pattern's endpoint slots and
|
|
205
214
|
// query the primary verb.
|
|
206
215
|
let verb = pattern.verb
|
|
@@ -217,11 +226,15 @@ const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, pol
|
|
|
217
226
|
objectSlot = subjectSlot
|
|
218
227
|
subjectSlot = swapped
|
|
219
228
|
verb = { kind: 'verb', name: primary, transitive: verb.transitive }
|
|
229
|
+
} else {
|
|
230
|
+
// A lifecycle alias keeps direction but resolves to the stable
|
|
231
|
+
// storage spelling before SQL is compiled (spec §5.8).
|
|
232
|
+
verb = { kind: 'verb', name: primary, transitive: verb.transitive }
|
|
220
233
|
}
|
|
221
234
|
}
|
|
222
235
|
|
|
223
236
|
if (verb.kind === 'verb' && verb.transitive) {
|
|
224
|
-
return compileTransitive(pattern, verb.name, subjectSlot, objectSlot, options, policy)
|
|
237
|
+
return bounded(compileTransitive(pattern, verb.name, subjectSlot, objectSlot, options, policy), options)
|
|
225
238
|
}
|
|
226
239
|
|
|
227
240
|
const aliases = options.aliases === true
|
|
@@ -390,7 +403,7 @@ const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, pol
|
|
|
390
403
|
const valueVars = [...varColumns]
|
|
391
404
|
.filter(([, columns]) => columns[0] === 'value_text')
|
|
392
405
|
.map(([name]) => name)
|
|
393
|
-
return { sql, params, bind, transitive: false, valueVars }
|
|
406
|
+
return bounded({ sql, params, bind, transitive: false, valueVars }, options)
|
|
394
407
|
}
|
|
395
408
|
|
|
396
409
|
const compileTransitive = (
|
|
@@ -412,19 +425,23 @@ const compileTransitive = (
|
|
|
412
425
|
aliases ? aliasSame(left, right) : `${left} = ${right}`
|
|
413
426
|
const conditions: string[] = []
|
|
414
427
|
const params: (string | number)[] = [verb]
|
|
428
|
+
const pushTerm = (term: string): void => {
|
|
429
|
+
params.push(term)
|
|
430
|
+
if (aliases) params.push(term)
|
|
431
|
+
}
|
|
432
|
+
// A concrete source gives forward recursion its smallest seed. If only the
|
|
433
|
+
// destination is concrete, recurse backwards over the object index. Both
|
|
434
|
+
// forms retain normal (src, dst) orientation in their output.
|
|
435
|
+
const direction = subjectSlot.kind === 'term' ? 'forward' :
|
|
436
|
+
objectSlot?.kind === 'term' ? 'reverse' : 'all-pairs'
|
|
415
437
|
if (subjectSlot.kind === 'term') {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
params.push(subjectSlot.text)
|
|
420
|
-
}
|
|
438
|
+
pushTerm(subjectSlot.text)
|
|
439
|
+
} else if (objectSlot?.kind === 'term') {
|
|
440
|
+
pushTerm(objectSlot.text)
|
|
421
441
|
}
|
|
422
|
-
if (objectSlot?.kind === 'term') {
|
|
442
|
+
if (objectSlot?.kind === 'term' && direction !== 'reverse') {
|
|
423
443
|
conditions.push(same('h.dst', '?'))
|
|
424
|
-
|
|
425
|
-
if (aliases) {
|
|
426
|
-
params.push(objectSlot.text)
|
|
427
|
-
}
|
|
444
|
+
pushTerm(objectSlot.text)
|
|
428
445
|
}
|
|
429
446
|
// A repeated variable forces equality here just as in single-hop
|
|
430
447
|
// patterns: `?x EXTENDS+ ?x` asks for nodes on a cycle, not for every
|
|
@@ -433,38 +450,74 @@ const compileTransitive = (
|
|
|
433
450
|
conditions.push(same('h.src', 'h.dst'))
|
|
434
451
|
}
|
|
435
452
|
const withRecursive = `WITH RECURSIVE ${aliases ? `${aliasPairSql(options.asOf)}, ` : ''}`
|
|
436
|
-
|
|
437
|
-
|
|
453
|
+
// Reachable pairs are the complete recursive state. UNION (not UNION ALL)
|
|
454
|
+
// deduplicates each pair before it can be expanded again, so a finite graph
|
|
455
|
+
// reaches a fixed point even with cycles and no hop-depth cutoff.
|
|
456
|
+
const seedSql = direction === 'forward' ?
|
|
457
|
+
`SELECT src, dst FROM cur WHERE ${same('src', '?')}` :
|
|
458
|
+
direction === 'reverse' ?
|
|
459
|
+
`SELECT src, dst FROM cur WHERE ${same('dst', '?')}` :
|
|
460
|
+
'SELECT src, dst FROM cur'
|
|
461
|
+
const recursiveSql = direction === 'reverse' ?
|
|
462
|
+
`SELECT cur.src, h.dst FROM hops h JOIN cur ON ${same('cur.dst', 'h.src')}` :
|
|
463
|
+
`SELECT h.src, cur.dst FROM hops h JOIN cur ON ${same('cur.src', 'h.dst')}`
|
|
464
|
+
const hopsSql = `hops(src, dst) AS (
|
|
465
|
+
${seedSql}
|
|
466
|
+
UNION
|
|
467
|
+
${recursiveSql}
|
|
468
|
+
)`
|
|
469
|
+
// Support (an opt-in): propagate each edge id with every reachable pair it
|
|
470
|
+
// supports. The triple is finite and UNION-deduplicated like `hops`, while
|
|
471
|
+
// preserving forward/reverse seeding instead of rebuilding all-pairs
|
|
472
|
+
// reachability merely to recover path evidence.
|
|
473
|
+
const supportAddedSql = direction === 'reverse' ?
|
|
474
|
+
`SELECT cur.src, h.dst, cur.edge_id FROM hops h JOIN cur ON ${same('cur.dst', 'h.src')}` :
|
|
475
|
+
`SELECT h.src, cur.dst, cur.edge_id FROM hops h JOIN cur ON ${same('cur.src', 'h.dst')}`
|
|
476
|
+
const supportRecursiveSql = direction === 'reverse' ?
|
|
477
|
+
`SELECT cur.src, s.dst, s.edge_id FROM support s JOIN cur ON ${same('cur.dst', 's.src')}` :
|
|
478
|
+
`SELECT s.src, cur.dst, s.edge_id FROM support s JOIN cur ON ${same('cur.src', 's.dst')}`
|
|
479
|
+
const supportSql = `support(src, dst, edge_id) AS (
|
|
480
|
+
SELECT h.src, h.dst, cur.edge_id FROM hops h JOIN cur
|
|
481
|
+
ON ${same('cur.src', 'h.src')} AND ${same('cur.dst', 'h.dst')}
|
|
482
|
+
UNION
|
|
483
|
+
${supportAddedSql}
|
|
438
484
|
UNION
|
|
439
|
-
|
|
440
|
-
WHERE h.depth < 32
|
|
485
|
+
${supportRecursiveSql}
|
|
441
486
|
)`
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
487
|
+
const aliasedSelect = (() => {
|
|
488
|
+
if (!aliases) return 'SELECT DISTINCT h.src AS src, h.dst AS dst FROM hops h'
|
|
489
|
+
// Alias widening can produce several physical endpoint pairs with the
|
|
490
|
+
// same visible bindings. Project unbound slots to constants before
|
|
491
|
+
// DISTINCT so SQL-level pagination sees the same solutions match()
|
|
492
|
+
// exposes, rather than duplicate pairs that only collapse afterwards.
|
|
493
|
+
const repeated = subjectSlot.kind === 'var' && objectSlot?.kind === 'var' &&
|
|
494
|
+
subjectSlot.name === objectSlot.name
|
|
495
|
+
const src = subjectSlot.kind === 'var' ? repeated ? 'h.dst' : 'h.src' : "''"
|
|
496
|
+
const dst = objectSlot?.kind === 'var' ? 'h.dst' : "''"
|
|
497
|
+
return `SELECT DISTINCT ${src} AS src, ${dst} AS dst FROM hops h`
|
|
498
|
+
})()
|
|
445
499
|
const sql = options.support === true ? `
|
|
446
500
|
${withRecursive}edge AS (
|
|
447
501
|
SELECT c.* FROM (${base}) c
|
|
448
502
|
WHERE c.verb = ? AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
|
|
449
503
|
), cur AS (
|
|
450
|
-
SELECT
|
|
451
|
-
), ${hopsSql}, pair(src, dst) AS (
|
|
504
|
+
SELECT subject AS src, object AS dst, id AS edge_id FROM edge
|
|
505
|
+
), ${hopsSql}, ${supportSql}, pair(src, dst) AS (
|
|
452
506
|
SELECT DISTINCT h.src, h.dst FROM hops h
|
|
453
507
|
${conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''}
|
|
454
508
|
)
|
|
455
509
|
SELECT p.src AS src, p.dst AS dst, e.*
|
|
456
|
-
FROM pair p JOIN
|
|
457
|
-
|
|
458
|
-
AND (${same('e.object', 'p.dst')} OR EXISTS (SELECT 1 FROM hops b WHERE ${same('b.src', 'e.object')} AND ${same('b.dst', 'p.dst')}))
|
|
510
|
+
FROM pair p JOIN support s ON ${same('s.src', 'p.src')} AND ${same('s.dst', 'p.dst')}
|
|
511
|
+
JOIN edge e ON e.id = s.edge_id
|
|
459
512
|
ORDER BY p.src, p.dst, e.tx` : `
|
|
460
513
|
${withRecursive}cur AS (
|
|
461
514
|
SELECT c.subject AS src, c.object AS dst
|
|
462
515
|
FROM (${base}) c
|
|
463
516
|
WHERE c.verb = ? AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
|
|
464
517
|
), ${hopsSql}
|
|
465
|
-
|
|
518
|
+
${aliasedSelect}
|
|
466
519
|
${conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''}
|
|
467
|
-
ORDER BY
|
|
520
|
+
ORDER BY src, dst`
|
|
468
521
|
const bind = (row: Record<string, unknown>): Record<string, string> => {
|
|
469
522
|
const bindings: Record<string, string> = {}
|
|
470
523
|
if (subjectSlot.kind === 'var') {
|
|
@@ -528,7 +581,14 @@ const evaluateAt = (
|
|
|
528
581
|
* (the §24 rules engine substitutes bindings into premise patterns
|
|
529
582
|
* between joins) rather than as text.
|
|
530
583
|
*/
|
|
531
|
-
export
|
|
584
|
+
export type Window = {
|
|
585
|
+
/** Matches remaining after any valid-time evaluation. */
|
|
586
|
+
readonly matches: Match[]
|
|
587
|
+
/** Rows returned by SQLite before valid-time evaluation. */
|
|
588
|
+
readonly scanned: number
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const run = (store: Store, pattern: Pattern.t, options: Options = {}): Window => {
|
|
532
592
|
const instant = options.at === undefined ? undefined : Time.parseInstant(options.at)
|
|
533
593
|
if (options.at !== undefined && instant === undefined) {
|
|
534
594
|
throw new Error(
|
|
@@ -564,7 +624,10 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
|
|
|
564
624
|
entry.rows.push(row as unknown as Row.t)
|
|
565
625
|
}
|
|
566
626
|
}
|
|
567
|
-
return
|
|
627
|
+
return {
|
|
628
|
+
scanned: rows.length,
|
|
629
|
+
matches: [...matches.values()].map(entry => ({ bindings: entry.bindings, rows: entry.rows }))
|
|
630
|
+
}
|
|
568
631
|
}
|
|
569
632
|
if (compiled.transitive && options.aliases === true) {
|
|
570
633
|
// Distinct (src, dst) pairs can repeat a binding set when an endpoint
|
|
@@ -572,7 +635,7 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
|
|
|
572
635
|
// transitive match carries no row, so identical bindings are identical
|
|
573
636
|
// answers.
|
|
574
637
|
const seen = new Set<string>()
|
|
575
|
-
return rows.flatMap(row => {
|
|
638
|
+
return { scanned: rows.length, matches: rows.flatMap(row => {
|
|
576
639
|
const bindings = compiled.bind(row)
|
|
577
640
|
const key = JSON.stringify(bindings)
|
|
578
641
|
if (seen.has(key)) {
|
|
@@ -580,18 +643,21 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
|
|
|
580
643
|
}
|
|
581
644
|
seen.add(key)
|
|
582
645
|
return [{ bindings }]
|
|
583
|
-
})
|
|
646
|
+
}) }
|
|
584
647
|
}
|
|
585
648
|
if (compiled.transitive) {
|
|
586
|
-
return rows.map(row => ({ bindings: compiled.bind(row) }))
|
|
649
|
+
return { scanned: rows.length, matches: rows.map(row => ({ bindings: compiled.bind(row) })) }
|
|
587
650
|
}
|
|
588
651
|
if (instant === undefined) {
|
|
589
|
-
return
|
|
652
|
+
return {
|
|
653
|
+
scanned: rows.length,
|
|
654
|
+
matches: rows.map(row => ({ bindings: compiled.bind(row), row: row as unknown as Row.t }))
|
|
655
|
+
}
|
|
590
656
|
}
|
|
591
657
|
// Valid-time pass (spec §32.4): contexts are read per row — coverage
|
|
592
658
|
// needs period interpretation no SQL expression can do.
|
|
593
659
|
const contextsOf = store.db.prepare('SELECT context FROM cave_context WHERE claim_id = ?')
|
|
594
|
-
return rows.flatMap(row => {
|
|
660
|
+
return { scanned: rows.length, matches: rows.flatMap(row => {
|
|
595
661
|
const contexts = (contextsOf.all(String(row['id'])) as { context: string }[])
|
|
596
662
|
.map(entry => entry.context)
|
|
597
663
|
if (!Time.appliesAt(contexts, instant)) {
|
|
@@ -606,5 +672,12 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
|
|
|
606
672
|
bindings[name] = at.text
|
|
607
673
|
}
|
|
608
674
|
return [{ bindings, row: row as unknown as Row.t, at }]
|
|
609
|
-
})
|
|
675
|
+
}) }
|
|
610
676
|
}
|
|
677
|
+
|
|
678
|
+
/** Execute one SQL window and report how many pre-filter rows it consumed. */
|
|
679
|
+
export const window = (store: Store, pattern: Pattern.t, options: Options = {}): Window =>
|
|
680
|
+
run(store, pattern, options)
|
|
681
|
+
|
|
682
|
+
export const match = (store: Store, pattern: Pattern.t, options: Options = {}): Match[] =>
|
|
683
|
+
run(store, pattern, options).matches
|
package/src/index.ts
CHANGED
|
@@ -16,3 +16,7 @@
|
|
|
16
16
|
export * as Pattern from './pattern.ts'
|
|
17
17
|
export { match, query } from './bounded.ts'
|
|
18
18
|
export type { Match, Options } from './bounded.ts'
|
|
19
|
+
export { queryRecords } from './record.ts'
|
|
20
|
+
export * as Record from './record.ts'
|
|
21
|
+
export { defaultLimit, maxLimit, page } from './page.ts'
|
|
22
|
+
export type { Page, PageOptions } from './page.ts'
|