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
11 changes: 8 additions & 3 deletions src/17alasql.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ alasql.options = {
/** Maximum iterations for recursive CTEs to prevent infinite loops */
maxCteIterations: 1000,

/** Enable JavaScript property access via dot notation (e.g., column.length) */
angularBrackets: false,

/** Whether GETDATE() and NOW() return dates as string. If false, then a Date object is returned */
dateAsString: true,
};
Expand Down Expand Up @@ -272,9 +275,11 @@ alasql.dexec = function (databaseid, sql, params, cb, scope) {
// if(db.databaseid != databaseid) console.trace('got!');
// console.log(3,db.databaseid,databaseid);

// Include joinstar option in cache key because it affects how SELECT * compiles
// Without this, changing joinstar would use stale cached queries compiled with old option
var hh = hash(sql + '|joinstar:' + alasql.options.joinstar);
// Include joinstar and angularBrackets options in cache key because they affect how queries compile
// Without this, changing these options would use stale cached queries compiled with old options
var hh = hash(
sql + '|joinstar:' + alasql.options.joinstar + '|angularBrackets:' + alasql.options.angularBrackets
);

// Create hash
if (alasql.options.cache) {
Expand Down
37 changes: 35 additions & 2 deletions src/424select.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,29 @@ yy.Select.prototype.compileSelect1 = function (query, params) {
var tbid = col.tableid;
// console.log(query.sources);
var dbid = col.databaseid || query.sources[0].databaseid || query.database.databaseid;
if (!tbid) tbid = query.defcols[col.columnid];

// Check if tableid is actually a column name (property access pattern like name.length)
// This handles cases where the parser sees "columnname.property" and interprets it as "table.column"
var isPropertyAccess = false;
var actualTableid;
if (alasql.options.angularBrackets && tbid) {
const tbidValue = query.defcols?.[tbid];
if (tbidValue && tbidValue !== '-' && !query.defcols?.['.']?.[tbid]) {
// tbid is actually a column name (not a table name), so this is property access
isPropertyAccess = true;
actualTableid = tbidValue;
}
}

if (!tbid) tbid = query.defcols?.[col.columnid];
if (!tbid) tbid = query.defaultTableid;
if (col.columnid !== '_') {
if (false && tbid && !query.defcols['.'][col.tableid] && !query.defcols[col.columnid]) {
if (
false &&
tbid &&
!query.defcols?.['.']?.[col.tableid] &&
!query.defcols?.[col.columnid]
) {
ss.push(
"'" +
escapeq(col.as || col.columnid) +
Expand Down Expand Up @@ -260,6 +279,20 @@ yy.Select.prototype.compileSelect1 = function (query, params) {
.join(' ?? ');

ss.push("'" + escapeq(col.as || col.columnid) + "':(" + searchExpr + ')');
} else if (isPropertyAccess) {
// Property access pattern: columnname.property (e.g., name.length)
// Generate code to access the property on the column value
ss.push(
"'" +
escapeq(col.as || col.columnid) +
"':((p['" +
actualTableid +
"']['" +
col.tableid +
"'] || {})['" +
col.columnid +
"'])"
);
} else {
ss.push(
"'" +
Expand Down
14 changes: 14 additions & 0 deletions src/50expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,20 @@
}

if (this.tableid) {
// Check if tableid is actually a column name (property access pattern like name.length)
// This handles cases where the parser sees "columnname.property" and interprets it as "table.column"
if (alasql.options.angularBrackets) {
const tableidValue = defcols?.[this.tableid];
if (tableidValue && tableidValue !== '-' && !defcols?.['.']?.[this.tableid]) {
// tableid is actually a column name (not a table name), so this is property access
// Generate code to access the property on the column value
const actualTable = tableidValue;
if (this.columnid !== '_') {
return `((${context}['${actualTable}']['${this.tableid}'] || {})['${this.columnid}'])`;
}
}
}
// Otherwise, tableid is a table name (normal table.column access)
return this.columnid !== '_'
? `${context}['${this.tableid}']['${this.columnid}']`
: context === 'g'
Expand Down
54 changes: 51 additions & 3 deletions test/test341.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ if (typeof exports === 'object') {
}

describe('Test 341 Intellectual DOT operator', function () {
// Enable JavaScript property access via dot notation for these tests
before(function () {
alasql.options.angularBrackets = true;
});

after(function () {
alasql.options.angularBrackets = false;
});

it('1. CREATE DATABASE', function (done) {
alasql('CREATE DATABASE test341;USE test341');
done();
Expand Down Expand Up @@ -35,18 +44,57 @@ describe('Test 341 Intellectual DOT operator', function () {
done();
});

it.skip('4. JavaScript way', function (done) {
it('4. JavaScript way', function (done) {
// Test SET statement with JavaScript property access
// SET returns the number of rows affected (1 for variable assignment)
var res = alasql('SET @a = "who".length');
assert.deepEqual(res, [6, 6, 7]);
assert.deepEqual(res, 1);

// Verify the variable @a was set to the correct value (length of "who" = 3)
assert.deepEqual(alasql.vars.a, 3);

// Verify we can use the variable in subsequent queries
var res2 = alasql('SELECT @a AS result');
assert.deepEqual(res2, [{result: 3}]);
done();
});

it.skip('5. JavaScript way', function (done) {
it('5. JavaScript way', function (done) {
var res = alasql('SELECT COLUMN name.length FROM persons');
assert.deepEqual(res, [6, 6, 7]);
done();
});

it('6. JavaScript way with table.column.length', function (done) {
var res = alasql('SELECT COLUMN persons.name.length FROM persons');
assert.deepEqual(res, [6, 6, 7]);
done();
});

it('7. Edge case: table and column with same name', function (done) {
// Create a table named "item" with a column named "item"
alasql('CREATE TABLE item (id INT, item STRING)');
alasql('INSERT INTO item VALUES (1, "Alpha"), (2, "Beta")');

// Test 1: table.column access (item.item should get the column value)
var res1 = alasql('SELECT item.item FROM item');
assert.deepEqual(res1, [{item: 'Alpha'}, {item: 'Beta'}]);

// Test 2: table.column.property access (item.item.length should get the length)
var res2 = alasql('SELECT COLUMN item.item.length FROM item');
assert.deepEqual(res2, [5, 4]);

// Test 3: When only one column exists with unique name, property access works
alasql('CREATE TABLE products (title STRING)');
alasql('INSERT INTO products VALUES ("Product"), ("Item")');
var res3 = alasql('SELECT COLUMN title.length FROM products');
assert.deepEqual(res3, [7, 4]);

alasql('DROP TABLE item');
alasql('DROP TABLE products');
done();
});

it('5. FOREIGN KEY way', function (done) {
var res = alasql('SELECT VALUE $0; SET $0 = 200; SELECT VALUE $0', [100]);
assert.deepEqual(res, [100, 1, 200]);
Expand Down