Skip to content

Commit 24bcf58

Browse files
authored
Merge pull request #330 from hoijnet/issues/261-support-for-unique-vars
feat(woql): Add vars_unique() for generating unique variable names fi…
2 parents fd3d3ee + 61fed00 commit 24bcf58

File tree

3 files changed

+154
-3
lines changed

3 files changed

+154
-3
lines changed

lib/query/woqlDoc.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ 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+
};
4349
} if (typeof (obj) === 'object' && !Array.isArray(obj)) {
4450
const pairs = [];
4551
// eslint-disable-next-line no-restricted-syntax
@@ -80,6 +86,30 @@ function Var(name) {
8086
};
8187
}
8288

89+
/**
90+
* Global counter for generating unique variable names
91+
*/
92+
let uniqueVarCounter = 0;
93+
94+
/**
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
99+
*/
100+
function VarUnique(name) {
101+
uniqueVarCounter += 1;
102+
this.name = `${name}_${uniqueVarCounter}`;
103+
this.baseName = name;
104+
this.counter = uniqueVarCounter;
105+
this.json = function () {
106+
return {
107+
'@type': 'Value',
108+
variable: this.name,
109+
};
110+
};
111+
}
112+
83113
/**
84114
* @param {object} name
85115
* @returns {object}
@@ -105,4 +135,6 @@ function Vars(...args) {
105135
return varObj;
106136
}
107137

108-
module.exports = { Vars, Var, Doc };
138+
module.exports = {
139+
Vars, Var, VarUnique, Doc,
140+
};

lib/woql.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
// I HAVE TO REVIEW THE Inheritance and the prototype chain
55
const WOQLQuery = require('./query/woqlBuilder');
66
const WOQLLibrary = require('./query/woqlLibrary');
7-
const { Vars, Var, Doc } = require('./query/woqlDoc');
7+
const {
8+
Vars, Var, VarUnique, Doc,
9+
} = require('./query/woqlDoc');
810
// eslint-disable-next-line no-unused-vars
911
const typedef = require('./typedef');
1012
// eslint-disable-next-line no-unused-vars
@@ -1352,6 +1354,33 @@ WOQL.vars = function (...varNames) {
13521354
return varNames.map((item) => new Var(item));
13531355
};
13541356

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

test/woql.spec.js

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { expect } = require('chai');
22

33
const WOQL = require('../lib/woql');
4-
const { Var, Vars } = require('../lib/query/woqlDoc');
4+
const { Var, VarUnique, Vars } = require('../lib/query/woqlDoc');
55

66
const idGenJson = require('./woqlJson/woqlIdgenJson');
77
const woqlStarJson = require('./woqlJson/woqlStarJson');
@@ -404,6 +404,96 @@ describe('woql queries', () => {
404404
expect(varsArr[0]).to.be.instanceof(Var);
405405
});
406406

407+
it('check the vars_unique method creates VarUnique instances', () => {
408+
const varsArr = WOQL.vars_unique('A', 'B', 'C');
409+
410+
expect(varsArr[0]).to.be.instanceof(VarUnique);
411+
expect(varsArr[1]).to.be.instanceof(VarUnique);
412+
expect(varsArr[2]).to.be.instanceof(VarUnique);
413+
});
414+
415+
it('check vars_unique creates unique variable names within a single call', () => {
416+
const [a, b, c] = WOQL.vars_unique('A', 'B', 'C');
417+
418+
// Each variable should have a unique name
419+
expect(a.name).to.not.equal(b.name);
420+
expect(b.name).to.not.equal(c.name);
421+
expect(a.name).to.not.equal(c.name);
422+
423+
// Each should contain the base name
424+
expect(a.name).to.include('A');
425+
expect(b.name).to.include('B');
426+
expect(c.name).to.include('C');
427+
});
428+
429+
it('check vars_unique creates unique variable names across multiple calls', () => {
430+
const [a1] = WOQL.vars_unique('X');
431+
const [a2] = WOQL.vars_unique('X');
432+
const [a3] = WOQL.vars_unique('X');
433+
434+
// Variables with the same base name should still be unique
435+
expect(a1.name).to.not.equal(a2.name);
436+
expect(a2.name).to.not.equal(a3.name);
437+
expect(a1.name).to.not.equal(a3.name);
438+
439+
// All should contain the base name 'X'
440+
expect(a1.name).to.include('X');
441+
expect(a2.name).to.include('X');
442+
expect(a3.name).to.include('X');
443+
});
444+
445+
it('check vars_unique appends incrementing counter', () => {
446+
// Get the current counter value by creating a variable
447+
const [v1] = WOQL.vars_unique('test');
448+
const counter1 = v1.counter;
449+
450+
// Next variable should have counter + 1
451+
const [v2] = WOQL.vars_unique('test');
452+
expect(v2.counter).to.equal(counter1 + 1);
453+
454+
// And so on
455+
const [v3] = WOQL.vars_unique('test');
456+
expect(v3.counter).to.equal(counter1 + 2);
457+
});
458+
459+
it('check vars_unique generates correct JSON with unique variable names', () => {
460+
const [a, b] = WOQL.vars_unique('myvar', 'myvar');
461+
462+
const jsonA = a.json();
463+
const jsonB = b.json();
464+
465+
expect(jsonA).to.have.property('@type', 'Value');
466+
expect(jsonA).to.have.property('variable');
467+
expect(jsonB).to.have.property('@type', 'Value');
468+
expect(jsonB).to.have.property('variable');
469+
470+
// Variable names in JSON should be different even with same base name
471+
expect(jsonA.variable).to.not.equal(jsonB.variable);
472+
expect(jsonA.variable).to.include('myvar');
473+
expect(jsonB.variable).to.include('myvar');
474+
});
475+
476+
it('check vars still works exactly as before (no changes)', () => {
477+
const [x, y, z] = WOQL.vars('X', 'Y', 'Z');
478+
479+
// Should create Var instances, not VarUnique
480+
expect(x).to.be.instanceof(Var);
481+
expect(x).to.not.be.instanceof(VarUnique);
482+
483+
// Names should be exactly as provided
484+
expect(x.name).to.equal('X');
485+
expect(y.name).to.equal('Y');
486+
expect(z.name).to.equal('Z');
487+
488+
// Should not have counter property
489+
expect(x).to.not.have.property('counter');
490+
491+
// Multiple calls with same names should produce identical variable names
492+
const [x2] = WOQL.vars('X');
493+
expect(x2.name).to.equal('X');
494+
expect(x2.name).to.equal(x.name);
495+
});
496+
407497
it('check type_of(Var,Var)', () => {
408498
const TypeOf = WOQL.type_of('v:X', 'v:Y').json()
409499
expect(TypeOf).to.deep.eql({

0 commit comments

Comments
 (0)