alasql 4.15.0 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- //! AlaSQL v4.14.0 build: develop-3b8d5542 | © 2014-2025 Andrey Gershun & Mathias Wulff | License: MIT
1
+ //! AlaSQL v4.15.0 build: develop-ff717ec7 | © 2014-2025 Andrey Gershun & Mathias Wulff | License: MIT
2
2
  /*
3
3
  @module alasql
4
- @version 4.14.0
4
+ @version 4.15.0
5
5
 
6
6
  AlaSQL - JavaScript SQL database
7
7
  © 2014-2025 Andrey Gershun & Mathias Wulff
package/dist/alasql.fs.js CHANGED
@@ -1,7 +1,7 @@
1
- //! AlaSQL v4.14.0 build: develop-3b8d5542 | © 2014-2025 Andrey Gershun & Mathias Wulff | License: MIT
1
+ //! AlaSQL v4.15.0 build: develop-ff717ec7 | © 2014-2025 Andrey Gershun & Mathias Wulff | License: MIT
2
2
  /*
3
3
  @module alasql
4
- @version 4.14.0
4
+ @version 4.15.0
5
5
 
6
6
  AlaSQL - JavaScript SQL database
7
7
  © 2014-2025 Andrey Gershun & Mathias Wulff
@@ -141,8 +141,8 @@ SOFTWARE.
141
141
  Current version of alasql
142
142
  @constant {string}
143
143
  */
144
- alasql.version = '4.14.0';
145
- alasql.build = 'develop-3b8d5542';
144
+ alasql.version = '4.15.0';
145
+ alasql.build = 'develop-ff717ec7';
146
146
 
147
147
  /**
148
148
  Debug flag
@@ -8527,6 +8527,12 @@ yy.ExistsValue = class ExistsValue {
8527
8527
  alasql.precompile = function (statement, databaseid, params) {
8528
8528
  if (!statement) return;
8529
8529
  statement.params = params;
8530
+ // For CREATE VIEW statements, move queries to the select object
8531
+ // so they can be compiled later when the view is first used
8532
+ if (statement.view && statement.select && statement.queries) {
8533
+ statement.select.queries = statement.queries;
8534
+ return;
8535
+ }
8530
8536
  if (statement.queries) {
8531
8537
  statement.queriesfn = statement.queries.map(function (q) {
8532
8538
  var nq = q.compile(databaseid || statement.database.databaseid);
@@ -8615,7 +8621,12 @@ yy.Select.prototype.compileFrom = function (query) {
8615
8621
  };
8616
8622
  } else if (alasql.databases[source.databaseid].tables[source.tableid].view) {
8617
8623
  source.datafn = (query, params, cb, idx, alasql) => {
8618
- let res = alasql.databases[source.databaseid].tables[source.tableid].select(params);
8624
+ let table = alasql.databases[source.databaseid].tables[source.tableid];
8625
+ // Lazily compile the view's select on first use
8626
+ if (!table.select && table.viewSelect) {
8627
+ table.select = table.viewSelect.compile(table.viewDatabaseid);
8628
+ }
8629
+ let res = table.select(params);
8619
8630
  if (cb) res = cb(res, idx, query);
8620
8631
  return res;
8621
8632
  };
@@ -11976,17 +11987,23 @@ yy.Over = class Over {
11976
11987
  }
11977
11988
 
11978
11989
  findAggregator(query) {
11979
- const colas = escapeq(this.toString()) + ':' + query.selectGroup.length;
11980
-
11981
- if (!this.nick) {
11982
- this.nick = colas;
11990
+ // Check if an identical aggregate already exists in selectGroup
11991
+ let existingAggr = query.selectGroup.find(agg => agg.toString() === this.toString());
11983
11992
 
11984
- if (!query.removeKeys.includes(colas)) {
11985
- query.removeKeys.push(colas);
11993
+ if (existingAggr) {
11994
+ // Reuse the existing aggregate's nick to share the same accumulator
11995
+ this.aggrNick = existingAggr.nick;
11996
+ } else {
11997
+ // New aggregate - assign nick and add to selectGroup
11998
+ if (!this.nick) {
11999
+ this.nick = escapeq(this.toString()) + ':' + query.selectGroup.length;
12000
+ if (!query.removeKeys.includes(this.nick)) {
12001
+ query.removeKeys.push(this.nick);
12002
+ }
11986
12003
  }
12004
+ this.aggrNick = this.nick;
12005
+ query.selectGroup.push(this);
11987
12006
  }
11988
-
11989
- query.selectGroup.push(this);
11990
12007
  }
11991
12008
 
11992
12009
  toType() {
@@ -12006,7 +12023,8 @@ yy.Over = class Over {
12006
12023
  }
12007
12024
 
12008
12025
  toJS() {
12009
- var colas = this.nick;
12026
+ // Use aggrNick for duplicate aggregates to share the same accumulator, otherwise use the unique nick
12027
+ var colas = this.aggrNick || this.nick;
12010
12028
  if (colas === undefined) {
12011
12029
  colas = escapeq(this.toString());
12012
12030
  }
@@ -12506,8 +12524,20 @@ stdfn.CONCAT_WS = function () {
12506
12524
  // Aggregator for joining strings
12507
12525
  alasql.aggr.group_concat = alasql.aggr.GROUP_CONCAT = function (v, s, stage) {
12508
12526
  if (stage === 1) {
12527
+ // Initialize: skip null values
12528
+ if (v === null || v === undefined) {
12529
+ return null;
12530
+ }
12509
12531
  return '' + v;
12510
12532
  } else if (stage === 2) {
12533
+ // Accumulate: skip null values
12534
+ if (v === null || v === undefined) {
12535
+ return s;
12536
+ }
12537
+ // If accumulator is null/undefined, start with current value
12538
+ if (s === null || s === undefined) {
12539
+ return '' + v;
12540
+ }
12511
12541
  s += ',' + v;
12512
12542
  return s;
12513
12543
  }
@@ -13400,7 +13430,9 @@ yy.CreateTable.prototype.execute = function (databaseid, params, cb) {
13400
13430
  //Used in 420from queryfn when table.view = true!
13401
13431
  if (this.view && this.select) {
13402
13432
  table.view = true;
13403
- table.select = this.select.compile(this.table.databaseid || databaseid);
13433
+ // Store the uncompiled select statement and databaseid for lazy compilation
13434
+ table.viewSelect = this.select;
13435
+ table.viewDatabaseid = this.table.databaseid || databaseid;
13404
13436
  }
13405
13437
 
13406
13438
  if (db.engineid) {
@@ -15188,6 +15220,14 @@ yy.Insert.prototype.compile = function (databaseid) {
15188
15220
  throw "Table '" + tableid + "' could not be found";
15189
15221
  }
15190
15222
 
15223
+ // Helper function to create error message for value/column count mismatch
15224
+ var createValueCountMismatchError = function (valueCount, columnCount, columnType) {
15225
+ return (
15226
+ `The number of values (${valueCount}) does not match the number of ${columnType} (${columnCount}). ` +
15227
+ 'If using a subquery, use INSERT INTO ... SELECT instead of INSERT INTO ... VALUES (SELECT ...)'
15228
+ );
15229
+ };
15230
+
15191
15231
  // Check, if this dirty flag is required
15192
15232
  var s = '';
15193
15233
  var sw = '';
@@ -15221,6 +15261,12 @@ yy.Insert.prototype.compile = function (databaseid) {
15221
15261
 
15222
15262
  // s += '';
15223
15263
  if (self.columns) {
15264
+ // Validate that we have the right number of values for the columns
15265
+ if (values.length !== self.columns.length) {
15266
+ throw new Error(
15267
+ createValueCountMismatchError(values.length, self.columns.length, 'columns')
15268
+ );
15269
+ }
15224
15270
  self.columns.forEach(function (col, idx) {
15225
15271
 
15226
15272
  // ss.push(col.columnid +':'+ self.values[idx].value.toString());
@@ -15253,6 +15299,12 @@ yy.Insert.prototype.compile = function (databaseid) {
15253
15299
  // var table = db.tables[tableid];
15254
15300
 
15255
15301
  if (Array.isArray(values) && table.columns && table.columns.length > 0) {
15302
+ // Validate that we have the right number of values for the table columns
15303
+ if (values.length !== table.columns.length) {
15304
+ throw new Error(
15305
+ createValueCountMismatchError(values.length, table.columns.length, 'table columns')
15306
+ );
15307
+ }
15256
15308
  table.columns.forEach(function (col, idx) {
15257
15309
  var q = "'" + col.columnid + "':";
15258
15310
  // var val = values[idx].toJS();
@@ -19587,6 +19639,35 @@ IDB.updateTable = function (databaseid, tableid, assignfn, wherefn, params, cb)
19587
19639
  };
19588
19640
  };
19589
19641
  };
19642
+
19643
+ /**
19644
+ * Commit transaction for IndexedDB
19645
+ * Note: IndexedDB operations are auto-committed per operation
19646
+ * This method provides API compatibility
19647
+ */
19648
+ IDB.commit = function (databaseid, cb) {
19649
+ // IndexedDB auto-commits each operation's transaction
19650
+ // No additional action needed
19651
+ return cb ? cb(1) : 1;
19652
+ };
19653
+
19654
+ /**
19655
+ * Begin transaction - alias to commit for IndexedDB
19656
+ * Similar to LOCALSTORAGE pattern
19657
+ */
19658
+ IDB.begin = IDB.commit;
19659
+
19660
+ /**
19661
+ * Rollback transaction for IndexedDB
19662
+ * Note: IndexedDB operations are auto-committed per operation
19663
+ * Manual rollback not supported - operations cannot be undone
19664
+ */
19665
+ IDB.rollback = function (databaseid, cb) {
19666
+ // IndexedDB auto-commits each operation
19667
+ // Cannot rollback already-committed operations
19668
+ // This provides API compatibility only
19669
+ return cb ? cb(1) : 1;
19670
+ };
19590
19671
  //
19591
19672
  // 91localstorage.js
19592
19673
  // localStorage and DOM-Storage engine
package/dist/alasql.js CHANGED
@@ -1,7 +1,7 @@
1
- //! AlaSQL v4.14.0 build: develop-3b8d5542 | © 2014-2025 Andrey Gershun & Mathias Wulff | License: MIT
1
+ //! AlaSQL v4.15.0 build: develop-ff717ec7 | © 2014-2025 Andrey Gershun & Mathias Wulff | License: MIT
2
2
  /*
3
3
  @module alasql
4
- @version 4.14.0
4
+ @version 4.15.0
5
5
 
6
6
  AlaSQL - JavaScript SQL database
7
7
  © 2014-2025 Andrey Gershun & Mathias Wulff
@@ -141,8 +141,8 @@ SOFTWARE.
141
141
  Current version of alasql
142
142
  @constant {string}
143
143
  */
144
- alasql.version = '4.14.0';
145
- alasql.build = 'develop-3b8d5542';
144
+ alasql.version = '4.15.0';
145
+ alasql.build = 'develop-ff717ec7';
146
146
 
147
147
  /**
148
148
  Debug flag
@@ -8527,6 +8527,12 @@ yy.ExistsValue = class ExistsValue {
8527
8527
  alasql.precompile = function (statement, databaseid, params) {
8528
8528
  if (!statement) return;
8529
8529
  statement.params = params;
8530
+ // For CREATE VIEW statements, move queries to the select object
8531
+ // so they can be compiled later when the view is first used
8532
+ if (statement.view && statement.select && statement.queries) {
8533
+ statement.select.queries = statement.queries;
8534
+ return;
8535
+ }
8530
8536
  if (statement.queries) {
8531
8537
  statement.queriesfn = statement.queries.map(function (q) {
8532
8538
  var nq = q.compile(databaseid || statement.database.databaseid);
@@ -8615,7 +8621,12 @@ yy.Select.prototype.compileFrom = function (query) {
8615
8621
  };
8616
8622
  } else if (alasql.databases[source.databaseid].tables[source.tableid].view) {
8617
8623
  source.datafn = (query, params, cb, idx, alasql) => {
8618
- let res = alasql.databases[source.databaseid].tables[source.tableid].select(params);
8624
+ let table = alasql.databases[source.databaseid].tables[source.tableid];
8625
+ // Lazily compile the view's select on first use
8626
+ if (!table.select && table.viewSelect) {
8627
+ table.select = table.viewSelect.compile(table.viewDatabaseid);
8628
+ }
8629
+ let res = table.select(params);
8619
8630
  if (cb) res = cb(res, idx, query);
8620
8631
  return res;
8621
8632
  };
@@ -11976,17 +11987,23 @@ yy.Over = class Over {
11976
11987
  }
11977
11988
 
11978
11989
  findAggregator(query) {
11979
- const colas = escapeq(this.toString()) + ':' + query.selectGroup.length;
11980
-
11981
- if (!this.nick) {
11982
- this.nick = colas;
11990
+ // Check if an identical aggregate already exists in selectGroup
11991
+ let existingAggr = query.selectGroup.find(agg => agg.toString() === this.toString());
11983
11992
 
11984
- if (!query.removeKeys.includes(colas)) {
11985
- query.removeKeys.push(colas);
11993
+ if (existingAggr) {
11994
+ // Reuse the existing aggregate's nick to share the same accumulator
11995
+ this.aggrNick = existingAggr.nick;
11996
+ } else {
11997
+ // New aggregate - assign nick and add to selectGroup
11998
+ if (!this.nick) {
11999
+ this.nick = escapeq(this.toString()) + ':' + query.selectGroup.length;
12000
+ if (!query.removeKeys.includes(this.nick)) {
12001
+ query.removeKeys.push(this.nick);
12002
+ }
11986
12003
  }
12004
+ this.aggrNick = this.nick;
12005
+ query.selectGroup.push(this);
11987
12006
  }
11988
-
11989
- query.selectGroup.push(this);
11990
12007
  }
11991
12008
 
11992
12009
  toType() {
@@ -12006,7 +12023,8 @@ yy.Over = class Over {
12006
12023
  }
12007
12024
 
12008
12025
  toJS() {
12009
- var colas = this.nick;
12026
+ // Use aggrNick for duplicate aggregates to share the same accumulator, otherwise use the unique nick
12027
+ var colas = this.aggrNick || this.nick;
12010
12028
  if (colas === undefined) {
12011
12029
  colas = escapeq(this.toString());
12012
12030
  }
@@ -12506,8 +12524,20 @@ stdfn.CONCAT_WS = function () {
12506
12524
  // Aggregator for joining strings
12507
12525
  alasql.aggr.group_concat = alasql.aggr.GROUP_CONCAT = function (v, s, stage) {
12508
12526
  if (stage === 1) {
12527
+ // Initialize: skip null values
12528
+ if (v === null || v === undefined) {
12529
+ return null;
12530
+ }
12509
12531
  return '' + v;
12510
12532
  } else if (stage === 2) {
12533
+ // Accumulate: skip null values
12534
+ if (v === null || v === undefined) {
12535
+ return s;
12536
+ }
12537
+ // If accumulator is null/undefined, start with current value
12538
+ if (s === null || s === undefined) {
12539
+ return '' + v;
12540
+ }
12511
12541
  s += ',' + v;
12512
12542
  return s;
12513
12543
  }
@@ -13400,7 +13430,9 @@ yy.CreateTable.prototype.execute = function (databaseid, params, cb) {
13400
13430
  //Used in 420from queryfn when table.view = true!
13401
13431
  if (this.view && this.select) {
13402
13432
  table.view = true;
13403
- table.select = this.select.compile(this.table.databaseid || databaseid);
13433
+ // Store the uncompiled select statement and databaseid for lazy compilation
13434
+ table.viewSelect = this.select;
13435
+ table.viewDatabaseid = this.table.databaseid || databaseid;
13404
13436
  }
13405
13437
 
13406
13438
  if (db.engineid) {
@@ -15188,6 +15220,14 @@ yy.Insert.prototype.compile = function (databaseid) {
15188
15220
  throw "Table '" + tableid + "' could not be found";
15189
15221
  }
15190
15222
 
15223
+ // Helper function to create error message for value/column count mismatch
15224
+ var createValueCountMismatchError = function (valueCount, columnCount, columnType) {
15225
+ return (
15226
+ `The number of values (${valueCount}) does not match the number of ${columnType} (${columnCount}). ` +
15227
+ 'If using a subquery, use INSERT INTO ... SELECT instead of INSERT INTO ... VALUES (SELECT ...)'
15228
+ );
15229
+ };
15230
+
15191
15231
  // Check, if this dirty flag is required
15192
15232
  var s = '';
15193
15233
  var sw = '';
@@ -15221,6 +15261,12 @@ yy.Insert.prototype.compile = function (databaseid) {
15221
15261
 
15222
15262
  // s += '';
15223
15263
  if (self.columns) {
15264
+ // Validate that we have the right number of values for the columns
15265
+ if (values.length !== self.columns.length) {
15266
+ throw new Error(
15267
+ createValueCountMismatchError(values.length, self.columns.length, 'columns')
15268
+ );
15269
+ }
15224
15270
  self.columns.forEach(function (col, idx) {
15225
15271
 
15226
15272
  // ss.push(col.columnid +':'+ self.values[idx].value.toString());
@@ -15253,6 +15299,12 @@ yy.Insert.prototype.compile = function (databaseid) {
15253
15299
  // var table = db.tables[tableid];
15254
15300
 
15255
15301
  if (Array.isArray(values) && table.columns && table.columns.length > 0) {
15302
+ // Validate that we have the right number of values for the table columns
15303
+ if (values.length !== table.columns.length) {
15304
+ throw new Error(
15305
+ createValueCountMismatchError(values.length, table.columns.length, 'table columns')
15306
+ );
15307
+ }
15256
15308
  table.columns.forEach(function (col, idx) {
15257
15309
  var q = "'" + col.columnid + "':";
15258
15310
  // var val = values[idx].toJS();
@@ -19587,6 +19639,35 @@ IDB.updateTable = function (databaseid, tableid, assignfn, wherefn, params, cb)
19587
19639
  };
19588
19640
  };
19589
19641
  };
19642
+
19643
+ /**
19644
+ * Commit transaction for IndexedDB
19645
+ * Note: IndexedDB operations are auto-committed per operation
19646
+ * This method provides API compatibility
19647
+ */
19648
+ IDB.commit = function (databaseid, cb) {
19649
+ // IndexedDB auto-commits each operation's transaction
19650
+ // No additional action needed
19651
+ return cb ? cb(1) : 1;
19652
+ };
19653
+
19654
+ /**
19655
+ * Begin transaction - alias to commit for IndexedDB
19656
+ * Similar to LOCALSTORAGE pattern
19657
+ */
19658
+ IDB.begin = IDB.commit;
19659
+
19660
+ /**
19661
+ * Rollback transaction for IndexedDB
19662
+ * Note: IndexedDB operations are auto-committed per operation
19663
+ * Manual rollback not supported - operations cannot be undone
19664
+ */
19665
+ IDB.rollback = function (databaseid, cb) {
19666
+ // IndexedDB auto-commits each operation
19667
+ // Cannot rollback already-committed operations
19668
+ // This provides API compatibility only
19669
+ return cb ? cb(1) : 1;
19670
+ };
19590
19671
  //
19591
19672
  // 91localstorage.js
19592
19673
  // localStorage and DOM-Storage engine