Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions integration_tests/woql_arithmetic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ describe('Tests for woql arithmetic', () => {
expect(result?.bindings).toStrictEqual(expectedJson);
});

// test('Eval with variable in expression should unify, not internal server error', async () => {
// // evaluate(plus("v:a", 5), 10) should unify v:a with 5
// // Currently this gives an internal server error due to Prolog is/2
// // requiring a fully ground RHS expression.
// const query = WOQL.eval(WOQL.plus("v:a", 5), 10);

// const result = await client.query(query);
// // Should not throw an internal server error.
// // Expected: v:a unifies with 5 (since 5 + 5 = 10)
// const expectedJson = [{"a": {"@type": "xsd:decimal", "@value": 5}}];
// expect(result?.bindings).toStrictEqual(expectedJson);
// });

});

afterAll(async () => {
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/woql_triple_slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ beforeAll(async () => {

afterAll(async () => {
await cleanupDatabase(client, dbName);
});
}, 30000);

describe("triple_slice: forward range queries on integers", () => {

Expand Down
208 changes: 208 additions & 0 deletions integration_tests/woql_var_type_compat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//@ts-check
/**
* Integration tests for issue #378: Var arguments in read_document and length
*
* These tests verify that WOQL.vars() Var objects can be passed to
* read_document() and length() without TypeScript errors, and that the
* resulting queries compile and execute correctly against TerminusDB.
*
* Before the fix: TypeScript rejects Var arguments (compile-time failure).
* After the fix: Tests compile and pass at runtime.
*/
import { describe, expect, test, beforeAll, afterAll } from "@jest/globals";
import { WOQL } from "../index.js";
import { Vars } from "../lib/woql.js";
import {
createTestClient,
setupTestBranch,
teardownTestBranch,
} from "./test_utils";

const branchName = "test_woql_var_type_compat";
let client = createTestClient();

// Schema: a simple Item class with a name and a tags list
const itemSchema = [
{
"@base": "terminusdb:///data/",
"@schema": "terminusdb:///schema#",
"@type": "@context",
},
{
"@id": "Item",
"@key": { "@type": "Lexical", "@fields": ["name"] },
"@type": "Class",
name: "xsd:string",
tags: { "@type": "Set", "@class": "xsd:string" },
},
];

beforeAll(async () => {
await setupTestBranch(client, branchName);

// Add schema
await client.addDocument(itemSchema, {
graph_type: "schema",
full_replace: true,
});

// Insert test documents
await client.addDocument([
{ "@type": "Item", name: "Alpha", tags: ["fast", "reliable"] },
{ "@type": "Item", name: "Beta", tags: ["cheap"] },
{ "@type": "Item", name: "Gamma", tags: ["fast", "cheap", "new"] },
]);
}, 60000);

afterAll(async () => {
await teardownTestBranch(client, branchName);
});

describe("Issue #378: Var type compatibility with read_document", () => {
test("read_document accepts Var objects from WOQL.vars() for IRI", async () => {
// Use Var objects (not "v:" strings) for read_document arguments
const [uri, doc] = WOQL.vars("uri", "doc");

// Construct query: find an Item by type, then read it as a document
const query = WOQL.and(
WOQL.triple(uri, "rdf:type", "@schema:Item"),
WOQL.read_document(uri, doc)
);

const result = await client.query(query);

// Should return bindings for each Item document
expect(result?.bindings).toBeDefined();
expect(result!.bindings.length).toBeGreaterThanOrEqual(1);

// Each binding should have a doc that is an object with @type "Item"
const docs = result!.bindings.map(
(b: Record<string, unknown>) => b.doc
);
for (const d of docs) {
expect(d).toHaveProperty("@type", "Item");
expect(d).toHaveProperty("name");
}
});

test("read_document accepts Var objects from WOQL.Vars() for both IRI and output", async () => {
// Use WOQL.Vars (record-style) instead of destructured array
const v = Vars("itemUri", "itemDoc");

const query = WOQL.and(
WOQL.eq(v.itemUri, "Item/Alpha"),
WOQL.read_document(v.itemUri, v.itemDoc)
);

const result = await client.query(query);

expect(result?.bindings).toHaveLength(1);
const doc = result!.bindings[0].itemDoc;
expect(doc).toHaveProperty("@type", "Item");
expect(doc).toHaveProperty("name", "Alpha");
});

test("read_document compiles correct JSON-LD with Var arguments", () => {
const [uri, doc] = WOQL.vars("uri", "doc");

const query = WOQL.read_document(uri, doc);
const json = query.json();

// Verify the compiled JSON-LD structure uses NodeValue/Value with variable
expect(json).toHaveProperty("@type", "ReadDocument");
expect(json).toHaveProperty("identifier");
expect(json.identifier).toHaveProperty("variable", "uri");
expect(json).toHaveProperty("document");
expect(json.document).toHaveProperty("variable", "doc");
});
});

describe("Issue #378: Var type compatibility with length", () => {
test("length accepts Var object for result variable", async () => {
const v = Vars("name", "names", "count");

// Collect all item names, then count them
const query = WOQL.and(
WOQL.collect(
v.name,
v.names,
WOQL.triple("v:itemId", "name", v.name)
),
WOQL.length(v.names, v.count)
);

const result = await client.query(query);

expect(result?.bindings).toHaveLength(1);
expect(result!.bindings[0].count["@value"]).toBe(3);
});

test("length accepts Var object for list argument via string variable", async () => {
const [count] = WOQL.vars("count");

// Use a string "v:list" for the list and a Var for the count
const query = WOQL.and(
WOQL.collect("v:name", "v:list", WOQL.triple("v:itemId", "name", "v:name")),
WOQL.length("v:list", count)
);

const result = await client.query(query);

expect(result?.bindings).toHaveLength(1);
expect(result!.bindings[0].count["@value"]).toBe(3);
});

test("length compiles correct JSON-LD with Var arguments", () => {
const v = Vars("myList", "len");

const query = WOQL.length(v.myList, v.len);
const json = query.json();

// Verify the compiled JSON-LD structure
expect(json).toHaveProperty("@type", "Length");
expect(json).toHaveProperty("list");
expect(json).toHaveProperty("length");
// The length field should be a variable reference
expect(json.length).toHaveProperty("variable", "len");
});
});

describe("Issue #378: Mixed Var and string variable usage", () => {
test("read_document works with Var for IRI and string for output", async () => {
const [uri] = WOQL.vars("uri");

const query = WOQL.and(
WOQL.eq(uri, "Item/Beta"),
WOQL.read_document(uri, "v:betaDoc")
);

const result = await client.query(query);

expect(result?.bindings).toHaveLength(1);
expect(result!.bindings[0].betaDoc).toHaveProperty("@type", "Item");
expect(result!.bindings[0].betaDoc).toHaveProperty("name", "Beta");
});

test("read_document works with string for IRI and Var for output", async () => {
const [doc] = WOQL.vars("doc");

const query = WOQL.read_document("Item/Gamma", doc);

const result = await client.query(query);

expect(result?.bindings).toHaveLength(1);
expect(result!.bindings[0].doc).toHaveProperty("@type", "Item");
expect(result!.bindings[0].doc).toHaveProperty("name", "Gamma");
});

test("length works with literal list and Var result", async () => {
const [count] = WOQL.vars("count");

const query = WOQL.length(["a", "b", "c"], count);

const result = await client.query(query);

expect(result?.bindings).toHaveLength(1);
expect(result!.bindings[0].count["@value"]).toBe(3);
});
});
20 changes: 10 additions & 10 deletions lib/query/woqlQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ class WOQLQuery extends WOQLCore {

/**
* Read a node identified by an IRI as a JSON-LD document
* @param {string} IRI - The document id or a variable to read
* @param {string} output - Variable which will be bound to the document.
* @param {string|Var} IRI - The document id or a variable to read
* @param {string|Var} output - Variable which will be bound to the document.
* @return {WOQLQuery} WOQLQuery
*/
WOQLQuery.prototype.read_document = function (IRI, output) {
Expand All @@ -204,7 +204,7 @@ WOQLQuery.prototype.read_document = function (IRI, output) {
* Insert a document in the graph.
* @param {object} docjson - The document to insert. Must either have an '@id' or
* have a class specified key.
* @param {string} [IRI] - An optional identifier specifying the document location.
* @param {string|Var} [IRI] - An optional identifier specifying the document location.
* @return {WOQLQuery} WOQLQuery
*/

Expand All @@ -222,7 +222,7 @@ WOQLQuery.prototype.insert_document = function (docjson, IRI) {
* Update a document identified by an IRI
* @param {object} docjson - The document to update. Must either have an '@id' or
* have a class specified key.
* @param {string} [IRI] - An optional identifier specifying the document location.
* @param {string|Var} [IRI] - An optional identifier specifying the document location.
* @return {WOQLQuery} WOQLQuery
*/

Expand All @@ -238,7 +238,7 @@ WOQLQuery.prototype.update_document = function (docjson, IRI) {

/**
* Delete a document from the graph.
* @param {string} IRI - The document id or a variable
* @param {string|Var} IRI - The document id or a variable
* @return {WOQLQuery} WOQLQuery
*/

Expand Down Expand Up @@ -842,9 +842,9 @@ WOQLQuery.prototype.removed_quad = function (subject, predicate, object, graphRe

/**
* Returns true if ClassA subsumes ClassB, according to the current DB schema
* @param {string} classA - ClassA
* @param {string} classB - ClassB
* @returns {boolean} WOQLQuery
* @param {string|Var} classA - ClassA
* @param {string|Var} classB - ClassB
* @returns {WOQLQuery} WOQLQuery
*/
WOQLQuery.prototype.sub = function (classA, classB) {
if (!classA || !classB) return this.parameterError('Subsumption takes two parameters, both URIs');
Expand Down Expand Up @@ -1803,7 +1803,7 @@ WOQLQuery.prototype.idgenerator = WOQLQuery.prototype.idgen;
* Generates a random ID with a specified prefix
* Uses cryptographically secure random base64 encoding to generate unique identifiers
* @param {string} prefix - prefix for the generated ID
* @param {string} outputVar - variable that stores the generated ID
* @param {string|Var} outputVar - variable that stores the generated ID
* @returns {WOQLQuery} A WOQLQuery which contains the random ID generation pattern
* idgen_random("Person/", "v:person_id")
*/
Expand Down Expand Up @@ -2112,7 +2112,7 @@ WOQLQuery.prototype.regexp = WOQLQuery.prototype.re;

/**
* Calculates the length of the list in va and stores it in vb
* @param {string|array} inputVarList - Either a variable representing a list or a list of
* @param {string|Var|array} inputVarList - Either a variable representing a list or a list of
* variables or literals
* @param {string|Var} resultVarName - A variable in which the length of the list is stored or
* the length of the list as a non-negative integer
Expand Down
Loading
Loading