Skip to content

Commit 674b98b

Browse files
committed
Document accurate handling of dot operator
1 parent ee83de2 commit 674b98b

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
//@ts-check
2+
import { describe, expect, test, beforeAll, afterAll } from "@jest/globals";
3+
import { WOQLClient, WOQL } from "../index.js";
4+
import { DbDetails } from "../dist/typescript/lib/typedef.js";
5+
6+
let client: WOQLClient;
7+
const db01 = "db__test_woql_dot_operator";
8+
9+
// Reusable WOQL query that uses dot operator with path results
10+
let pathQueryWithDot: any;
11+
12+
// Schema for Person with knows relationship
13+
const personSchema = [
14+
{
15+
"@base": "terminusdb:///data/",
16+
"@schema": "terminusdb:///schema#",
17+
"@type": "@context"
18+
},
19+
{
20+
"@id": "Person",
21+
"@key": {
22+
"@fields": ["name"],
23+
"@type": "Lexical"
24+
},
25+
"@type": "Class",
26+
name: {
27+
"@class": "xsd:string",
28+
"@type": "Optional"
29+
},
30+
knows: {
31+
"@class": "Person",
32+
"@type": "Set"
33+
}
34+
}
35+
];
36+
37+
beforeAll(async () => {
38+
client = new WOQLClient("http://127.0.0.1:6363", {
39+
user: "admin",
40+
organization: "admin",
41+
key: process.env.TDB_ADMIN_PASS ?? "root"
42+
});
43+
client.db(db01);
44+
45+
// Create database with schema
46+
const dbObj: DbDetails = {
47+
label: db01,
48+
comment: "Test Dot operator",
49+
schema: true
50+
};
51+
await client.createDatabase(db01, dbObj);
52+
53+
// Add schema
54+
await client.addDocument(personSchema, {
55+
graph_type: "schema",
56+
full_replace: true
57+
});
58+
59+
// Insert test data: Alice -> Bob -> Charlie
60+
// Note: IDs match the name field due to lexical key
61+
const people = [
62+
{
63+
"@id": "Person/Alice",
64+
"@type": "Person",
65+
name: "Alice",
66+
knows: ["Person/Bob", "Person/Ada"] // Forward reference - will be resolved
67+
},
68+
{
69+
"@id": "Person/Bob",
70+
"@type": "Person",
71+
name: "Bob",
72+
knows: ["Person/Charlie", "Person/Ada"]
73+
},
74+
{
75+
"@id": "Person/Ada",
76+
"@type": "Person",
77+
name: "Ada",
78+
knows: ["Person/Charlie"]
79+
},
80+
{
81+
"@id": "Person/Charlie",
82+
"@type": "Person",
83+
name: "Charlie",
84+
knows: []
85+
}
86+
];
87+
88+
await client.addDocument(people, { graph_type: "instance" });
89+
90+
pathQueryWithDot = WOQL.and(
91+
WOQL.path("Person/Alice", "knows+", "v:destination", "v:path_edges"),
92+
WOQL.member("v:edge", "v:path_edges"),
93+
WOQL.dot("v:edge", "subject", "v:from"),
94+
WOQL.dot("v:edge", "predicate", "v:via"),
95+
WOQL.dot("v:edge", "object", "v:to")
96+
);
97+
});
98+
99+
afterAll(async () => {
100+
await client.deleteDatabase(db01);
101+
});
102+
103+
describe("Tests for WOQL Dot operator", () => {
104+
test("Dot operator field should be DataValue with xsd:string", () => {
105+
// This test verifies the CORRECT client behavior: field wrapped as xsd:string
106+
// Both JS and Python clients implement it this way
107+
108+
const query = WOQL.dot("v:dict", "field_name", "v:output");
109+
110+
// @ts-ignore - Testing internal JSON structure
111+
const json = query.json();
112+
113+
// The field should be wrapped as DataValue with xsd:string (correct client behavior)
114+
// @ts-ignore - Testing internal JSON structure
115+
expect(json.field).toBeDefined();
116+
// @ts-ignore - Testing internal JSON structure
117+
expect(json.field["@type"]).toBe("DataValue");
118+
// @ts-ignore - Testing internal JSON structure
119+
expect(json.field.data).toBeDefined();
120+
// @ts-ignore - Testing internal JSON structure
121+
expect(json.field.data["@type"]).toBe("xsd:string");
122+
// @ts-ignore - Testing internal JSON structure
123+
expect(json.field.data["@value"]).toBe("field_name");
124+
});
125+
126+
test("Dot operator generates valid WOQL JSON structure", () => {
127+
// Test that dot operator generates valid WOQL JSON that can be serialized
128+
// The dot operator is typically used with path query results to extract
129+
// edge metadata fields like 'source', 'target', etc.
130+
131+
// Example: Extract source and target from path edges
132+
const query = WOQL.and(
133+
WOQL.triple("v:a", "knows", "v:b"),
134+
WOQL.path("v:a", "knows+", "v:end", "v:path_edges"),
135+
WOQL.dot("v:path_edges", "source", "v:source_node"),
136+
WOQL.dot("v:path_edges", "target", "v:target_node")
137+
);
138+
139+
// @ts-ignore - Testing internal JSON structure
140+
const json = query.json();
141+
142+
// Verify the structure is valid
143+
// @ts-ignore - Testing internal JSON structure
144+
expect(json["@type"]).toBe("And");
145+
// @ts-ignore - Testing internal JSON structure
146+
expect(json.and).toBeDefined();
147+
// @ts-ignore - Testing internal JSON structure
148+
expect(Array.isArray(json.and)).toBe(true);
149+
// @ts-ignore - Testing internal JSON structure
150+
expect(json.and.length).toBe(4);
151+
152+
// Find the dot operations
153+
// @ts-ignore - Testing internal JSON structure
154+
const dotOps = json.and.filter((op: any) => op["@type"] === "Dot");
155+
expect(dotOps.length).toBe(2);
156+
157+
// Verify field is wrapped as DataValue with xsd:string in both dot operations
158+
dotOps.forEach((dotOp: any) => {
159+
expect(dotOp.field).toBeDefined();
160+
expect(dotOp.field["@type"]).toBe("DataValue");
161+
expect(dotOp.field.data).toBeDefined();
162+
expect(dotOp.field.data["@type"]).toBe("xsd:string");
163+
const fieldValue = dotOp.field.data["@value"];
164+
expect(fieldValue === "source" || fieldValue === "target").toBe(true);
165+
});
166+
});
167+
168+
test("Dot operator compiles without server error", async () => {
169+
// End-to-end test: verify dot operator compiles successfully
170+
// The server compiler now correctly handles xsd:string typed literals
171+
//
172+
// Before the fix, ANY query with dot operator would fail with:
173+
// "Not well formed WOQL JSON-LD" error during compilation
174+
//
175+
// After the fix, queries with dot operator compile successfully
176+
177+
// Use the reusable pathQueryWithDot that contains multiple dot operators
178+
// This query may return 0 results (depends on path structure), but the key
179+
// test is that it compiles without error - proving the bug fix works
180+
const result = await client.query(pathQueryWithDot);
181+
182+
if (!result.bindings) {
183+
console.error(
184+
`=== Dot Operator Compilation Test Error ===` +
185+
`\n Query compiled successfully (no server compilation error)` +
186+
`\n However, 0 bindings returned`
187+
);
188+
}
189+
expect(result.bindings).toBeDefined();
190+
191+
// The key verification: we got here without a compilation error
192+
// Before the fix, this would throw: "Not well formed WOQL JSON-LD"
193+
expect(result.bindings.length).toBeGreaterThanOrEqual(0);
194+
});
195+
196+
test("Dot operator extracts values from queried documents", async () => {
197+
const query = WOQL.and(
198+
// Find Alice's person ID
199+
WOQL.eq("v:person_id", "Person/Alice"),
200+
WOQL.triple("v:person_id", "rdf:type", "@schema:Person"),
201+
WOQL.triple("v:person_id", "name", "v:name_value"),
202+
// Read the document as JSON object
203+
// @ts-ignore - read_document exists but may not be in TypeScript definitions
204+
WOQL.read_document("v:person_id", "v:person_doc"),
205+
// Extract the name field using dot operator
206+
WOQL.opt().dot("v:person_doc", "name", "v:extracted_name")
207+
);
208+
209+
const result = await client.query(query);
210+
211+
expect(result.bindings?.length).toBeGreaterThan(0);
212+
if (!result.bindings || result.bindings.length === 0) {
213+
console.error(
214+
`=== Dot Operator Extraction Test Error ===` +
215+
`\n Query compiled successfully (no server compilation error)` +
216+
`\n However, 0 bindings returned`
217+
);
218+
} else {
219+
const binding = result.bindings[0];
220+
expect(binding?.name_value?.["@value"]).toBe("Alice");
221+
expect(binding?.extracted_name).toBe("Alice");
222+
}
223+
});
224+
225+
test("Dot operator extracts values from path edges", async () => {
226+
const fullQuery = WOQL.and(
227+
WOQL.path("Person/Alice", "knows+", "v:known_person", "v:path_edges"),
228+
WOQL.member("v:edge", "v:path_edges"),
229+
WOQL.opt().dot("v:edge", "subject", "v:extracted_subject"),
230+
WOQL.opt().dot("v:edge", "woql:subject", "v:extracted_woql_subject"),
231+
WOQL.opt().dot(
232+
"v:edge",
233+
"http://terminusdb.com/schema/woql#subject",
234+
"v:extracted_terminus_subject"
235+
)
236+
);
237+
238+
const fullResult = await client.query(fullQuery);
239+
240+
expect(fullResult.bindings?.length).toBeGreaterThan(0);
241+
if (!fullResult.bindings || fullResult.bindings.length === 0) {
242+
console.error(
243+
`=== Dot Operator Extraction Path Edge Error ===` +
244+
`\n Query compiled successfully (no server compilation error)` +
245+
`\n However, 0 bindings returned`
246+
);
247+
} else {
248+
const binding = fullResult.bindings[0];
249+
expect(binding?.extracted_subject).toBe("Person/Alice");
250+
expect(binding?.extracted_woql_subject).toBe("Person/Alice");
251+
expect(binding?.extracted_terminus_subject).toBe("Person/Alice");
252+
}
253+
});
254+
255+
test("Reusable query JSON structure verification", () => {
256+
// Verify the reusable pathQueryWithDot has correct JSON structure
257+
// with DataValue wrapped fields (xsd:string)
258+
259+
// @ts-ignore - Testing internal JSON structure
260+
const json = pathQueryWithDot.json();
261+
262+
// @ts-ignore - Testing internal JSON structure
263+
expect(json["@type"]).toBe("And");
264+
// @ts-ignore - Testing internal JSON structure
265+
expect(json.and).toBeDefined();
266+
267+
// Find all dot operations in the query
268+
// @ts-ignore - Testing internal JSON structure
269+
const dotOps = json.and.filter((op: any) => op["@type"] === "Dot");
270+
271+
expect(dotOps.length).toBe(3); // subject, predicate, object
272+
273+
// Verify ALL dot operations have DataValue wrapped xsd:string fields
274+
dotOps.forEach((dotOp: any, i: number) => {
275+
const fieldValue = dotOp.field.data["@value"];
276+
277+
expect(dotOp.field["@type"]).toBe("DataValue");
278+
expect(dotOp.field.data["@type"]).toBe("xsd:string");
279+
expect(["subject", "predicate", "object"].includes(fieldValue)).toBe(
280+
true
281+
);
282+
});
283+
});
284+
285+
test("Field value structure - DataValue with xsd:string", () => {
286+
// This test verifies the client produces the CORRECT structure:
287+
// DataValue wrapped with xsd:string type (same as Python client)
288+
289+
const actualQuery = WOQL.dot("v:dict", "field_name", "v:output");
290+
// @ts-ignore - Testing internal JSON structure
291+
const actualJson = actualQuery.json();
292+
// @ts-ignore - Testing internal JSON structure
293+
const actualField = actualJson.field;
294+
295+
// Verify the field is wrapped as DataValue with xsd:string
296+
// This matches the Python client implementation
297+
expect(actualField).toBeDefined();
298+
expect(actualField["@type"]).toBe("DataValue");
299+
expect(actualField.data).toBeDefined();
300+
expect(actualField.data["@type"]).toBe("xsd:string");
301+
expect(actualField.data["@value"]).toBe("field_name");
302+
});
303+
});

0 commit comments

Comments
 (0)