Skip to content

Commit e26b9a5

Browse files
committed
Ensure variables are constructed correctly
1 parent 76bc0dc commit e26b9a5

File tree

4 files changed

+124
-49
lines changed

4 files changed

+124
-49
lines changed

integration_tests/woql_arithmetic.test.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
import { describe, expect, test, beforeAll } from '@jest/globals';
33
import { WOQLClient, WOQL } from '../index.js';
44
import { DbDetails } from '../dist/typescript/lib/typedef.js';
5-
import { Vars } from '../lib/woql.js';
5+
import { vars, vars_unique, Vars, VarsUnique } from '../lib/woql.js';
66

77
let client: WOQLClient //= new WOQLClient('http://127.0.0.1:6363');
88
const db01 = 'db__test_woql_arithmetic';
99

1010
beforeAll(() => {
11+
WOQL.vars_unique_reset_start(20);
1112
client = new WOQLClient("http://127.0.0.1:6363", { user: 'admin', organization: 'admin', key: process.env.TDB_ADMIN_PASS ?? 'root' })
1213
client.db(db01);
1314
});
@@ -22,11 +23,43 @@ describe('Tests for woql arithmetic', () => {
2223
expect(result["api:status"]).toEqual("api:success");
2324
});
2425

26+
test('Test simple arithmetic with Vars variables handling', async () => {
27+
let v = Vars("result1", "result2");
28+
const query = WOQL.limit(100).eval(WOQL.times(2, 3), v.result1);
29+
30+
const expectedJson = [{"result1": {"@type": "xsd:decimal", "@value": 6}}];
31+
32+
const result = await client.query(query);
33+
expect(result?.bindings).toStrictEqual(expectedJson);
34+
});
35+
36+
test('Test simple arithmetic with VarsUnique variables handling', async () => {
37+
let v = VarsUnique("result1", "result2");
38+
const query = WOQL.limit(100).eval(WOQL.times(2, 3), v.result1);
39+
40+
// suffix is 21 because we reset it to 20 above
41+
const expectedJson = [{"result1_21": {"@type": "xsd:decimal", "@value": 6}}];
42+
43+
const result = await client.query(query);
44+
expect(result?.bindings).toStrictEqual(expectedJson);
45+
});
46+
2547
test('Test simple arithmetic with vars variables handling', async () => {
26-
let v = Vars("result");
27-
const query = WOQL.limit(100).eval(WOQL.times(2, 3), v.result);
48+
let [result1] = vars("result1");
49+
const query = WOQL.limit(100).eval(WOQL.times(2, 3), result1);
2850

29-
const expectedJson = [{"result": {"@type": "xsd:decimal", "@value": 6}}];
51+
const expectedJson = [{"result1": {"@type": "xsd:decimal", "@value": 6}}];
52+
53+
const result = await client.query(query);
54+
expect(result?.bindings).toStrictEqual(expectedJson);
55+
});
56+
57+
test('Test simple arithmetic with vars_unique variables handling', async () => {
58+
let [result3] = vars_unique("result3");
59+
const query = WOQL.limit(100).eval(WOQL.times(2, 3), result3);
60+
61+
// suffix is 23 because we already used vars_unique from WOQL 2 times since we reset it to 20 above
62+
const expectedJson = [{"result3_23": {"@type": "xsd:decimal", "@value": 6}}];
3063

3164
const result = await client.query(query);
3265
expect(result?.bindings).toStrictEqual(expectedJson);

lib/query/woqlDoc.js

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,6 @@ function convert(obj) {
4040
'@type': 'Value',
4141
variable: obj.name,
4242
};
43-
// eslint-disable-next-line no-use-before-define
44-
} if (obj instanceof VarUnique) {
45-
return {
46-
'@type': 'Value',
47-
variable: obj.name,
48-
};
4943
} if (typeof (obj) === 'object' && !Array.isArray(obj)) {
5044
const pairs = [];
5145
// eslint-disable-next-line no-restricted-syntax
@@ -73,7 +67,7 @@ function convert(obj) {
7367
}
7468

7569
/**
76-
* @param {string} name
70+
* @param {string} name The variable name
7771
* @returns
7872
*/
7973
function Var(name) {
@@ -86,29 +80,33 @@ function Var(name) {
8680
};
8781
}
8882

89-
/**
90-
* Global counter for generating unique variable names
91-
*/
9283
let uniqueVarCounter = 0;
93-
9484
/**
95-
* Creates a unique variable by appending an incrementing counter to the name.
96-
* This ensures variables are unique across all instantiations, even with the same input name.
97-
* @param {string} name - Base name for the variable
98-
* @returns {VarUnique} - A unique variable object
85+
* @param {string} name The variable name
86+
* @returns
9987
*/
10088
function VarUnique(name) {
10189
uniqueVarCounter += 1;
102-
this.name = `${name}_${uniqueVarCounter}`;
103-
this.baseName = name;
104-
this.counter = uniqueVarCounter;
90+
const localName = `${name}_${uniqueVarCounter}`;
91+
this.name = localName;
10592
this.json = function () {
10693
return {
10794
'@type': 'Value',
10895
variable: this.name,
10996
};
11097
};
11198
}
99+
// Inherit Var prototype chain for VarUnique to pass instanceof check for Var (same)
100+
VarUnique.prototype = Object.create(Var.prototype);
101+
VarUnique.prototype.constructor = VarUnique;
102+
103+
/**
104+
* Reset the unique variable counter to a specific value
105+
* @param {number} start - starting value
106+
*/
107+
function SetVarsUniqueCounter(start) {
108+
uniqueVarCounter = start;
109+
}
112110

113111
/**
114112
* @param {object} name
@@ -126,7 +124,7 @@ function Doc(obj) {
126124
*/
127125
function Vars(...args) {
128126
const varObj = {};
129-
for (let i = 0, j = arguments.length; i < j; i += 1) {
127+
for (let i = 0, j = args.length; i < j; i += 1) {
130128
const argumentName = args[i];
131129

132130
// this[argumentName] = new Var(argumentName);
@@ -135,6 +133,21 @@ function Vars(...args) {
135133
return varObj;
136134
}
137135

136+
/**
137+
* @param {...string} varNames
138+
* @returns {object<Var>}
139+
*/
140+
function VarsUnique(...args) {
141+
const varObj = {};
142+
for (let i = 0, j = args.length; i < j; i += 1) {
143+
const argumentName = args[i];
144+
145+
uniqueVarCounter += 1;
146+
varObj[argumentName] = new Var(argumentName + (uniqueVarCounter ? `_${uniqueVarCounter}` : ''));
147+
}
148+
return varObj;
149+
}
150+
138151
module.exports = {
139-
Vars, Var, VarUnique, Doc,
152+
Vars, VarsUnique, Var, VarUnique, Doc, SetVarsUniqueCounter,
140153
};

lib/woql.js

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
const WOQLQuery = require('./query/woqlBuilder');
66
const WOQLLibrary = require('./query/woqlLibrary');
77
const {
8-
Vars, Var, VarUnique, Doc,
8+
Vars, Var, Doc,
9+
VarsUnique,
10+
VarUnique,
11+
SetVarsUniqueCounter,
912
} = require('./query/woqlDoc');
1013
// eslint-disable-next-line no-unused-vars
1114
const typedef = require('./typedef');
@@ -1359,32 +1362,29 @@ WOQL.vars = function (...varNames) {
13591362
};
13601363

13611364
/**
1362-
* Generates unique javascript variables for use as WOQL variables within a query.
1363-
* Each call to vars_unique() creates variables with unique names by appending an
1364-
* incrementing counter, ensuring that variables are fully unique across all scopes.
1365-
* This is particularly useful with select() to firewall local variables from other
1366-
* scopes, even when requesting the same variable name multiple times.
1367-
*
1368-
* @param {...string} varNames - Base names for the variables
1369-
* @returns {array<VarUnique>} an array of unique javascript variables which can be
1370-
* dereferenced using the array destructuring operation
1371-
* @example
1372-
* const [a, b, c] = WOQL.vars_unique("a", "b", "c")
1373-
* // Creates variables like "a_1", "b_2", "c_3"
1374-
* const [a2, b2] = WOQL.vars_unique("a", "b")
1375-
* // Creates variables like "a_4", "b_5" - guaranteed unique even with same input names
1376-
*
1365+
* Generates unique javascript variables for use as WOQL variables within a query
1366+
* @param {...string} varNames
1367+
* @returns {array<Var>} an array of javascript variables which can be dereferenced using the
1368+
* array destructuring operation
13771369
* @example
1378-
* // Using with select() to create isolated scopes
1379-
* const [localVar] = WOQL.vars_unique("x")
1380-
* WOQL.select(localVar, WOQL.triple(localVar, "rdf:type", "Person"))
1381-
* // localVar is "x_1" and won't conflict with any other "x" variables
1370+
* const [a, b, c] = WOQL.vars("a", "b", "c")
1371+
* //a, b, c are javascript variables that are unique WOQL variables
13821372
*/
13831373

13841374
WOQL.vars_unique = function (...varNames) {
13851375
return varNames.map((item) => new VarUnique(item));
13861376
};
13871377

1378+
/**
1379+
* Sets the unique variable counter to a specific value
1380+
* This is particularly useful together with select() for locally scoped variables
1381+
* @param {number} start - starting value
1382+
*/
1383+
1384+
WOQL.vars_unique_reset_start = function (start) {
1385+
SetVarsUniqueCounter(start ?? 0);
1386+
};
1387+
13881388
/**
13891389
* Produces an encoded form of a document that can be used by a WOQL operation
13901390
* such as `WOQL.insert_document`.
@@ -1422,6 +1422,22 @@ WOQL.Vars = function (...varNames) {
14221422
return new Vars(...varNames);
14231423
};
14241424

1425+
/**
1426+
*
1427+
* Produces variables with unique names by appending an incrementing counter to each variable name.
1428+
* This is particularly useful together with select() for locally scoped variables
1429+
*
1430+
* @param {...string} varNames
1431+
* @returns {object<Var>}
1432+
* @example
1433+
* // Creates variables like "a_4", "b_5" - unique even with same input names
1434+
* const v = WOQL.VarsUnique('var01', 'var02', 'var03');
1435+
* triple(v.var01, v.var02, v.var03) // locally scoped
1436+
*/
1437+
WOQL.VarsUnique = function (...varNames) {
1438+
return new VarsUnique(...varNames);
1439+
};
1440+
14251441
/**
14261442
*
14271443
* query module

test/woql.spec.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,20 +443,25 @@ describe('woql queries', () => {
443443
});
444444

445445
it('check vars_unique appends incrementing counter', () => {
446+
WOQL.vars_unique_reset_start(0);
446447
// Get the current counter value by creating a variable
447448
const [v1] = WOQL.vars_unique('test');
448-
const counter1 = v1.counter;
449+
const counter1 = Number(v1.name.split('_')[1]);
449450

450451
// Next variable should have counter + 1
451452
const [v2] = WOQL.vars_unique('test');
452-
expect(v2.counter).to.equal(counter1 + 1);
453+
const counter2 = Number(v2.name.split('_')[1]);
454+
expect(counter2).to.equal(counter1 + 1);
455+
expect(counter2).to.equal(2);
453456

454457
// And so on
455458
const [v3] = WOQL.vars_unique('test');
456-
expect(v3.counter).to.equal(counter1 + 2);
459+
const counter3 = Number(v3.name.split('_')[1]);
460+
expect(counter3).to.equal(counter1 + 2);
457461
});
458462

459463
it('check vars_unique generates correct JSON with unique variable names', () => {
464+
WOQL.vars_unique_reset_start(0);
460465
const [a, b] = WOQL.vars_unique('myvar', 'myvar');
461466

462467
const jsonA = a.json();
@@ -469,10 +474,18 @@ describe('woql queries', () => {
469474

470475
// Variable names in JSON should be different even with same base name
471476
expect(jsonA.variable).to.not.equal(jsonB.variable);
472-
expect(jsonA.variable).to.include('myvar');
473-
expect(jsonB.variable).to.include('myvar');
477+
expect(jsonA.variable).to.equal('myvar_1');
478+
expect(jsonB.variable).to.equal('myvar_2');
474479
});
475480

481+
it('checks that vars_unique and vars work the same way', () => {
482+
const [a, b] = WOQL.vars_unique('myvar', 'myvar');
483+
const [x, y] = WOQL.vars('myvar', 'myvar');
484+
485+
expect(a.name.split('_')[0]).to.equal(x.name);
486+
expect(b.name.split('_')[0]).to.equal(y.name);
487+
})
488+
476489
it('check vars still works exactly as before (no changes)', () => {
477490
const [x, y, z] = WOQL.vars('X', 'Y', 'Z');
478491

0 commit comments

Comments
 (0)