Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 61fed00

Browse files
committed
feat(woql): Add vars_unique() for generating unique variable names fixes #261
Implements a new WOQL.vars_unique() function that creates variables with guaranteed unique names using an incrementing counter. This solves the problem where vars() generates generic variables that can collide across scopes. ## Implementation: **VarUnique Constructor (woqlDoc.js):** - Global counter for unique variable generation - Appends incrementing counter to base name (e.g., 'x' -> 'x_1', 'x_2') - Stores baseName, name, and counter properties - Generates proper JSON with unique variable names **WOQL.vars_unique() Function (woql.js):** - Maps input names to VarUnique instances - Works identically to vars() but with uniqueness guarantee - Useful with select() to firewall local variables from other scopes **Convert Function Update:** - Handles VarUnique instances like Var instances - Ensures proper JSON serialization ## Benefits: ✅ **Guaranteed Uniqueness:** Variables are unique across all scopes, even with same input names ✅ **Scope Isolation:** Works with select() to prevent variable collision ✅ **Backward Compatible:** Original vars() unchanged and works exactly as before ✅ **Well Documented:** Comprehensive JSDoc with usage examples ## Testing: **6 New Comprehensive Tests:** 1. ✅ Creates VarUnique instances 2. ✅ Unique names within single call 3. ✅ Unique names across multiple calls 4. ✅ Incrementing counter validation 5. ✅ Correct JSON generation 6. ✅ Original vars() unchanged **All 156 tests passing** ## Usage Examples: ```javascript // Basic usage const [a, b, c] = WOQL.vars_unique('a', 'b', 'c') // Creates: a_1, b_2, c_3 // Guaranteed uniqueness even with same names const [x1] = WOQL.vars_unique('x') // x_4 const [x2] = WOQL.vars_unique('x') // x_5 // Scope isolation with select() const [localVar] = WOQL.vars_unique('x') WOQL.select(localVar, WOQL.triple(localVar, 'rdf:type', 'Person')) // localVar is unique and won't conflict with other 'x' variables ```
1 parent ed94e33 commit 61fed00

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');
@@ -376,6 +376,96 @@ describe('woql queries', () => {
376376
expect(varsArr[0]).to.be.instanceof(Var);
377377
});
378378

379+
it('check the vars_unique method creates VarUnique instances', () => {
380+
const varsArr = WOQL.vars_unique('A', 'B', 'C');
381+
382+
expect(varsArr[0]).to.be.instanceof(VarUnique);
383+
expect(varsArr[1]).to.be.instanceof(VarUnique);
384+
expect(varsArr[2]).to.be.instanceof(VarUnique);
385+
});
386+
387+
it('check vars_unique creates unique variable names within a single call', () => {
388+
const [a, b, c] = WOQL.vars_unique('A', 'B', 'C');
389+
390+
// Each variable should have a unique name
391+
expect(a.name).to.not.equal(b.name);
392+
expect(b.name).to.not.equal(c.name);
393+
expect(a.name).to.not.equal(c.name);
394+
395+
// Each should contain the base name
396+
expect(a.name).to.include('A');
397+
expect(b.name).to.include('B');
398+
expect(c.name).to.include('C');
399+
});
400+
401+
it('check vars_unique creates unique variable names across multiple calls', () => {
402+
const [a1] = WOQL.vars_unique('X');
403+
const [a2] = WOQL.vars_unique('X');
404+
const [a3] = WOQL.vars_unique('X');
405+
406+
// Variables with the same base name should still be unique
407+
expect(a1.name).to.not.equal(a2.name);
408+
expect(a2.name).to.not.equal(a3.name);
409+
expect(a1.name).to.not.equal(a3.name);
410+
411+
// All should contain the base name 'X'
412+
expect(a1.name).to.include('X');
413+
expect(a2.name).to.include('X');
414+
expect(a3.name).to.include('X');
415+
});
416+
417+
it('check vars_unique appends incrementing counter', () => {
418+
// Get the current counter value by creating a variable
419+
const [v1] = WOQL.vars_unique('test');
420+
const counter1 = v1.counter;
421+
422+
// Next variable should have counter + 1
423+
const [v2] = WOQL.vars_unique('test');
424+
expect(v2.counter).to.equal(counter1 + 1);
425+
426+
// And so on
427+
const [v3] = WOQL.vars_unique('test');
428+
expect(v3.counter).to.equal(counter1 + 2);
429+
});
430+
431+
it('check vars_unique generates correct JSON with unique variable names', () => {
432+
const [a, b] = WOQL.vars_unique('myvar', 'myvar');
433+
434+
const jsonA = a.json();
435+
const jsonB = b.json();
436+
437+
expect(jsonA).to.have.property('@type', 'Value');
438+
expect(jsonA).to.have.property('variable');
439+
expect(jsonB).to.have.property('@type', 'Value');
440+
expect(jsonB).to.have.property('variable');
441+
442+
// Variable names in JSON should be different even with same base name
443+
expect(jsonA.variable).to.not.equal(jsonB.variable);
444+
expect(jsonA.variable).to.include('myvar');
445+
expect(jsonB.variable).to.include('myvar');
446+
});
447+
448+
it('check vars still works exactly as before (no changes)', () => {
449+
const [x, y, z] = WOQL.vars('X', 'Y', 'Z');
450+
451+
// Should create Var instances, not VarUnique
452+
expect(x).to.be.instanceof(Var);
453+
expect(x).to.not.be.instanceof(VarUnique);
454+
455+
// Names should be exactly as provided
456+
expect(x.name).to.equal('X');
457+
expect(y.name).to.equal('Y');
458+
expect(z.name).to.equal('Z');
459+
460+
// Should not have counter property
461+
expect(x).to.not.have.property('counter');
462+
463+
// Multiple calls with same names should produce identical variable names
464+
const [x2] = WOQL.vars('X');
465+
expect(x2.name).to.equal('X');
466+
expect(x2.name).to.equal(x.name);
467+
});
468+
379469
it('check type_of(Var,Var)', () => {
380470
const TypeOf = WOQL.type_of('v:X', 'v:Y').json()
381471
expect(TypeOf).to.deep.eql({

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /