@minnowdb/core 0.7.1 → 0.7.2

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.
@@ -5373,13 +5373,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
5373
5373
  }
5374
5374
  const open = this.#openTransaction;
5375
5375
  if (open !== void 0) {
5376
- const boundStatement = bindStatementParameters(statement, params);
5377
- if (!isTransactionalStatement(boundStatement)) {
5378
- throw new TypeError(`${boundStatement.kind.toUpperCase().replace("-", " ")} is not allowed inside a transaction`);
5376
+ const boundStatement2 = bindStatementParameters(statement, params);
5377
+ if (!isTransactionalStatement(boundStatement2)) {
5378
+ throw new TypeError(`${boundStatement2.kind.toUpperCase().replace("-", " ")} is not allowed inside a transaction`);
5379
5379
  }
5380
- return externalizeExecuteResult(await this.#projectReturningItems(boundStatement, await this.#duringTransaction(open, () => open.session.executeStatement(boundStatement))));
5380
+ return externalizeExecuteResult(await this.#projectReturningItems(boundStatement2, await this.#duringTransaction(open, () => open.session.executeStatement(boundStatement2))));
5381
5381
  }
5382
- return externalizeExecuteResult(await this.#projectReturningItems(statement, await this.runStatement(bindStatementParameters(statement, params))));
5382
+ const boundStatement = bindStatementParameters(statement, params);
5383
+ return externalizeExecuteResult(await this.#projectReturningItems(boundStatement, await this.runStatement(boundStatement)));
5383
5384
  }
5384
5385
  async #assertForeignKeysPresent(table, rowsByColumn, read, transaction) {
5385
5386
  for (const key of table.foreignKeys ?? []) {
@@ -1494,14 +1494,17 @@ function scalarOrderExpression(expression, rows) {
1494
1494
  const matches = rows.select.filter((item) => item.alias === expression.reference);
1495
1495
  return matches.length === 1 ? structuredClone(matches[0]?.expression ?? expression) : structuredClone(expression);
1496
1496
  }
1497
- function emptyScalarExpression(expression) {
1497
+ function emptyScalarExpression(expression, preservedColumns = /* @__PURE__ */ new Set()) {
1498
1498
  if (isAggregateCall(expression)) {
1499
1499
  return { kind: "literal", value: expression.name === "COUNT" ? 0 : null };
1500
1500
  }
1501
- if (expression.kind === "column" || expression.kind === "wildcard") {
1501
+ if (expression.kind === "column") {
1502
+ return preservedColumns.has(expression.reference) ? structuredClone(expression) : { kind: "literal", value: null };
1503
+ }
1504
+ if (expression.kind === "wildcard") {
1502
1505
  return { kind: "literal", value: null };
1503
1506
  }
1504
- return mapChildExpressions(expression, emptyScalarExpression);
1507
+ return mapChildExpressions(expression, (child) => emptyScalarExpression(child, preservedColumns));
1505
1508
  }
1506
1509
  function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1507
1510
  const shape = scalarShape(inner);
@@ -1512,14 +1515,44 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1512
1515
  if (rows.limitWithTies === true && rows.orderBy.length === 0) {
1513
1516
  throw new TypeError("A correlated scalar WITH TIES query requires ORDER BY");
1514
1517
  }
1515
- const keys = extractCorrelation(rows, scope, "scalar", true);
1516
- const outerExpressions = keys.map((key) => structuredClone(key.outer));
1518
+ const expressionOutside = [];
1519
+ collectExpressionOutsideReferences(shape.expression, /* @__PURE__ */ new Set([rows.base.alias, ...rows.joins.map((join) => join.alias)]), expressionOutside);
1520
+ const probeLifted = expressionOutside.length > 0 || !canExtractCorrelation(rows, scope, "scalar", true);
1521
+ const references = probeLifted ? probeOuterReferences(rows, expressionOutside, scope, "scalar") : [];
1522
+ const keys = probeLifted ? takeCorrelationPredicates(rows, scope, true) : extractCorrelation(rows, scope, "scalar", true);
1523
+ const outerExpressions = probeLifted ? references.map((reference) => ({ kind: "column", reference })) : keys.map((key) => structuredClone(key.outer));
1524
+ const originalExpression = structuredClone(shape.expression);
1517
1525
  const probes = outerProbeBlockForExpressions(block, outerExpressions, nextAlias.resolvedNames);
1518
1526
  const probesAlias = nextAlias();
1519
1527
  const probeReferences = outerExpressions.map((_, index) => ({
1520
1528
  kind: "column",
1521
1529
  reference: `${probesAlias}.${correlationProbeAlias(index)}`
1522
1530
  }));
1531
+ if (probeLifted) {
1532
+ const replacements = new Map(references.map((reference, index) => [reference, probeReferences[index] ?? null]));
1533
+ replacePlanReferences(rows, replacements);
1534
+ shape.expression = replaceExpressionReferences(shape.expression, replacements);
1535
+ const source = { table: probesAlias, alias: probesAlias, derived: probes };
1536
+ const missingProbe = { kind: "literal", value: null };
1537
+ const keyProbes = keys.map((key) => {
1538
+ const reference = key.outer.kind === "column" ? key.outer.reference : "";
1539
+ return probeReferences[references.indexOf(reference)] ?? missingProbe;
1540
+ });
1541
+ const keysReadBase = keys.every((key) => {
1542
+ const parts = key.inner.kind === "column" ? key.inner.reference.split(".") : [];
1543
+ return parts.length === 1 || parts[0] === rows.base.alias;
1544
+ });
1545
+ if (keys.length > 0 && keysReadBase) {
1546
+ rows.joins.unshift(generalCorrelationJoin(probesAlias, probes, keys.map((key) => key.inner), keyProbes, keys.map((key) => key.operator)));
1547
+ } else {
1548
+ rows.joins.unshift(crossJoinPlan(source));
1549
+ rows.predicates.push(...keys.map((key, index) => ({
1550
+ left: key.inner,
1551
+ operator: key.operator,
1552
+ right: keyProbes[index] ?? missingProbe
1553
+ })));
1554
+ }
1555
+ }
1523
1556
  const finalRowsAlias = nextAlias();
1524
1557
  const projected = [];
1525
1558
  const project = (expression, alias) => {
@@ -1556,6 +1589,15 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1556
1589
  return rewritten;
1557
1590
  }
1558
1591
  if (expression.kind === "column" || expression.kind === "wildcard") {
1592
+ if (probeLifted && expression.kind === "column") {
1593
+ const index = probeReferences.findIndex((reference) => reference.kind === "column" && reference.reference === expression.reference);
1594
+ if (index >= 0) {
1595
+ return {
1596
+ kind: "column",
1597
+ reference: `${finalRowsAlias}.${correlationKeyAlias(index)}`
1598
+ };
1599
+ }
1600
+ }
1559
1601
  throw new TypeError("A correlated scalar aggregate cannot select an ungrouped inner column");
1560
1602
  }
1561
1603
  if (expression.kind === "subquery" || expression.kind === "exists") {
@@ -1575,18 +1617,21 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1575
1617
  base: rows.base,
1576
1618
  joins: rows.joins,
1577
1619
  select: [
1578
- ...keys.map((key, index) => ({
1579
- expression: key.inner,
1620
+ ...(probeLifted ? probeReferences : keys.map((key) => key.inner)).map((expression, index) => ({
1621
+ expression,
1580
1622
  alias: correlationKeyAlias(index)
1581
1623
  })),
1582
1624
  ...projected
1583
1625
  ],
1584
- predicates: [...rows.predicates, ...mirrorPredicatesThroughKeys(probes.predicates, keys)],
1626
+ predicates: [
1627
+ ...rows.predicates,
1628
+ ...probeLifted ? [] : mirrorPredicatesThroughKeys(probes.predicates, keys)
1629
+ ],
1585
1630
  groupBy: [],
1586
1631
  having: [],
1587
1632
  orderBy: []
1588
1633
  };
1589
- const matched = {
1634
+ const matched = probeLifted ? rawRows : {
1590
1635
  sql: "(correlated scalar matched probes)",
1591
1636
  base: { table: probesAlias, alias: probesAlias, derived: probes },
1592
1637
  joins: [
@@ -1623,7 +1668,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1623
1668
  {
1624
1669
  alias: correlationRowNumberAlias,
1625
1670
  name: rows.limitWithTies === true ? "RANK" : "ROW_NUMBER",
1626
- partitionAliases: keys.map((_, index) => correlationKeyAlias(index)),
1671
+ partitionAliases: outerExpressions.map((_, index) => correlationKeyAlias(index)),
1627
1672
  orderAliases: rows.orderBy.map((order, index) => ({
1628
1673
  alias: correlationOrderAlias(index),
1629
1674
  direction: order.direction,
@@ -1659,7 +1704,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1659
1704
  base: ranked,
1660
1705
  joins: [],
1661
1706
  select: [
1662
- ...keys.map((_, index) => ({
1707
+ ...outerExpressions.map((_, index) => ({
1663
1708
  expression: {
1664
1709
  kind: "column",
1665
1710
  reference: `${rankedAlias}.${correlationKeyAlias(index)}`
@@ -1679,7 +1724,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1679
1724
  ...rows.offsetParameter === void 0 ? {} : { offsetValidationParameters: [rows.offsetParameter] }
1680
1725
  };
1681
1726
  }
1682
- const finalProbeReferences = keys.map((_, index) => ({
1727
+ const finalProbeReferences = outerExpressions.map((_, index) => ({
1683
1728
  kind: "column",
1684
1729
  reference: `${finalRowsAlias}.${correlationKeyAlias(index)}`
1685
1730
  }));
@@ -1716,7 +1761,7 @@ function decorrelateGeneralScalar(block, inner, scope, nextAlias) {
1716
1761
  left: marker,
1717
1762
  right: { kind: "literal", value: null }
1718
1763
  },
1719
- then: grouped ? emptyScalarExpression(structuredClone(shape.expression)) : { kind: "literal", value: null }
1764
+ then: grouped ? emptyScalarExpression(originalExpression, new Set(references)) : { kind: "literal", value: null }
1720
1765
  }
1721
1766
  ],
1722
1767
  otherwise: {
@@ -1880,19 +1925,7 @@ function decorrelateExistsExpression(block, exists, scope, nextAlias) {
1880
1925
  }
1881
1926
  function decorrelateExistsWithProbes(block, exists, scope, nextAlias) {
1882
1927
  const inner = exists.block;
1883
- const outside = [];
1884
- collectOutsideReferences(inner, /* @__PURE__ */ new Set(), outside);
1885
- const references = [...new Set(outside)];
1886
- if (references.length === 0) {
1887
- throw new TypeError("Correlated EXISTS subquery has no usable outer reference");
1888
- }
1889
- for (const reference of references) {
1890
- const parts = reference.split(".");
1891
- const alias = parts.length === 2 ? parts[0] : void 0;
1892
- if (alias === void 0 || !scope.has(alias)) {
1893
- throw new TypeError(`Correlated EXISTS references an unavailable outer source: ${reference}`);
1894
- }
1895
- }
1928
+ const references = probeOuterReferences(inner, [], scope, "EXISTS");
1896
1929
  const outerExpressions = references.map((reference) => ({
1897
1930
  kind: "column",
1898
1931
  reference
@@ -1959,27 +1992,30 @@ function decorrelateExistsWithProbes(block, exists, scope, nextAlias) {
1959
1992
  right: { kind: "literal", value: null }
1960
1993
  };
1961
1994
  }
1962
- function replacePlanReferences(block, replacements) {
1963
- const rewrite = (expression) => {
1964
- if (expression.kind === "column") {
1965
- const replacement = replacements.get(expression.reference);
1966
- return replacement == null ? expression : structuredClone(replacement);
1995
+ function replaceExpressionReferences(expression, replacements) {
1996
+ const rewrite = (expression2) => {
1997
+ if (expression2.kind === "column") {
1998
+ const replacement = replacements.get(expression2.reference);
1999
+ return replacement == null ? expression2 : structuredClone(replacement);
1967
2000
  }
1968
- if (expression.kind === "subquery" || expression.kind === "exists") {
1969
- replacePlanReferences(expression.block, replacements);
1970
- return expression;
2001
+ if (expression2.kind === "subquery" || expression2.kind === "exists") {
2002
+ replacePlanReferences(expression2.block, replacements);
2003
+ return expression2;
1971
2004
  }
1972
- if (expression.kind === "window") {
1973
- expression.partitionBy = expression.partitionBy.map(rewrite);
1974
- for (const order of expression.orderBy)
2005
+ if (expression2.kind === "window") {
2006
+ expression2.partitionBy = expression2.partitionBy.map(rewrite);
2007
+ for (const order of expression2.orderBy)
1975
2008
  order.expression = rewrite(order.expression);
1976
- if (expression.argument !== void 0)
1977
- expression.argument = rewrite(expression.argument);
1978
- return expression;
2009
+ if (expression2.argument !== void 0)
2010
+ expression2.argument = rewrite(expression2.argument);
2011
+ return expression2;
1979
2012
  }
1980
- return mapChildExpressions(expression, rewrite);
2013
+ return mapChildExpressions(expression2, rewrite);
1981
2014
  };
1982
- mapBlockExpressions(block, rewrite);
2015
+ return rewrite(expression);
2016
+ }
2017
+ function replacePlanReferences(block, replacements) {
2018
+ mapBlockExpressions(block, (expression) => replaceExpressionReferences(expression, replacements));
1983
2019
  forEachNestedBlock(block, (nested) => replacePlanReferences(nested, replacements));
1984
2020
  }
1985
2021
  function outerProbeBlock(block, keys, resolvedNames) {
@@ -2166,30 +2202,62 @@ function blockReferencesOutside(block) {
2166
2202
  collectOutsideReferences(block, /* @__PURE__ */ new Set(), refs);
2167
2203
  return refs.length > 0;
2168
2204
  }
2169
- function collectOutsideReferences(block, outer, refs) {
2170
- const scope = /* @__PURE__ */ new Set([...outer, block.base.alias, ...block.joins.map((join) => join.alias)]);
2171
- const visit = (expression) => {
2172
- if (expression.kind === "column") {
2173
- const parts = expression.reference.split(".");
2205
+ function collectExpressionOutsideReferences(expression, scope, refs) {
2206
+ const visit = (expression2) => {
2207
+ if (expression2.kind === "column") {
2208
+ const parts = expression2.reference.split(".");
2174
2209
  const alias = parts[0];
2175
2210
  if (parts.length === 2 && alias !== void 0 && !scope.has(alias)) {
2176
- refs.push(expression.reference);
2211
+ refs.push(expression2.reference);
2177
2212
  }
2178
2213
  return;
2179
2214
  }
2180
- if (expression.kind === "subquery" || expression.kind === "exists") {
2181
- collectOutsideReferences(expression.block, scope, refs);
2215
+ if (expression2.kind === "subquery" || expression2.kind === "exists") {
2216
+ collectOutsideReferences(expression2.block, scope, refs);
2182
2217
  return;
2183
2218
  }
2184
- for (const child of childExpressions(expression))
2219
+ for (const child of childExpressions(expression2))
2185
2220
  visit(child);
2186
2221
  };
2187
- forEachBlockExpression(block, visit);
2222
+ visit(expression);
2223
+ }
2224
+ function collectOutsideReferences(block, outer, refs) {
2225
+ const scope = /* @__PURE__ */ new Set([...outer, block.base.alias, ...block.joins.map((join) => join.alias)]);
2226
+ forEachBlockExpression(block, (expression) => collectExpressionOutsideReferences(expression, scope, refs));
2188
2227
  forEachNestedBlock(block, (nested) => {
2189
2228
  collectOutsideReferences(nested, scope, refs);
2190
2229
  });
2191
2230
  }
2231
+ function probeOuterReferences(block, additional, scope, label) {
2232
+ const outside = [];
2233
+ collectOutsideReferences(block, /* @__PURE__ */ new Set(), outside);
2234
+ const references = [.../* @__PURE__ */ new Set([...outside, ...additional])];
2235
+ if (references.length === 0) {
2236
+ throw new TypeError(`Correlated ${label} subquery has no usable outer reference`);
2237
+ }
2238
+ for (const reference of references) {
2239
+ const parts = reference.split(".");
2240
+ const alias = parts.length === 2 ? parts[0] : void 0;
2241
+ if (alias === void 0 || !scope.has(alias)) {
2242
+ throw new TypeError(`Correlated ${label} references an unavailable outer source: ${reference}`);
2243
+ }
2244
+ }
2245
+ return references;
2246
+ }
2192
2247
  function extractCorrelation(inner, outerScope, label, comparisons = false) {
2248
+ const keys = takeCorrelationPredicates(inner, outerScope, comparisons);
2249
+ const leftover = [];
2250
+ collectOutsideReferences(inner, /* @__PURE__ */ new Set(), leftover);
2251
+ if (leftover.length > 0) {
2252
+ const supported = comparisons ? "comparison" : "equality";
2253
+ throw new TypeError(`Correlated ${label} subqueries support only ${supported} conditions between one inner and one outer qualified column (unsupported reference: ${leftover[0] ?? ""})`);
2254
+ }
2255
+ if (keys.length === 0) {
2256
+ throw new TypeError(`Correlated ${label} subquery has no usable correlation condition`);
2257
+ }
2258
+ return keys;
2259
+ }
2260
+ function takeCorrelationPredicates(inner, outerScope, comparisons) {
2193
2261
  const innerScope = /* @__PURE__ */ new Set([inner.base.alias, ...inner.joins.map((join) => join.alias)]);
2194
2262
  const keys = [];
2195
2263
  const kept = [];
@@ -2201,15 +2269,6 @@ function extractCorrelation(inner, outerScope, label, comparisons = false) {
2201
2269
  keys.push(key);
2202
2270
  }
2203
2271
  inner.predicates = kept;
2204
- const leftover = [];
2205
- collectOutsideReferences(inner, /* @__PURE__ */ new Set(), leftover);
2206
- if (leftover.length > 0) {
2207
- const supported = comparisons ? "comparison" : "equality";
2208
- throw new TypeError(`Correlated ${label} subqueries support only ${supported} conditions between one inner and one outer qualified column (unsupported reference: ${leftover[0] ?? ""})`);
2209
- }
2210
- if (keys.length === 0) {
2211
- throw new TypeError(`Correlated ${label} subquery has no usable correlation condition`);
2212
- }
2213
2272
  return keys;
2214
2273
  }
2215
2274
  function canExtractCorrelation(inner, outerScope, label, comparisons) {
@@ -1608,6 +1608,7 @@ function bindStatementParameters(statement, params) {
1608
1608
  if (clone.onConflict?.where !== void 0) {
1609
1609
  clone.onConflict.where = bindExpression(clone.onConflict.where, values);
1610
1610
  }
1611
+ bindReturningItems(clone.returningItems, values);
1611
1612
  return clone;
1612
1613
  }
1613
1614
  if (clone.kind === "drop-table" || clone.kind === "create-view" || clone.kind === "drop-view" || clone.kind === "transaction") {
@@ -1643,8 +1644,15 @@ function bindStatementParameters(statement, params) {
1643
1644
  predicate.left = bindExpression(predicate.left, values);
1644
1645
  predicate.right = bindExpression(predicate.right, values);
1645
1646
  }
1647
+ bindReturningItems(clone.returningItems, values);
1646
1648
  return clone;
1647
1649
  }
1650
+ function bindReturningItems(items, values) {
1651
+ if (items === void 0)
1652
+ return;
1653
+ for (const item of items)
1654
+ item.expression = bindExpression(item.expression, values);
1655
+ }
1648
1656
  function inferBlockSchema(plan, schemas) {
1649
1657
  const sources = [plan.base, ...plan.joins];
1650
1658
  const multipleSources = sources.filter((source) => {
@@ -3333,7 +3333,7 @@ class IndexedDbBlockStore {
3333
3333
  const indexKey = ftsChunkIndexKey(ftsEntry.tableId, column.columnId);
3334
3334
  const rawChunkIndex = await requestResult(catalog.get(indexKey));
3335
3335
  const chunkIndex = decodeFtsDeltaIndex(rawChunkIndex);
3336
- if (rawChunkIndex !== void 0 && chunkIndex === void 0 || (chunkIndex?.versions.length ?? 0) >= MAX_FTS_DELTA_CHUNKS) {
3336
+ if (rawChunkIndex !== void 0 && chunkIndex === void 0 || postings.length > 0 && (chunkIndex?.versions.length ?? 0) >= MAX_FTS_DELTA_CHUNKS) {
3337
3337
  for (const version of chunkIndex?.versions ?? []) {
3338
3338
  catalog.delete(ftsChunkKey(ftsEntry.tableId, column.columnId, version));
3339
3339
  }
@@ -3356,6 +3356,14 @@ class IndexedDbBlockStore {
3356
3356
  });
3357
3357
  continue;
3358
3358
  }
3359
+ if (postings.length === 0) {
3360
+ ftsDeltaCounts.push({
3361
+ tableId: ftsEntry.tableId,
3362
+ columnId: column.columnId,
3363
+ count: chunkIndex?.versions.length ?? 0
3364
+ });
3365
+ continue;
3366
+ }
3359
3367
  catalog.put(structuredClone({
3360
3368
  postings,
3361
3369
  totalTokens: column.totalTokens
@@ -1655,6 +1655,8 @@ class RecordCore {
1655
1655
  const active = activePostingStorageColumnIds(currentTable);
1656
1656
  if (!active.has(column.columnId))
1657
1657
  continue;
1658
+ if (column.postings.length === 0)
1659
+ continue;
1658
1660
  const key = `${changes.tableId}/${column.columnId}`;
1659
1661
  const deltas = this.#ftsDeltas.get(key) ?? /* @__PURE__ */ new Map();
1660
1662
  if (!deltas.has(version) && deltas.size >= MAX_FTS_DELTA_CHUNKS) {
@@ -4330,8 +4332,11 @@ class RecordCore {
4330
4332
  this.#nextAutoIncrement.set(key, next);
4331
4333
  for (const [key, base] of cloned.ftsBases)
4332
4334
  this.#ftsBases.set(key, base);
4333
- for (const [key, deltas] of cloned.ftsDeltas)
4334
- this.#ftsDeltas.set(key, new Map(deltas));
4335
+ for (const [key, deltas] of cloned.ftsDeltas) {
4336
+ const retained = deltas.filter(([, delta]) => delta.postings.length > 0);
4337
+ if (retained.length > 0)
4338
+ this.#ftsDeltas.set(key, new Map(retained));
4339
+ }
4335
4340
  for (const [tableId, tokens] of cloned.uniqueKeys) {
4336
4341
  this.#uniqueKeys.set(tableId, new OrderedStringSet(tokens));
4337
4342
  }
@@ -5425,7 +5430,14 @@ function validateRecordCoreState(state, physical) {
5425
5430
  throw new TypeError(`Full-text delta ${key} repeats a version`);
5426
5431
  versions.add(version);
5427
5432
  whole(delta.totalTokens, `Full-text delta ${key} token count`);
5428
- if (validateFtsPostingChunks([delta.postings], `Full-text delta ${key}`) !== delta.totalTokens) {
5433
+ if (!Array.isArray(delta.postings)) {
5434
+ throw new TypeError(`Full-text delta ${key} postings must be an array`);
5435
+ }
5436
+ if (delta.postings.length === 0) {
5437
+ if (delta.totalTokens !== 0) {
5438
+ throw new TypeError(`Empty full-text delta ${key} must have a zero token total`);
5439
+ }
5440
+ } else if (validateFtsPostingChunks([delta.postings], `Full-text delta ${key}`) !== delta.totalTokens) {
5429
5441
  throw new TypeError(`Full-text delta ${key} token total does not match its postings`);
5430
5442
  }
5431
5443
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnowdb/core",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "A columnar SQL database for the browser: PostgreSQL-style SQL over durable IndexedDB or OPFS data, with no server or WebAssembly module.",
5
5
  "license": "MIT",
6
6
  "author": "Eric Wilhite",
@@ -420,8 +420,8 @@
420
420
  {
421
421
  "id": "subquery.correlated-select",
422
422
  "status": "supported",
423
- "example": "SELECT r.region, (SELECT AVG(q.amount) FROM rows q WHERE q.region = r.region) AS regional FROM rows r",
424
- "notes": "Correlated scalar aggregates and single-row projections decorrelate in the select list. In a grouped query, their outer references must be GROUP BY columns and the scalar cannot sit inside an outer aggregate."
423
+ "example": "SELECT r.region, (SELECT MAX(q.amount + r.amount) FROM rows q WHERE q.region = r.region) AS regional FROM rows r",
424
+ "notes": "Correlated scalar aggregates and single-row projections decorrelate in the select list. Every qualified outer column used by the inner predicates or projection becomes part of the distinct probe tuple. In a grouped query, outer references must be GROUP BY columns and the scalar cannot sit inside an outer aggregate."
425
425
  },
426
426
  {
427
427
  "id": "subquery.correlated-select-limit",