js.documents 7.8.20 → 7.9.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.
package/README.md CHANGED
@@ -606,7 +606,7 @@ import { readFirebirdBackup } from "documents.js";
606
606
  const { summary, tables } = readFirebirdBackup(firebirdBackupBytes);
607
607
  ```
608
608
 
609
- **SQL `SELECT` engine** — `parseSelect`/`evaluateSelect` run a bounded `SELECT` over `readOdbTables`' output. Closed allowlist grammar: column list or `*` or aggregates (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`), `FROM` one table plus zero or more `[INNER] JOIN table ON predicate` clauses (a plain nested-loop inner join; a column past the first table must be qualified by its own table name, since there is no alias grammar), optional `WHERE`/`GROUP BY`/`ORDER BY`. OUTER/LEFT/RIGHT/FULL/CROSS/NATURAL joins, subqueries, and everything else outside the grammar throws `HsqldbSqlUnsupportedError`:
609
+ **SQL `SELECT` engine** — `parseSelect`/`evaluateSelect` run a bounded `SELECT` over `readOdbTables`' output. Closed allowlist grammar: column list or `*` or aggregates (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`), `FROM` one table plus zero or more joins (`[INNER]`, `LEFT [OUTER]`, `RIGHT [OUTER]`, `FULL [OUTER]`, `CROSS`, or `NATURAL`, each a plain nested-loop join with `ON`, `USING (col, ...)`, or NATURAL's own implicit equi-join over shared columns; a `USING`/`NATURAL` join's shared columns merge into one output column, `COALESCE(left, right)`), optional table aliases (`FROM t [AS] alias`, which is what makes a genuine self-join possible), optional `WHERE`/`GROUP BY`/`ORDER BY`. Subqueries, column aliases, and everything else outside the grammar throws `HsqldbSqlUnsupportedError`:
610
610
 
611
611
  ```ts
612
612
  import { decodePackage, readOdbInventory } from "odf.js";
@@ -892,7 +892,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
892
892
  - **The rpt formula engine's group scoping cascades enclosing breaks inward.** A group at level L starts a new instance when its own expression breaks OR when any enclosing group breaks — otherwise a "Q2" subtotal would span two regions. `HASCHANGED` itself knows nothing about groups; the cascade lives in the report structure. Aggregates are computed over complete ranges (not running totals); group expressions may not transitively depend on aggregates (circular).
893
893
  - **The rpt function set is a closed allowlist; separator is semicolon.** `rpt:HASCHANGED`/`rpt:LEFT`/`rpt:SUM`/`COUNT`/`AVG`/`MIN`/`MAX`/`field:[COLUMN]` — everything else throws. `[NAME]` and `"NAME"` are one concept. Three refusals where guessing would produce wrong values: non-boolean group expressions, `rpt:LEFT` over non-text, per-row formulas in report header/footer.
894
894
  - **The rpt engine emits no page headers/footers** — the renderer places them under a single-logical-page model, at report scope.
895
- - **The SQL engine is a closed allowlist** — JOINs, subqueries, `UNION`, `DISTINCT`, `HAVING`, `LIMIT`, aliases, `CASE`, arithmetic, etc. all throw `HsqldbSqlUnsupportedError` naming the construct. Silently dropping a clause would return plausible wrong rows.
895
+ - **The SQL engine is a closed allowlist** — subqueries, `UNION`, `DISTINCT`, `HAVING`, `LIMIT`, column aliases, `CASE`, arithmetic, etc. all throw `HsqldbSqlUnsupportedError` naming the construct. Silently dropping a clause would return plausible wrong rows. JOINs (every kind, `USING`, `NATURAL`) and table aliases are real grammar.
896
896
  - **Four SQL semantics decisions:** (1) NULL is `{ kind: 'empty' }`, three-valued logic; (2) values compare within classes (numeric/boolean/text), cross-class throws; (3) `GROUP BY` puts NULLs in one group, first-appearance order; `COUNT(*)` counts rows, `COUNT(column)` counts non-NULL; (4) `ORDER BY` sorts NULLs last under ASC, stable.
897
897
  - **Unquoted SQL identifiers fold to upper case; double-quoted match exactly.**
898
898
  - **All four `.odb` decoder tiers are implemented.** Tier 4 (BINARY/COMPRESSED) is a sibling of Tier 2, not a new value decoder — it recovers DDL as TEXT-format script text and decodes rows through the same per-column encoder. An external-only connection is a permanent scope boundary.
@@ -1,5 +1,5 @@
1
1
  import { r as HsqldbTable } from "./script-DsGGIzGU.cjs";
2
- import { f as SqlSelectStatement } from "./parser-D9fiOUfR.cjs";
2
+ import { m as SqlSelectStatement } from "./parser-h1n0BflA.cjs";
3
3
  import { ContentCellValue } from "document-schema.js";
4
4
  //#region src/odb/sql/evaluate.d.ts
5
5
  interface SqlResultSet {
@@ -1,5 +1,5 @@
1
1
  import { r as HsqldbTable } from "./script-DsGGIzGU.js";
2
- import { f as SqlSelectStatement } from "./parser-fyVOtBAo.js";
2
+ import { m as SqlSelectStatement } from "./parser-D-9P_0Qd.js";
3
3
  import { ContentCellValue } from "document-schema.js";
4
4
  //#region src/odb/sql/evaluate.d.ts
5
5
  interface SqlResultSet {
package/dist/index.d.cts CHANGED
@@ -114,8 +114,8 @@ import { odbTablesToSpreadsheetDocument } from "./odb/spreadsheet.cjs";
114
114
  import { readOdbForms, readOdbReports } from "./odb/components.cjs";
115
115
  import { OdbTableNotFoundError, OdbTableNotSpecifiedError } from "./odb/csv.cjs";
116
116
  import { i as tokenizeSql, n as SqlPunctuation, r as SqlToken, t as SqlComparisonOperator } from "./lexer-DgsW7jMA.cjs";
117
- import { a as SqlJoinClause, c as SqlOperand, d as SqlSelectItem, f as SqlSelectStatement, i as SqlFromClause, l as SqlOrderByTerm, m as parseSelect, n as SqlAggregateFunction, o as SqlLiteral, p as SqlSortDirection, r as SqlColumnRef, s as SqlNameRef, t as SqlAggregateArgument, u as SqlPredicate } from "./parser-D9fiOUfR.cjs";
118
- import { n as evaluateSelect, t as SqlResultSet } from "./evaluate-DjUkdPfO.cjs";
117
+ import { a as SqlJoinClause, c as SqlLiteral, d as SqlOrderByTerm, f as SqlPredicate, g as parseSelect, h as SqlSortDirection, i as SqlFromClause, l as SqlNameRef, m as SqlSelectStatement, n as SqlAggregateFunction, p as SqlSelectItem, r as SqlColumnRef, t as SqlAggregateArgument, u as SqlOperand } from "./parser-h1n0BflA.cjs";
118
+ import { n as evaluateSelect, t as SqlResultSet } from "./evaluate-3T-0yAut.cjs";
119
119
  import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError } from "./odb/sql/errors.cjs";
120
120
  import { RptBandDefinition, RptBandInstance, RptBandKind, RptGroupDefinition, RptNamedFunctionDefinition, RptReportDefinition, RptReportRun, RptScope, evaluateRptBandOutsideData, runRptReport } from "./odb/formula/evaluate.cjs";
121
121
  import { RptAggregateFunction, RptFormula, RptReference, parseRptFormula } from "./odb/formula/parser.cjs";
package/dist/index.d.ts CHANGED
@@ -114,8 +114,8 @@ import { odbTablesToSpreadsheetDocument } from "./odb/spreadsheet.js";
114
114
  import { readOdbForms, readOdbReports } from "./odb/components.js";
115
115
  import { OdbTableNotFoundError, OdbTableNotSpecifiedError } from "./odb/csv.js";
116
116
  import { i as tokenizeSql, n as SqlPunctuation, r as SqlToken, t as SqlComparisonOperator } from "./lexer-DgsW7jMA.js";
117
- import { a as SqlJoinClause, c as SqlOperand, d as SqlSelectItem, f as SqlSelectStatement, i as SqlFromClause, l as SqlOrderByTerm, m as parseSelect, n as SqlAggregateFunction, o as SqlLiteral, p as SqlSortDirection, r as SqlColumnRef, s as SqlNameRef, t as SqlAggregateArgument, u as SqlPredicate } from "./parser-fyVOtBAo.js";
118
- import { n as evaluateSelect, t as SqlResultSet } from "./evaluate-BKLK67uP.js";
117
+ import { a as SqlJoinClause, c as SqlLiteral, d as SqlOrderByTerm, f as SqlPredicate, g as parseSelect, h as SqlSortDirection, i as SqlFromClause, l as SqlNameRef, m as SqlSelectStatement, n as SqlAggregateFunction, p as SqlSelectItem, r as SqlColumnRef, t as SqlAggregateArgument, u as SqlOperand } from "./parser-D-9P_0Qd.js";
118
+ import { n as evaluateSelect, t as SqlResultSet } from "./evaluate-Ddsxehrf.js";
119
119
  import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError } from "./odb/sql/errors.js";
120
120
  import { RptBandDefinition, RptBandInstance, RptBandKind, RptGroupDefinition, RptNamedFunctionDefinition, RptReportDefinition, RptReportRun, RptScope, evaluateRptBandOutsideData, runRptReport } from "./odb/formula/evaluate.js";
121
121
  import { RptAggregateFunction, RptFormula, RptReference, parseRptFormula } from "./odb/formula/parser.js";
@@ -1,4 +1,4 @@
1
- import { t as SqlResultSet } from "../../evaluate-DjUkdPfO.cjs";
1
+ import { t as SqlResultSet } from "../../evaluate-3T-0yAut.cjs";
2
2
  import { ContentCellValue } from "document-schema.js";
3
3
  //#region src/odb/formula/evaluate.d.ts
4
4
  type RptBandKind = "report-header" | "group-header" | "detail" | "group-footer" | "report-footer";
@@ -1,4 +1,4 @@
1
- import { t as SqlResultSet } from "../../evaluate-BKLK67uP.js";
1
+ import { t as SqlResultSet } from "../../evaluate-Ddsxehrf.js";
2
2
  import { ContentCellValue } from "document-schema.js";
3
3
  //#region src/odb/formula/evaluate.d.ts
4
4
  type RptBandKind = "report-header" | "group-header" | "detail" | "group-footer" | "report-footer";
@@ -1,4 +1,4 @@
1
- import { t as SqlResultSet } from "../../evaluate-DjUkdPfO.cjs";
1
+ import { t as SqlResultSet } from "../../evaluate-3T-0yAut.cjs";
2
2
  import { ContentDocument } from "document-schema.js";
3
3
  import { OdbReport } from "odf.js";
4
4
  //#region src/odb/report/render.d.ts
@@ -1,4 +1,4 @@
1
- import { t as SqlResultSet } from "../../evaluate-BKLK67uP.js";
1
+ import { t as SqlResultSet } from "../../evaluate-Ddsxehrf.js";
2
2
  import { ContentDocument } from "document-schema.js";
3
3
  import { OdbReport } from "odf.js";
4
4
  //#region src/odb/report/render.d.ts
@@ -1,5 +1,5 @@
1
1
  import { n as HsqldbDecodeOptions } from "../../rowformat-Cl2exlEh.cjs";
2
- import { t as SqlResultSet } from "../../evaluate-DjUkdPfO.cjs";
2
+ import { t as SqlResultSet } from "../../evaluate-3T-0yAut.cjs";
3
3
  import { OdbQueryInfo, OdbReport, Package } from "odf.js";
4
4
  //#region src/odb/report/source.d.ts
5
5
  declare class OdbReportDataSourceError extends Error {
@@ -1,5 +1,5 @@
1
1
  import { n as HsqldbDecodeOptions } from "../../rowformat-Cl2exlEh.js";
2
- import { t as SqlResultSet } from "../../evaluate-BKLK67uP.js";
2
+ import { t as SqlResultSet } from "../../evaluate-Ddsxehrf.js";
3
3
  import { OdbQueryInfo, OdbReport, Package } from "odf.js";
4
4
  //#region src/odb/report/source.d.ts
5
5
  declare class OdbReportDataSourceError extends Error {
@@ -71,6 +71,16 @@ function resolveTable(tables, ref, sql) {
71
71
  if (table === void 0) throw new require_odb_sql_errors.HsqldbSqlEvaluationError(`table "${resolvedName}" not found`, sql);
72
72
  return table;
73
73
  }
74
+ function columnAt(columns, index, sql) {
75
+ const column = columns[index];
76
+ if (column === void 0) throw new require_odb_sql_errors.HsqldbSqlEvaluationError(`column index ${String(index)} is outside the joined column list`, sql);
77
+ return column;
78
+ }
79
+ function valueAtIndex(row, index, sql) {
80
+ const value = row[index];
81
+ if (value === void 0) throw new require_odb_sql_errors.HsqldbSqlEvaluationError(`malformed joined row: it carries ${String(row.length)} values but index ${String(index)} was expected`, sql);
82
+ return value;
83
+ }
74
84
  function resolveColumnIndex(columns, candidateIndices, ref, sql) {
75
85
  let firstExact;
76
86
  let exactCount = 0;
@@ -98,39 +108,32 @@ var ColumnResolver = class {
98
108
  sql;
99
109
  cache = /* @__PURE__ */ new Map();
100
110
  columns;
101
- tableNames;
102
- constructor(tables, sql) {
111
+ qualifierNames;
112
+ constructor(columns, sql) {
103
113
  this.sql = sql;
104
- this.tableNames = tables.map((table) => table.tableName);
105
- this.columns = tables.flatMap((table) => table.columns.map((column) => ({
106
- tableName: table.tableName,
107
- columnName: column.name
108
- })));
114
+ this.columns = columns;
115
+ this.qualifierNames = [...new Set(columns.flatMap((column) => column.qualifiers))];
109
116
  }
110
117
  get columnCount() {
111
118
  return this.columns.length;
112
119
  }
113
120
  nameAt(index) {
114
- const column = this.columns[index];
115
- if (column === void 0) throw new require_odb_sql_errors.HsqldbSqlEvaluationError(`column index ${String(index)} is outside the joined column list`, this.sql);
116
- return column.columnName;
121
+ return columnAt(this.columns, index, this.sql).columnName;
117
122
  }
118
123
  indexOf(ref) {
119
124
  const cached = this.cache.get(ref);
120
125
  if (cached !== void 0) return cached;
121
126
  let candidateIndices;
122
127
  if (ref.qualifier !== void 0) {
123
- const qualifierName = resolveName(this.tableNames, ref.qualifier, "table qualifier", this.sql);
124
- candidateIndices = this.columns.map((column, index) => column.tableName === qualifierName ? index : -1).filter((index) => index >= 0);
128
+ const qualifierName = resolveName(this.qualifierNames, ref.qualifier, "table qualifier", this.sql);
129
+ candidateIndices = this.columns.map((column, index) => column.qualifiers.includes(qualifierName) ? index : -1).filter((index) => index >= 0);
125
130
  } else candidateIndices = this.columns.map((_column, index) => index);
126
131
  const index = resolveColumnIndex(this.columns, candidateIndices, ref.column, this.sql);
127
132
  this.cache.set(ref, index);
128
133
  return index;
129
134
  }
130
135
  valueAt(index, row) {
131
- const value = row[index];
132
- if (value === void 0) throw new require_odb_sql_errors.HsqldbSqlEvaluationError(`malformed joined row: it carries ${String(row.length)} values but the joined column list declares ${String(this.columns.length)}`, this.sql);
133
- return value;
136
+ return valueAtIndex(row, index, this.sql);
134
137
  }
135
138
  valueOf(ref, row) {
136
139
  return this.valueAt(this.indexOf(ref), row);
@@ -326,29 +329,129 @@ function evaluateGrouped(statement, plan, matching, resolver) {
326
329
  rows: sortRows(sortable, statement.orderBy.map((term) => term.direction), statement.sql)
327
330
  };
328
331
  }
329
- function joinTables(from, tables, sql) {
330
- const baseTable = resolveTable(tables, from.table, sql);
331
- const resolvedTables = [baseTable];
332
- let rows = baseTable.rows;
333
- for (const join of from.joins) {
334
- const joinTable = resolveTable(tables, join.table, sql);
335
- const resolver = new ColumnResolver([...resolvedTables, joinTable], sql);
336
- const nextRows = [];
337
- for (const leftRow of rows) for (const rightRow of joinTable.rows) {
338
- const combined = [...leftRow, ...rightRow];
339
- if (evaluatePredicate(join.on, combined, resolver, sql) === "true") nextRows.push(combined);
340
- }
341
- rows = nextRows;
342
- resolvedTables.push(joinTable);
332
+ function tableColumns(qualifier, table) {
333
+ return table.columns.map((column) => ({
334
+ qualifiers: [qualifier],
335
+ columnName: column.name
336
+ }));
337
+ }
338
+ function nullRow(count) {
339
+ return new Array(count).fill(require_odb_values.CELL_NULL);
340
+ }
341
+ function computeNaturalSharedPairs(leftColumns, rightColumns, sql) {
342
+ const rightIndexByName = /* @__PURE__ */ new Map();
343
+ const ambiguousRightNames = /* @__PURE__ */ new Set();
344
+ rightColumns.forEach((column, index) => {
345
+ if (rightIndexByName.has(column.columnName)) ambiguousRightNames.add(column.columnName);
346
+ else rightIndexByName.set(column.columnName, index);
347
+ });
348
+ const pairs = [];
349
+ const seenNames = /* @__PURE__ */ new Set();
350
+ leftColumns.forEach((leftColumn, leftIndex) => {
351
+ const rightIndex = rightIndexByName.get(leftColumn.columnName);
352
+ if (rightIndex === void 0) return;
353
+ if (ambiguousRightNames.has(leftColumn.columnName) || seenNames.has(leftColumn.columnName)) throw new require_odb_sql_errors.HsqldbSqlEvaluationError(`NATURAL JOIN cannot determine a unique match for column "${leftColumn.columnName}" -- more than one column shares that name on one side of the join`, sql);
354
+ seenNames.add(leftColumn.columnName);
355
+ pairs.push({
356
+ leftIndex,
357
+ rightIndex,
358
+ columnName: leftColumn.columnName
359
+ });
360
+ });
361
+ return pairs;
362
+ }
363
+ function evaluateSharedColumnMatch(combined, pairs, leftColumnCount, sql) {
364
+ return pairs.every((pair) => {
365
+ const left = valueAtIndex(combined, pair.leftIndex, sql);
366
+ const right = valueAtIndex(combined, leftColumnCount + pair.rightIndex, sql);
367
+ return left.kind !== "empty" && right.kind !== "empty" && compareValues(left, right, sql) === 0;
368
+ });
369
+ }
370
+ function mergeSharedColumns(rawColumns, rows, sharedPairs, leftColumnCount, sql) {
371
+ const sharedLeftIndices = new Set(sharedPairs.map((pair) => pair.leftIndex));
372
+ const sharedRightIndices = new Set(sharedPairs.map((pair) => leftColumnCount + pair.rightIndex));
373
+ const remainingIndices = rawColumns.map((_column, index) => index).filter((index) => !sharedLeftIndices.has(index) && !sharedRightIndices.has(index));
374
+ return {
375
+ columns: [...sharedPairs.map((pair) => {
376
+ const leftColumn = columnAt(rawColumns, pair.leftIndex, sql);
377
+ const rightColumn = columnAt(rawColumns, leftColumnCount + pair.rightIndex, sql);
378
+ return {
379
+ qualifiers: [.../* @__PURE__ */ new Set([...leftColumn.qualifiers, ...rightColumn.qualifiers])],
380
+ columnName: leftColumn.columnName
381
+ };
382
+ }), ...remainingIndices.map((index) => columnAt(rawColumns, index, sql))],
383
+ rows: rows.map((row) => {
384
+ const mergedValues = sharedPairs.map((pair) => {
385
+ const leftValue = valueAtIndex(row, pair.leftIndex, sql);
386
+ const rightValue = valueAtIndex(row, leftColumnCount + pair.rightIndex, sql);
387
+ return leftValue.kind !== "empty" ? leftValue : rightValue;
388
+ });
389
+ const remainingValues = remainingIndices.map((index) => valueAtIndex(row, index, sql));
390
+ return [...mergedValues, ...remainingValues];
391
+ })
392
+ };
393
+ }
394
+ function applyJoinClause(left, join, tables, sql) {
395
+ const rightTable = resolveTable(tables, join.table, sql);
396
+ const rightColumns = tableColumns(join.alias !== void 0 ? join.alias.name : rightTable.tableName, rightTable);
397
+ const leftColumnCount = left.columns.length;
398
+ const rawColumns = [...left.columns, ...rightColumns];
399
+ let onPredicate;
400
+ let sharedPairs = [];
401
+ if (join.condition.kind === "on") onPredicate = join.condition.predicate;
402
+ else if (join.condition.kind === "using") {
403
+ const allLeftIndices = left.columns.map((_column, index) => index);
404
+ const allRightIndices = rightColumns.map((_column, index) => index);
405
+ sharedPairs = join.condition.columns.map((ref) => {
406
+ const leftIndex = resolveColumnIndex(left.columns, allLeftIndices, ref, sql);
407
+ return {
408
+ leftIndex,
409
+ rightIndex: resolveColumnIndex(rightColumns, allRightIndices, ref, sql),
410
+ columnName: columnAt(left.columns, leftIndex, sql).columnName
411
+ };
412
+ });
413
+ } else if (join.condition.kind === "natural") {
414
+ sharedPairs = computeNaturalSharedPairs(left.columns, rightColumns, sql);
415
+ if (sharedPairs.length === 0) throw new require_odb_sql_errors.HsqldbSqlEvaluationError("NATURAL JOIN found no columns shared between the joined tables", sql);
343
416
  }
417
+ const rawResolver = new ColumnResolver(rawColumns, sql);
418
+ const matchedLeft = /* @__PURE__ */ new Set();
419
+ const matchedRight = /* @__PURE__ */ new Set();
420
+ const paired = [];
421
+ left.rows.forEach((leftRow, leftIndex) => {
422
+ rightTable.rows.forEach((rightRow, rightIndex) => {
423
+ const combined = [...leftRow, ...rightRow];
424
+ if (onPredicate !== void 0 ? evaluatePredicate(onPredicate, combined, rawResolver, sql) === "true" : sharedPairs.length > 0 ? evaluateSharedColumnMatch(combined, sharedPairs, leftColumnCount, sql) : true) {
425
+ paired.push(combined);
426
+ matchedLeft.add(leftIndex);
427
+ matchedRight.add(rightIndex);
428
+ }
429
+ });
430
+ });
431
+ if (join.joinKind === "left" || join.joinKind === "full") left.rows.forEach((leftRow, leftIndex) => {
432
+ if (!matchedLeft.has(leftIndex)) paired.push([...leftRow, ...nullRow(rightColumns.length)]);
433
+ });
434
+ if (join.joinKind === "right" || join.joinKind === "full") rightTable.rows.forEach((rightRow, rightIndex) => {
435
+ if (!matchedRight.has(rightIndex)) paired.push([...nullRow(leftColumnCount), ...rightRow]);
436
+ });
437
+ if (join.condition.kind === "using" || join.condition.kind === "natural") return mergeSharedColumns(rawColumns, paired, sharedPairs, leftColumnCount, sql);
344
438
  return {
345
- resolvedTables,
346
- rows
439
+ columns: rawColumns,
440
+ rows: paired
441
+ };
442
+ }
443
+ function joinTables(from, tables, sql) {
444
+ const baseTable = resolveTable(tables, from.table, sql);
445
+ let result = {
446
+ columns: tableColumns(from.alias !== void 0 ? from.alias.name : baseTable.tableName, baseTable),
447
+ rows: baseTable.rows
347
448
  };
449
+ for (const join of from.joins) result = applyJoinClause(result, join, tables, sql);
450
+ return result;
348
451
  }
349
452
  function evaluateSelect(statement, tables) {
350
- const { resolvedTables, rows } = joinTables(statement.from, tables, statement.sql);
351
- const resolver = new ColumnResolver(resolvedTables, statement.sql);
453
+ const { columns, rows } = joinTables(statement.from, tables, statement.sql);
454
+ const resolver = new ColumnResolver(columns, statement.sql);
352
455
  const plan = planSelectList(statement, resolver);
353
456
  const where = statement.where;
354
457
  const matching = where === void 0 ? rows : rows.filter((row) => evaluatePredicate(where, row, resolver, statement.sql) === "true");
@@ -1,2 +1,2 @@
1
- import { n as evaluateSelect, t as SqlResultSet } from "../../evaluate-DjUkdPfO.cjs";
1
+ import { n as evaluateSelect, t as SqlResultSet } from "../../evaluate-3T-0yAut.cjs";
2
2
  export { SqlResultSet, evaluateSelect };
@@ -1,2 +1,2 @@
1
- import { n as evaluateSelect, t as SqlResultSet } from "../../evaluate-BKLK67uP.js";
1
+ import { n as evaluateSelect, t as SqlResultSet } from "../../evaluate-Ddsxehrf.js";
2
2
  export { SqlResultSet, evaluateSelect };
@@ -70,6 +70,16 @@ function resolveTable(tables, ref, sql) {
70
70
  if (table === void 0) throw new HsqldbSqlEvaluationError(`table "${resolvedName}" not found`, sql);
71
71
  return table;
72
72
  }
73
+ function columnAt(columns, index, sql) {
74
+ const column = columns[index];
75
+ if (column === void 0) throw new HsqldbSqlEvaluationError(`column index ${String(index)} is outside the joined column list`, sql);
76
+ return column;
77
+ }
78
+ function valueAtIndex(row, index, sql) {
79
+ const value = row[index];
80
+ if (value === void 0) throw new HsqldbSqlEvaluationError(`malformed joined row: it carries ${String(row.length)} values but index ${String(index)} was expected`, sql);
81
+ return value;
82
+ }
73
83
  function resolveColumnIndex(columns, candidateIndices, ref, sql) {
74
84
  let firstExact;
75
85
  let exactCount = 0;
@@ -97,39 +107,32 @@ var ColumnResolver = class {
97
107
  sql;
98
108
  cache = /* @__PURE__ */ new Map();
99
109
  columns;
100
- tableNames;
101
- constructor(tables, sql) {
110
+ qualifierNames;
111
+ constructor(columns, sql) {
102
112
  this.sql = sql;
103
- this.tableNames = tables.map((table) => table.tableName);
104
- this.columns = tables.flatMap((table) => table.columns.map((column) => ({
105
- tableName: table.tableName,
106
- columnName: column.name
107
- })));
113
+ this.columns = columns;
114
+ this.qualifierNames = [...new Set(columns.flatMap((column) => column.qualifiers))];
108
115
  }
109
116
  get columnCount() {
110
117
  return this.columns.length;
111
118
  }
112
119
  nameAt(index) {
113
- const column = this.columns[index];
114
- if (column === void 0) throw new HsqldbSqlEvaluationError(`column index ${String(index)} is outside the joined column list`, this.sql);
115
- return column.columnName;
120
+ return columnAt(this.columns, index, this.sql).columnName;
116
121
  }
117
122
  indexOf(ref) {
118
123
  const cached = this.cache.get(ref);
119
124
  if (cached !== void 0) return cached;
120
125
  let candidateIndices;
121
126
  if (ref.qualifier !== void 0) {
122
- const qualifierName = resolveName(this.tableNames, ref.qualifier, "table qualifier", this.sql);
123
- candidateIndices = this.columns.map((column, index) => column.tableName === qualifierName ? index : -1).filter((index) => index >= 0);
127
+ const qualifierName = resolveName(this.qualifierNames, ref.qualifier, "table qualifier", this.sql);
128
+ candidateIndices = this.columns.map((column, index) => column.qualifiers.includes(qualifierName) ? index : -1).filter((index) => index >= 0);
124
129
  } else candidateIndices = this.columns.map((_column, index) => index);
125
130
  const index = resolveColumnIndex(this.columns, candidateIndices, ref.column, this.sql);
126
131
  this.cache.set(ref, index);
127
132
  return index;
128
133
  }
129
134
  valueAt(index, row) {
130
- const value = row[index];
131
- if (value === void 0) throw new HsqldbSqlEvaluationError(`malformed joined row: it carries ${String(row.length)} values but the joined column list declares ${String(this.columns.length)}`, this.sql);
132
- return value;
135
+ return valueAtIndex(row, index, this.sql);
133
136
  }
134
137
  valueOf(ref, row) {
135
138
  return this.valueAt(this.indexOf(ref), row);
@@ -325,29 +328,129 @@ function evaluateGrouped(statement, plan, matching, resolver) {
325
328
  rows: sortRows(sortable, statement.orderBy.map((term) => term.direction), statement.sql)
326
329
  };
327
330
  }
328
- function joinTables(from, tables, sql) {
329
- const baseTable = resolveTable(tables, from.table, sql);
330
- const resolvedTables = [baseTable];
331
- let rows = baseTable.rows;
332
- for (const join of from.joins) {
333
- const joinTable = resolveTable(tables, join.table, sql);
334
- const resolver = new ColumnResolver([...resolvedTables, joinTable], sql);
335
- const nextRows = [];
336
- for (const leftRow of rows) for (const rightRow of joinTable.rows) {
337
- const combined = [...leftRow, ...rightRow];
338
- if (evaluatePredicate(join.on, combined, resolver, sql) === "true") nextRows.push(combined);
339
- }
340
- rows = nextRows;
341
- resolvedTables.push(joinTable);
331
+ function tableColumns(qualifier, table) {
332
+ return table.columns.map((column) => ({
333
+ qualifiers: [qualifier],
334
+ columnName: column.name
335
+ }));
336
+ }
337
+ function nullRow(count) {
338
+ return new Array(count).fill(CELL_NULL);
339
+ }
340
+ function computeNaturalSharedPairs(leftColumns, rightColumns, sql) {
341
+ const rightIndexByName = /* @__PURE__ */ new Map();
342
+ const ambiguousRightNames = /* @__PURE__ */ new Set();
343
+ rightColumns.forEach((column, index) => {
344
+ if (rightIndexByName.has(column.columnName)) ambiguousRightNames.add(column.columnName);
345
+ else rightIndexByName.set(column.columnName, index);
346
+ });
347
+ const pairs = [];
348
+ const seenNames = /* @__PURE__ */ new Set();
349
+ leftColumns.forEach((leftColumn, leftIndex) => {
350
+ const rightIndex = rightIndexByName.get(leftColumn.columnName);
351
+ if (rightIndex === void 0) return;
352
+ if (ambiguousRightNames.has(leftColumn.columnName) || seenNames.has(leftColumn.columnName)) throw new HsqldbSqlEvaluationError(`NATURAL JOIN cannot determine a unique match for column "${leftColumn.columnName}" -- more than one column shares that name on one side of the join`, sql);
353
+ seenNames.add(leftColumn.columnName);
354
+ pairs.push({
355
+ leftIndex,
356
+ rightIndex,
357
+ columnName: leftColumn.columnName
358
+ });
359
+ });
360
+ return pairs;
361
+ }
362
+ function evaluateSharedColumnMatch(combined, pairs, leftColumnCount, sql) {
363
+ return pairs.every((pair) => {
364
+ const left = valueAtIndex(combined, pair.leftIndex, sql);
365
+ const right = valueAtIndex(combined, leftColumnCount + pair.rightIndex, sql);
366
+ return left.kind !== "empty" && right.kind !== "empty" && compareValues(left, right, sql) === 0;
367
+ });
368
+ }
369
+ function mergeSharedColumns(rawColumns, rows, sharedPairs, leftColumnCount, sql) {
370
+ const sharedLeftIndices = new Set(sharedPairs.map((pair) => pair.leftIndex));
371
+ const sharedRightIndices = new Set(sharedPairs.map((pair) => leftColumnCount + pair.rightIndex));
372
+ const remainingIndices = rawColumns.map((_column, index) => index).filter((index) => !sharedLeftIndices.has(index) && !sharedRightIndices.has(index));
373
+ return {
374
+ columns: [...sharedPairs.map((pair) => {
375
+ const leftColumn = columnAt(rawColumns, pair.leftIndex, sql);
376
+ const rightColumn = columnAt(rawColumns, leftColumnCount + pair.rightIndex, sql);
377
+ return {
378
+ qualifiers: [.../* @__PURE__ */ new Set([...leftColumn.qualifiers, ...rightColumn.qualifiers])],
379
+ columnName: leftColumn.columnName
380
+ };
381
+ }), ...remainingIndices.map((index) => columnAt(rawColumns, index, sql))],
382
+ rows: rows.map((row) => {
383
+ const mergedValues = sharedPairs.map((pair) => {
384
+ const leftValue = valueAtIndex(row, pair.leftIndex, sql);
385
+ const rightValue = valueAtIndex(row, leftColumnCount + pair.rightIndex, sql);
386
+ return leftValue.kind !== "empty" ? leftValue : rightValue;
387
+ });
388
+ const remainingValues = remainingIndices.map((index) => valueAtIndex(row, index, sql));
389
+ return [...mergedValues, ...remainingValues];
390
+ })
391
+ };
392
+ }
393
+ function applyJoinClause(left, join, tables, sql) {
394
+ const rightTable = resolveTable(tables, join.table, sql);
395
+ const rightColumns = tableColumns(join.alias !== void 0 ? join.alias.name : rightTable.tableName, rightTable);
396
+ const leftColumnCount = left.columns.length;
397
+ const rawColumns = [...left.columns, ...rightColumns];
398
+ let onPredicate;
399
+ let sharedPairs = [];
400
+ if (join.condition.kind === "on") onPredicate = join.condition.predicate;
401
+ else if (join.condition.kind === "using") {
402
+ const allLeftIndices = left.columns.map((_column, index) => index);
403
+ const allRightIndices = rightColumns.map((_column, index) => index);
404
+ sharedPairs = join.condition.columns.map((ref) => {
405
+ const leftIndex = resolveColumnIndex(left.columns, allLeftIndices, ref, sql);
406
+ return {
407
+ leftIndex,
408
+ rightIndex: resolveColumnIndex(rightColumns, allRightIndices, ref, sql),
409
+ columnName: columnAt(left.columns, leftIndex, sql).columnName
410
+ };
411
+ });
412
+ } else if (join.condition.kind === "natural") {
413
+ sharedPairs = computeNaturalSharedPairs(left.columns, rightColumns, sql);
414
+ if (sharedPairs.length === 0) throw new HsqldbSqlEvaluationError("NATURAL JOIN found no columns shared between the joined tables", sql);
342
415
  }
416
+ const rawResolver = new ColumnResolver(rawColumns, sql);
417
+ const matchedLeft = /* @__PURE__ */ new Set();
418
+ const matchedRight = /* @__PURE__ */ new Set();
419
+ const paired = [];
420
+ left.rows.forEach((leftRow, leftIndex) => {
421
+ rightTable.rows.forEach((rightRow, rightIndex) => {
422
+ const combined = [...leftRow, ...rightRow];
423
+ if (onPredicate !== void 0 ? evaluatePredicate(onPredicate, combined, rawResolver, sql) === "true" : sharedPairs.length > 0 ? evaluateSharedColumnMatch(combined, sharedPairs, leftColumnCount, sql) : true) {
424
+ paired.push(combined);
425
+ matchedLeft.add(leftIndex);
426
+ matchedRight.add(rightIndex);
427
+ }
428
+ });
429
+ });
430
+ if (join.joinKind === "left" || join.joinKind === "full") left.rows.forEach((leftRow, leftIndex) => {
431
+ if (!matchedLeft.has(leftIndex)) paired.push([...leftRow, ...nullRow(rightColumns.length)]);
432
+ });
433
+ if (join.joinKind === "right" || join.joinKind === "full") rightTable.rows.forEach((rightRow, rightIndex) => {
434
+ if (!matchedRight.has(rightIndex)) paired.push([...nullRow(leftColumnCount), ...rightRow]);
435
+ });
436
+ if (join.condition.kind === "using" || join.condition.kind === "natural") return mergeSharedColumns(rawColumns, paired, sharedPairs, leftColumnCount, sql);
343
437
  return {
344
- resolvedTables,
345
- rows
438
+ columns: rawColumns,
439
+ rows: paired
440
+ };
441
+ }
442
+ function joinTables(from, tables, sql) {
443
+ const baseTable = resolveTable(tables, from.table, sql);
444
+ let result = {
445
+ columns: tableColumns(from.alias !== void 0 ? from.alias.name : baseTable.tableName, baseTable),
446
+ rows: baseTable.rows
346
447
  };
448
+ for (const join of from.joins) result = applyJoinClause(result, join, tables, sql);
449
+ return result;
347
450
  }
348
451
  function evaluateSelect(statement, tables) {
349
- const { resolvedTables, rows } = joinTables(statement.from, tables, statement.sql);
350
- const resolver = new ColumnResolver(resolvedTables, statement.sql);
452
+ const { columns, rows } = joinTables(statement.from, tables, statement.sql);
453
+ const resolver = new ColumnResolver(columns, statement.sql);
351
454
  const plan = planSelectList(statement, resolver);
352
455
  const where = statement.where;
353
456
  const matching = where === void 0 ? rows : rows.filter((row) => evaluatePredicate(where, row, resolver, statement.sql) === "true");
@@ -13,13 +13,6 @@ function isAggregateFunction(keyword) {
13
13
  return AGGREGATE_FUNCTIONS.has(keyword);
14
14
  }
15
15
  const OUT_OF_SCOPE_KEYWORDS = /* @__PURE__ */ new Map([
16
- ["OUTER", "a JOIN"],
17
- ["LEFT", "a JOIN"],
18
- ["RIGHT", "a JOIN"],
19
- ["FULL", "a JOIN"],
20
- ["CROSS", "a JOIN"],
21
- ["NATURAL", "a JOIN"],
22
- ["USING", "a JOIN"],
23
16
  ["UNION", "UNION"],
24
17
  ["INTERSECT", "INTERSECT"],
25
18
  ["EXCEPT", "EXCEPT"],
@@ -31,7 +24,6 @@ const OUT_OF_SCOPE_KEYWORDS = /* @__PURE__ */ new Map([
31
24
  ["OFFSET", "a row-limit clause (OFFSET)"],
32
25
  ["FETCH", "a row-limit clause (FETCH)"],
33
26
  ["TOP", "a row-limit clause (TOP)"],
34
- ["AS", "a column or table alias (AS)"],
35
27
  ["CASE", "a CASE expression"],
36
28
  ["WHEN", "a CASE expression"],
37
29
  ["THEN", "a CASE expression"],
@@ -175,11 +167,12 @@ var SqlParser = class {
175
167
  }
176
168
  parseSelectItem() {
177
169
  const token = this.peek();
178
- if (token.kind === "keyword" && isAggregateFunction(token.keyword)) return this.parseAggregate(token.keyword);
179
- return {
170
+ const item = token.kind === "keyword" && isAggregateFunction(token.keyword) ? this.parseAggregate(token.keyword) : {
180
171
  kind: "column",
181
172
  column: this.parseColumnRef()
182
173
  };
174
+ if (this.atKeyword("AS")) throw new require_odb_sql_errors.HsqldbSqlUnsupportedError("a column alias (AS)", this.sql);
175
+ return item;
183
176
  }
184
177
  parseSelectList() {
185
178
  if (this.atPunctuation("*")) {
@@ -439,27 +432,93 @@ var SqlParser = class {
439
432
  direction: "asc"
440
433
  };
441
434
  }
442
- parseTableRef() {
435
+ parseTableRefWithAlias() {
443
436
  if (this.atPunctuation("(")) throw new require_odb_sql_errors.HsqldbSqlUnsupportedError("a derived table in FROM", this.sql);
444
- const name = this.takeName("a table name");
437
+ const table = this.takeName("a table name");
445
438
  if (this.atPunctuation(".")) throw new require_odb_sql_errors.HsqldbSqlUnsupportedError("a schema-qualified table name", this.sql);
446
- if (this.peek().kind === "identifier") throw new require_odb_sql_errors.HsqldbSqlUnsupportedError("a table alias", this.sql);
447
- return name;
439
+ return {
440
+ table,
441
+ alias: this.parseOptionalTableAlias()
442
+ };
443
+ }
444
+ parseOptionalTableAlias() {
445
+ if (this.atKeyword("AS")) {
446
+ this.advance();
447
+ return this.takeName("a table alias after AS");
448
+ }
449
+ if (this.peek().kind === "identifier") return this.takeName("a table alias");
450
+ }
451
+ parseJoinKind() {
452
+ if (this.atKeyword("INNER")) {
453
+ this.advance();
454
+ return "inner";
455
+ }
456
+ if (this.atKeyword("LEFT")) {
457
+ this.advance();
458
+ this.consumeOptionalOuter();
459
+ return "left";
460
+ }
461
+ if (this.atKeyword("RIGHT")) {
462
+ this.advance();
463
+ this.consumeOptionalOuter();
464
+ return "right";
465
+ }
466
+ if (this.atKeyword("FULL")) {
467
+ this.advance();
468
+ this.consumeOptionalOuter();
469
+ return "full";
470
+ }
471
+ if (this.atKeyword("CROSS")) {
472
+ this.advance();
473
+ return "cross";
474
+ }
475
+ if (this.atKeyword("JOIN")) return "inner";
476
+ }
477
+ consumeOptionalOuter() {
478
+ if (this.atKeyword("OUTER")) this.advance();
479
+ }
480
+ parseJoinCondition(joinKind, natural) {
481
+ if (natural) return { kind: "natural" };
482
+ if (joinKind === "cross") return { kind: "none" };
483
+ if (this.atKeyword("USING")) {
484
+ this.advance();
485
+ this.takePunctuation("(");
486
+ const columns = [this.takeName("a column name in USING")];
487
+ while (this.atPunctuation(",")) {
488
+ this.advance();
489
+ columns.push(this.takeName("a column name in USING"));
490
+ }
491
+ this.takePunctuation(")");
492
+ return {
493
+ kind: "using",
494
+ columns
495
+ };
496
+ }
497
+ this.takeKeyword("ON");
498
+ return {
499
+ kind: "on",
500
+ predicate: this.parsePredicate()
501
+ };
448
502
  }
449
503
  parseJoins() {
450
504
  const joins = [];
451
505
  for (;;) {
452
- if (this.atKeyword("INNER")) {
453
- this.advance();
454
- this.takeKeyword("JOIN");
455
- } else if (this.atKeyword("JOIN")) this.advance();
456
- else break;
457
- const table = this.parseTableRef();
458
- this.takeKeyword("ON");
459
- const on = this.parsePredicate();
506
+ const natural = this.atKeyword("NATURAL");
507
+ if (natural) this.advance();
508
+ const joinKind = this.parseJoinKind();
509
+ if (joinKind === void 0) {
510
+ if (natural) this.fail("keyword JOIN, INNER, LEFT, RIGHT or FULL after NATURAL");
511
+ break;
512
+ }
513
+ if (natural && joinKind === "cross") this.fail("keyword JOIN, INNER, LEFT, RIGHT or FULL after NATURAL (NATURAL CROSS JOIN is not valid SQL)");
514
+ this.takeKeyword("JOIN");
515
+ const { table, alias } = this.parseTableRefWithAlias();
516
+ const condition = this.parseJoinCondition(joinKind, natural);
460
517
  joins.push({
518
+ joinKind,
461
519
  table,
462
- on
520
+ alias,
521
+ condition
463
522
  });
464
523
  }
465
524
  return joins;
@@ -470,8 +529,10 @@ var SqlParser = class {
470
529
  this.advance();
471
530
  const items = this.parseSelectList();
472
531
  this.takeKeyword("FROM");
532
+ const { table, alias } = this.parseTableRefWithAlias();
473
533
  const from = {
474
- table: this.parseTableRef(),
534
+ table,
535
+ alias,
475
536
  joins: this.parseJoins()
476
537
  };
477
538
  if (this.atPunctuation(",")) throw new require_odb_sql_errors.HsqldbSqlUnsupportedError("a comma-separated FROM list (write an explicit JOIN instead)", this.sql);
@@ -1,2 +1,2 @@
1
- import { a as SqlJoinClause, c as SqlOperand, d as SqlSelectItem, f as SqlSelectStatement, i as SqlFromClause, l as SqlOrderByTerm, m as parseSelect, n as SqlAggregateFunction, o as SqlLiteral, p as SqlSortDirection, r as SqlColumnRef, s as SqlNameRef, t as SqlAggregateArgument, u as SqlPredicate } from "../../parser-D9fiOUfR.cjs";
2
- export { SqlAggregateArgument, SqlAggregateFunction, SqlColumnRef, SqlFromClause, SqlJoinClause, SqlLiteral, SqlNameRef, SqlOperand, SqlOrderByTerm, SqlPredicate, SqlSelectItem, SqlSelectStatement, SqlSortDirection, parseSelect };
1
+ import { a as SqlJoinClause, c as SqlLiteral, d as SqlOrderByTerm, f as SqlPredicate, g as parseSelect, h as SqlSortDirection, i as SqlFromClause, l as SqlNameRef, m as SqlSelectStatement, n as SqlAggregateFunction, o as SqlJoinCondition, p as SqlSelectItem, r as SqlColumnRef, s as SqlJoinKind, t as SqlAggregateArgument, u as SqlOperand } from "../../parser-h1n0BflA.cjs";
2
+ export { SqlAggregateArgument, SqlAggregateFunction, SqlColumnRef, SqlFromClause, SqlJoinClause, SqlJoinCondition, SqlJoinKind, SqlLiteral, SqlNameRef, SqlOperand, SqlOrderByTerm, SqlPredicate, SqlSelectItem, SqlSelectStatement, SqlSortDirection, parseSelect };
@@ -1,2 +1,2 @@
1
- import { a as SqlJoinClause, c as SqlOperand, d as SqlSelectItem, f as SqlSelectStatement, i as SqlFromClause, l as SqlOrderByTerm, m as parseSelect, n as SqlAggregateFunction, o as SqlLiteral, p as SqlSortDirection, r as SqlColumnRef, s as SqlNameRef, t as SqlAggregateArgument, u as SqlPredicate } from "../../parser-fyVOtBAo.js";
2
- export { SqlAggregateArgument, SqlAggregateFunction, SqlColumnRef, SqlFromClause, SqlJoinClause, SqlLiteral, SqlNameRef, SqlOperand, SqlOrderByTerm, SqlPredicate, SqlSelectItem, SqlSelectStatement, SqlSortDirection, parseSelect };
1
+ import { a as SqlJoinClause, c as SqlLiteral, d as SqlOrderByTerm, f as SqlPredicate, g as parseSelect, h as SqlSortDirection, i as SqlFromClause, l as SqlNameRef, m as SqlSelectStatement, n as SqlAggregateFunction, o as SqlJoinCondition, p as SqlSelectItem, r as SqlColumnRef, s as SqlJoinKind, t as SqlAggregateArgument, u as SqlOperand } from "../../parser-D-9P_0Qd.js";
2
+ export { SqlAggregateArgument, SqlAggregateFunction, SqlColumnRef, SqlFromClause, SqlJoinClause, SqlJoinCondition, SqlJoinKind, SqlLiteral, SqlNameRef, SqlOperand, SqlOrderByTerm, SqlPredicate, SqlSelectItem, SqlSelectStatement, SqlSortDirection, parseSelect };
@@ -12,13 +12,6 @@ function isAggregateFunction(keyword) {
12
12
  return AGGREGATE_FUNCTIONS.has(keyword);
13
13
  }
14
14
  const OUT_OF_SCOPE_KEYWORDS = /* @__PURE__ */ new Map([
15
- ["OUTER", "a JOIN"],
16
- ["LEFT", "a JOIN"],
17
- ["RIGHT", "a JOIN"],
18
- ["FULL", "a JOIN"],
19
- ["CROSS", "a JOIN"],
20
- ["NATURAL", "a JOIN"],
21
- ["USING", "a JOIN"],
22
15
  ["UNION", "UNION"],
23
16
  ["INTERSECT", "INTERSECT"],
24
17
  ["EXCEPT", "EXCEPT"],
@@ -30,7 +23,6 @@ const OUT_OF_SCOPE_KEYWORDS = /* @__PURE__ */ new Map([
30
23
  ["OFFSET", "a row-limit clause (OFFSET)"],
31
24
  ["FETCH", "a row-limit clause (FETCH)"],
32
25
  ["TOP", "a row-limit clause (TOP)"],
33
- ["AS", "a column or table alias (AS)"],
34
26
  ["CASE", "a CASE expression"],
35
27
  ["WHEN", "a CASE expression"],
36
28
  ["THEN", "a CASE expression"],
@@ -174,11 +166,12 @@ var SqlParser = class {
174
166
  }
175
167
  parseSelectItem() {
176
168
  const token = this.peek();
177
- if (token.kind === "keyword" && isAggregateFunction(token.keyword)) return this.parseAggregate(token.keyword);
178
- return {
169
+ const item = token.kind === "keyword" && isAggregateFunction(token.keyword) ? this.parseAggregate(token.keyword) : {
179
170
  kind: "column",
180
171
  column: this.parseColumnRef()
181
172
  };
173
+ if (this.atKeyword("AS")) throw new HsqldbSqlUnsupportedError("a column alias (AS)", this.sql);
174
+ return item;
182
175
  }
183
176
  parseSelectList() {
184
177
  if (this.atPunctuation("*")) {
@@ -438,27 +431,93 @@ var SqlParser = class {
438
431
  direction: "asc"
439
432
  };
440
433
  }
441
- parseTableRef() {
434
+ parseTableRefWithAlias() {
442
435
  if (this.atPunctuation("(")) throw new HsqldbSqlUnsupportedError("a derived table in FROM", this.sql);
443
- const name = this.takeName("a table name");
436
+ const table = this.takeName("a table name");
444
437
  if (this.atPunctuation(".")) throw new HsqldbSqlUnsupportedError("a schema-qualified table name", this.sql);
445
- if (this.peek().kind === "identifier") throw new HsqldbSqlUnsupportedError("a table alias", this.sql);
446
- return name;
438
+ return {
439
+ table,
440
+ alias: this.parseOptionalTableAlias()
441
+ };
442
+ }
443
+ parseOptionalTableAlias() {
444
+ if (this.atKeyword("AS")) {
445
+ this.advance();
446
+ return this.takeName("a table alias after AS");
447
+ }
448
+ if (this.peek().kind === "identifier") return this.takeName("a table alias");
449
+ }
450
+ parseJoinKind() {
451
+ if (this.atKeyword("INNER")) {
452
+ this.advance();
453
+ return "inner";
454
+ }
455
+ if (this.atKeyword("LEFT")) {
456
+ this.advance();
457
+ this.consumeOptionalOuter();
458
+ return "left";
459
+ }
460
+ if (this.atKeyword("RIGHT")) {
461
+ this.advance();
462
+ this.consumeOptionalOuter();
463
+ return "right";
464
+ }
465
+ if (this.atKeyword("FULL")) {
466
+ this.advance();
467
+ this.consumeOptionalOuter();
468
+ return "full";
469
+ }
470
+ if (this.atKeyword("CROSS")) {
471
+ this.advance();
472
+ return "cross";
473
+ }
474
+ if (this.atKeyword("JOIN")) return "inner";
475
+ }
476
+ consumeOptionalOuter() {
477
+ if (this.atKeyword("OUTER")) this.advance();
478
+ }
479
+ parseJoinCondition(joinKind, natural) {
480
+ if (natural) return { kind: "natural" };
481
+ if (joinKind === "cross") return { kind: "none" };
482
+ if (this.atKeyword("USING")) {
483
+ this.advance();
484
+ this.takePunctuation("(");
485
+ const columns = [this.takeName("a column name in USING")];
486
+ while (this.atPunctuation(",")) {
487
+ this.advance();
488
+ columns.push(this.takeName("a column name in USING"));
489
+ }
490
+ this.takePunctuation(")");
491
+ return {
492
+ kind: "using",
493
+ columns
494
+ };
495
+ }
496
+ this.takeKeyword("ON");
497
+ return {
498
+ kind: "on",
499
+ predicate: this.parsePredicate()
500
+ };
447
501
  }
448
502
  parseJoins() {
449
503
  const joins = [];
450
504
  for (;;) {
451
- if (this.atKeyword("INNER")) {
452
- this.advance();
453
- this.takeKeyword("JOIN");
454
- } else if (this.atKeyword("JOIN")) this.advance();
455
- else break;
456
- const table = this.parseTableRef();
457
- this.takeKeyword("ON");
458
- const on = this.parsePredicate();
505
+ const natural = this.atKeyword("NATURAL");
506
+ if (natural) this.advance();
507
+ const joinKind = this.parseJoinKind();
508
+ if (joinKind === void 0) {
509
+ if (natural) this.fail("keyword JOIN, INNER, LEFT, RIGHT or FULL after NATURAL");
510
+ break;
511
+ }
512
+ if (natural && joinKind === "cross") this.fail("keyword JOIN, INNER, LEFT, RIGHT or FULL after NATURAL (NATURAL CROSS JOIN is not valid SQL)");
513
+ this.takeKeyword("JOIN");
514
+ const { table, alias } = this.parseTableRefWithAlias();
515
+ const condition = this.parseJoinCondition(joinKind, natural);
459
516
  joins.push({
517
+ joinKind,
460
518
  table,
461
- on
519
+ alias,
520
+ condition
462
521
  });
463
522
  }
464
523
  return joins;
@@ -469,8 +528,10 @@ var SqlParser = class {
469
528
  this.advance();
470
529
  const items = this.parseSelectList();
471
530
  this.takeKeyword("FROM");
531
+ const { table, alias } = this.parseTableRefWithAlias();
472
532
  const from = {
473
- table: this.parseTableRef(),
533
+ table,
534
+ alias,
474
535
  joins: this.parseJoins()
475
536
  };
476
537
  if (this.atPunctuation(",")) throw new HsqldbSqlUnsupportedError("a comma-separated FROM list (write an explicit JOIN instead)", this.sql);
@@ -88,12 +88,27 @@ interface SqlOrderByTerm {
88
88
  readonly column: SqlColumnRef;
89
89
  readonly direction: SqlSortDirection;
90
90
  }
91
+ type SqlJoinKind = "inner" | "left" | "right" | "full" | "cross";
92
+ type SqlJoinCondition = {
93
+ readonly kind: "on";
94
+ readonly predicate: SqlPredicate;
95
+ } | {
96
+ readonly kind: "using";
97
+ readonly columns: readonly SqlNameRef[];
98
+ } | {
99
+ readonly kind: "natural";
100
+ } | {
101
+ readonly kind: "none";
102
+ };
91
103
  interface SqlJoinClause {
104
+ readonly joinKind: SqlJoinKind;
92
105
  readonly table: SqlNameRef;
93
- readonly on: SqlPredicate;
106
+ readonly alias: SqlNameRef | undefined;
107
+ readonly condition: SqlJoinCondition;
94
108
  }
95
109
  interface SqlFromClause {
96
110
  readonly table: SqlNameRef;
111
+ readonly alias: SqlNameRef | undefined;
97
112
  readonly joins: readonly SqlJoinClause[];
98
113
  }
99
114
  interface SqlSelectStatement {
@@ -106,4 +121,4 @@ interface SqlSelectStatement {
106
121
  }
107
122
  declare function parseSelect(sql: string): SqlSelectStatement;
108
123
  //#endregion
109
- export { SqlJoinClause as a, SqlOperand as c, SqlSelectItem as d, SqlSelectStatement as f, SqlFromClause as i, SqlOrderByTerm as l, parseSelect as m, SqlAggregateFunction as n, SqlLiteral as o, SqlSortDirection as p, SqlColumnRef as r, SqlNameRef as s, SqlAggregateArgument as t, SqlPredicate as u };
124
+ export { SqlJoinClause as a, SqlLiteral as c, SqlOrderByTerm as d, SqlPredicate as f, parseSelect as g, SqlSortDirection as h, SqlFromClause as i, SqlNameRef as l, SqlSelectStatement as m, SqlAggregateFunction as n, SqlJoinCondition as o, SqlSelectItem as p, SqlColumnRef as r, SqlJoinKind as s, SqlAggregateArgument as t, SqlOperand as u };
@@ -88,12 +88,27 @@ interface SqlOrderByTerm {
88
88
  readonly column: SqlColumnRef;
89
89
  readonly direction: SqlSortDirection;
90
90
  }
91
+ type SqlJoinKind = "inner" | "left" | "right" | "full" | "cross";
92
+ type SqlJoinCondition = {
93
+ readonly kind: "on";
94
+ readonly predicate: SqlPredicate;
95
+ } | {
96
+ readonly kind: "using";
97
+ readonly columns: readonly SqlNameRef[];
98
+ } | {
99
+ readonly kind: "natural";
100
+ } | {
101
+ readonly kind: "none";
102
+ };
91
103
  interface SqlJoinClause {
104
+ readonly joinKind: SqlJoinKind;
92
105
  readonly table: SqlNameRef;
93
- readonly on: SqlPredicate;
106
+ readonly alias: SqlNameRef | undefined;
107
+ readonly condition: SqlJoinCondition;
94
108
  }
95
109
  interface SqlFromClause {
96
110
  readonly table: SqlNameRef;
111
+ readonly alias: SqlNameRef | undefined;
97
112
  readonly joins: readonly SqlJoinClause[];
98
113
  }
99
114
  interface SqlSelectStatement {
@@ -106,4 +121,4 @@ interface SqlSelectStatement {
106
121
  }
107
122
  declare function parseSelect(sql: string): SqlSelectStatement;
108
123
  //#endregion
109
- export { SqlJoinClause as a, SqlOperand as c, SqlSelectItem as d, SqlSelectStatement as f, SqlFromClause as i, SqlOrderByTerm as l, parseSelect as m, SqlAggregateFunction as n, SqlLiteral as o, SqlSortDirection as p, SqlColumnRef as r, SqlNameRef as s, SqlAggregateArgument as t, SqlPredicate as u };
124
+ export { SqlJoinClause as a, SqlLiteral as c, SqlOrderByTerm as d, SqlPredicate as f, parseSelect as g, SqlSortDirection as h, SqlFromClause as i, SqlNameRef as l, SqlSelectStatement as m, SqlAggregateFunction as n, SqlJoinCondition as o, SqlSelectItem as p, SqlColumnRef as r, SqlJoinKind as s, SqlAggregateArgument as t, SqlOperand as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js.documents",
3
- "version": "7.8.20",
3
+ "version": "7.9.0",
4
4
  "description": "Bidirectional docx/pptx <-> PDF conversion and a read+write editable OOXML document model, built on ooxml.js and Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -77,21 +77,21 @@
77
77
  ],
78
78
  "license": "MIT",
79
79
  "dependencies": {
80
- "archive-codec": "1.9.2",
80
+ "archive-codec": "1.10.0",
81
81
  "byte-codec": "1.4.1",
82
- "doc-codec": "2.5.0",
82
+ "doc-codec": "2.6.0",
83
83
  "document-schema.js": "7.5.0",
84
84
  "epub-codec": "1.3.2",
85
85
  "fflate": "0.8.3",
86
86
  "markdown-codec": "6.5.2",
87
87
  "odf.js": "7.9.1",
88
- "ooxml.js": "8.6.8",
88
+ "ooxml.js": "8.6.9",
89
89
  "pdf-codec": "4.0.11",
90
- "ppt-codec": "1.4.2",
91
- "rtf-codec": "4.1.2",
90
+ "ppt-codec": "1.4.3",
91
+ "rtf-codec": "4.1.3",
92
92
  "temml": "0.13.4",
93
- "wpd-codec": "3.2.0",
94
- "xls-codec": "4.9.0",
93
+ "wpd-codec": "3.2.1",
94
+ "xls-codec": "4.11.0",
95
95
  "zod": "4.4.3"
96
96
  },
97
97
  "devDependencies": {