bigal 15.11.8 → 15.11.10

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/.oxlintrc.json CHANGED
@@ -16,6 +16,7 @@
16
16
  "rules": {
17
17
  "eqeqeq": ["error", "smart"],
18
18
  "no-console": "error",
19
+ "no-underscore-dangle": "off",
19
20
  "no-nested-ternary": "error",
20
21
  "no-await-in-loop": "error",
21
22
  "id-length": ["error", { "min": 2, "exceptions": ["e", "i", "j", "q", "x", "y", "_", "A", "D", "K", "P", "T", "U"] }],
package/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
+ ## [15.11.10](https://github.com/bigalorm/bigal/compare/v15.11.9...v15.11.10) (2026-06-09)
2
+
3
+ ### Bug Fixes
4
+
5
+ * qualify base-table columns when a query has a join ([#435](https://github.com/bigalorm/bigal/issues/435)) ([667e1f3](https://github.com/bigalorm/bigal/commit/667e1f3d427f73713e30bb7bf18f5f7e61acd206))
6
+
7
+ ## [15.11.9](https://github.com/bigalorm/bigal/compare/v15.11.8...v15.11.9) (2026-05-19)
8
+
1
9
  ## [15.11.8](https://github.com/bigalorm/bigal/compare/v15.11.7...v15.11.8) (2026-05-19)
2
10
 
3
11
  ### Bug Fixes
4
12
 
5
- * prevent propertyName leak across where-clause iteration ([#403](https://github.com/bigalorm/bigal/issues/403)) ([0454e84](https://github.com/bigalorm/bigal/commit/0454e84d4c6fdf46fc14bdcf57ab409619ea8a03)), closes [#393](https://github.com/bigalorm/bigal/issues/393)
13
+ - prevent propertyName leak across where-clause iteration ([#403](https://github.com/bigalorm/bigal/issues/403)) ([0454e84](https://github.com/bigalorm/bigal/commit/0454e84d4c6fdf46fc14bdcf57ab409619ea8a03)), closes [#393](https://github.com/bigalorm/bigal/issues/393)
6
14
 
7
15
  ## [15.11.7](https://github.com/bigalorm/bigal/compare/v15.11.6...v15.11.7) (2026-04-26)
8
16
 
package/dist/index.cjs CHANGED
@@ -557,6 +557,9 @@ function assertValidSqlIdentifier(value, context) {
557
557
  throw new Error(`Invalid SQL identifier for ${context}: "${value}". Identifiers must start with a letter or underscore and contain only letters, numbers, underscores, and dots.`);
558
558
  }
559
559
  }
560
+ function getBaseColumnPrefix(model, joins) {
561
+ return joins?.length ? `${model.qualifiedTableName}.` : "";
562
+ }
560
563
  function getSelectQueryAndParams({
561
564
  repositoriesByModelNameLowered,
562
565
  model,
@@ -599,18 +602,20 @@ function getSelectQueryAndParams({
599
602
  }
600
603
  let query = "SELECT ";
601
604
  if (distinctOn?.length) {
605
+ const baseColumnPrefix = getBaseColumnPrefix(model, joins);
602
606
  const distinctOnColumnNames = distinctOn.map((propertyName) => {
603
607
  const column = model.columnsByPropertyName[propertyName];
604
608
  if (!column) {
605
609
  throw new QueryError(`Unable to find column for DISTINCT ON property: ${propertyName} on ${model.tableName}`, model);
606
610
  }
607
- return `"${column.name}"`;
611
+ return `${baseColumnPrefix}"${column.name}"`;
608
612
  });
609
613
  query += `DISTINCT ON (${distinctOnColumnNames.join(",")}) `;
610
614
  }
611
615
  query += getColumnsToSelect({
612
616
  model,
613
- select
617
+ select,
618
+ joins
614
619
  });
615
620
  if (includeCount) {
616
621
  query += ',count(*) OVER() AS "__total_count__"';
@@ -1000,7 +1005,8 @@ function getDeleteQueryAndParams({
1000
1005
  }
1001
1006
  function getColumnsToSelect({
1002
1007
  model,
1003
- select
1008
+ select,
1009
+ joins
1004
1010
  }) {
1005
1011
  let selectColumns;
1006
1012
  if (select) {
@@ -1017,6 +1023,7 @@ function getColumnsToSelect({
1017
1023
  }
1018
1024
  }
1019
1025
  }
1026
+ const baseColumnPrefix = getBaseColumnPrefix(model, joins);
1020
1027
  let query = "";
1021
1028
  for (const [index, propertyName] of Array.from(selectColumns).entries()) {
1022
1029
  const column = model.columnsByPropertyName[propertyName];
@@ -1027,9 +1034,9 @@ function getColumnsToSelect({
1027
1034
  query += ",";
1028
1035
  }
1029
1036
  if (column.name === propertyName) {
1030
- query += `"${propertyName}"`;
1037
+ query += `${baseColumnPrefix}"${propertyName}"`;
1031
1038
  } else {
1032
- query += `"${column.name}" AS "${propertyName}"`;
1039
+ query += `${baseColumnPrefix}"${column.name}" AS "${propertyName}"`;
1033
1040
  }
1034
1041
  }
1035
1042
  return query;
@@ -1122,7 +1129,7 @@ function buildSubqueryJoinClause({
1122
1129
  throw new QueryError(`Unable to find property "${mainColumn}" on model "${model.name}" for subquery join`, model);
1123
1130
  }
1124
1131
  assertValidSqlIdentifier(subqueryColumn, "subquery join ON column");
1125
- onConditions.push(`"${model.tableName}"."${column.name}"="${join.alias}"."${subqueryColumn}"`);
1132
+ onConditions.push(`${model.qualifiedTableName}."${column.name}"="${join.alias}"."${subqueryColumn}"`);
1126
1133
  }
1127
1134
  return ` ${joinType} (${subquerySQL}) AS "${join.alias}" ON ${onConditions.join(" AND ")}`;
1128
1135
  }
@@ -1323,7 +1330,7 @@ function buildOrderStatement({
1323
1330
  if (!column) {
1324
1331
  throw new QueryError(`Property (${propertyName}) not found in model (${model.name}).`, model);
1325
1332
  }
1326
- orderStatement += `"${column.name}"`;
1333
+ orderStatement += `${getBaseColumnPrefix(model, joins)}"${column.name}"`;
1327
1334
  }
1328
1335
  if (descending) {
1329
1336
  orderStatement += " DESC";
@@ -1498,7 +1505,7 @@ function buildWhere({
1498
1505
  params,
1499
1506
  repositoriesByModelNameLowered
1500
1507
  });
1501
- return `"${column.name}"${isNegated ? " NOT" : ""} IN (${subquerySQL})`;
1508
+ return `${getBaseColumnPrefix(model, joins)}"${column.name}"${isNegated ? " NOT" : ""} IN (${subquerySQL})`;
1502
1509
  }
1503
1510
  throw new QueryError(`Expected subquery value for 'in' operator. Property (${propertyName ?? ""}) in model (${model.name}).`, model);
1504
1511
  case "exists":
@@ -1575,7 +1582,7 @@ function buildWhere({
1575
1582
  if (arrayColumn) {
1576
1583
  const arrayColumnType = arrayColumn.type ? arrayColumn.type.toLowerCase() : "";
1577
1584
  if (arrayColumnType === "array" || arrayColumnType === "string[]" || arrayColumnType === "integer[]" || arrayColumnType === "float[]" || arrayColumnType === "boolean[]") {
1578
- return `"${arrayColumn.name}"${isNegated ? "<>" : "="}'{}'`;
1585
+ return `${getBaseColumnPrefix(model, joins)}"${arrayColumn.name}"${isNegated ? "<>" : "="}'{}'`;
1579
1586
  }
1580
1587
  }
1581
1588
  if (isNegated) {
@@ -1685,7 +1692,7 @@ function buildWhere({
1685
1692
  break;
1686
1693
  }
1687
1694
  params.push(valueWithoutNull);
1688
- orConstraints.push(`"${columnType.name}"${isNegated ? "<>ALL" : "=ANY"}($${params.length}${castType})`);
1695
+ orConstraints.push(`${getBaseColumnPrefix(model, joins)}"${columnType.name}"${isNegated ? "<>ALL" : "=ANY"}($${params.length}${castType})`);
1689
1696
  }
1690
1697
  }
1691
1698
  }
@@ -1746,6 +1753,7 @@ function buildWhere({
1746
1753
  if (parentColumn?.type?.toLowerCase() === "json") {
1747
1754
  andValues.push(
1748
1755
  buildJsonPropertyClause({
1756
+ tablePrefix: getBaseColumnPrefix(model, joins),
1749
1757
  columnName: parentColumn.name,
1750
1758
  path: [key],
1751
1759
  isNegated,
@@ -1954,7 +1962,7 @@ function buildArrayOrSingleStatement({
1954
1962
  throw new QueryError(`Unable to find property ${propertyName} on model ${model.name}`, model);
1955
1963
  }
1956
1964
  column = localColumn;
1957
- tablePrefix = "";
1965
+ tablePrefix = getBaseColumnPrefix(model, joins);
1958
1966
  }
1959
1967
  if (value === null) {
1960
1968
  return `${tablePrefix}"${column.name}" ${isNegated ? "IS NOT" : "IS"} NULL`;
@@ -2128,8 +2136,8 @@ function getTypeCastSuffix(value) {
2128
2136
  }
2129
2137
  return "";
2130
2138
  }
2131
- function buildJsonAccessor(columnName, path) {
2132
- let result = `"${columnName}"`;
2139
+ function buildJsonAccessor(tablePrefix, columnName, path) {
2140
+ let result = `${tablePrefix}"${columnName}"`;
2133
2141
  for (let i = 0; i < path.length; i++) {
2134
2142
  const arrow = i === path.length - 1 ? "->>" : "->";
2135
2143
  result += `${arrow}'${path[i]}'`;
@@ -2140,6 +2148,7 @@ function isJsonConstraintOperator(key) {
2140
2148
  return key === "!" || key === "<" || key === "<=" || key === ">" || key === ">=";
2141
2149
  }
2142
2150
  function buildJsonPropertyClause({
2151
+ tablePrefix,
2143
2152
  columnName,
2144
2153
  path,
2145
2154
  isNegated,
@@ -2150,10 +2159,10 @@ function buildJsonPropertyClause({
2150
2159
  assertValidSqlIdentifier(segment, `JSON property name "${segment}"`);
2151
2160
  }
2152
2161
  if (constraint === null) {
2153
- return `${buildJsonAccessor(columnName, path)} ${isNegated ? "IS NOT" : "IS"} NULL`;
2162
+ return `${buildJsonAccessor(tablePrefix, columnName, path)} ${isNegated ? "IS NOT" : "IS"} NULL`;
2154
2163
  }
2155
2164
  if (Array.isArray(constraint)) {
2156
- const accessor2 = buildJsonAccessor(columnName, path);
2165
+ const accessor2 = buildJsonAccessor(tablePrefix, columnName, path);
2157
2166
  params.push(constraint);
2158
2167
  return `${accessor2}${isNegated ? "<>ALL" : "=ANY"}($${params.length})`;
2159
2168
  }
@@ -2161,7 +2170,7 @@ function buildJsonPropertyClause({
2161
2170
  const entries = Object.entries(constraint);
2162
2171
  const firstKey = entries[0]?.[0];
2163
2172
  if (firstKey && isJsonConstraintOperator(firstKey)) {
2164
- const accessor2 = buildJsonAccessor(columnName, path);
2173
+ const accessor2 = buildJsonAccessor(tablePrefix, columnName, path);
2165
2174
  const negatedComparisonOperators = { "<": ">=", "<=": ">", ">": "<=", ">=": "<" };
2166
2175
  const clauses2 = [];
2167
2176
  for (const [operator, operatorValue] of entries) {
@@ -2188,6 +2197,7 @@ function buildJsonPropertyClause({
2188
2197
  for (const [nestedKey, nestedValue] of entries) {
2189
2198
  clauses.push(
2190
2199
  buildJsonPropertyClause({
2200
+ tablePrefix,
2191
2201
  columnName,
2192
2202
  path: [...path, nestedKey],
2193
2203
  isNegated,
@@ -2198,7 +2208,7 @@ function buildJsonPropertyClause({
2198
2208
  }
2199
2209
  return clauses.join(" AND ");
2200
2210
  }
2201
- const accessor = buildJsonAccessor(columnName, path);
2211
+ const accessor = buildJsonAccessor(tablePrefix, columnName, path);
2202
2212
  params.push(constraint);
2203
2213
  const castSuffix = getTypeCastSuffix(constraint);
2204
2214
  const castAccessor = castSuffix ? `(${accessor})${castSuffix}` : accessor;
@@ -2262,7 +2272,7 @@ function buildComparisonOperatorStatement({
2262
2272
  throw new QueryError(`Unable to find property ${propertyName} on model ${model.name}`, model);
2263
2273
  }
2264
2274
  column = localColumn;
2265
- tablePrefix = "";
2275
+ tablePrefix = getBaseColumnPrefix(model, joins);
2266
2276
  }
2267
2277
  if (value === null) {
2268
2278
  return `${tablePrefix}"${column.name}" ${isNegated ? "IS NOT" : "IS"} NULL`;
package/dist/index.mjs CHANGED
@@ -555,6 +555,9 @@ function assertValidSqlIdentifier(value, context) {
555
555
  throw new Error(`Invalid SQL identifier for ${context}: "${value}". Identifiers must start with a letter or underscore and contain only letters, numbers, underscores, and dots.`);
556
556
  }
557
557
  }
558
+ function getBaseColumnPrefix(model, joins) {
559
+ return joins?.length ? `${model.qualifiedTableName}.` : "";
560
+ }
558
561
  function getSelectQueryAndParams({
559
562
  repositoriesByModelNameLowered,
560
563
  model,
@@ -597,18 +600,20 @@ function getSelectQueryAndParams({
597
600
  }
598
601
  let query = "SELECT ";
599
602
  if (distinctOn?.length) {
603
+ const baseColumnPrefix = getBaseColumnPrefix(model, joins);
600
604
  const distinctOnColumnNames = distinctOn.map((propertyName) => {
601
605
  const column = model.columnsByPropertyName[propertyName];
602
606
  if (!column) {
603
607
  throw new QueryError(`Unable to find column for DISTINCT ON property: ${propertyName} on ${model.tableName}`, model);
604
608
  }
605
- return `"${column.name}"`;
609
+ return `${baseColumnPrefix}"${column.name}"`;
606
610
  });
607
611
  query += `DISTINCT ON (${distinctOnColumnNames.join(",")}) `;
608
612
  }
609
613
  query += getColumnsToSelect({
610
614
  model,
611
- select
615
+ select,
616
+ joins
612
617
  });
613
618
  if (includeCount) {
614
619
  query += ',count(*) OVER() AS "__total_count__"';
@@ -998,7 +1003,8 @@ function getDeleteQueryAndParams({
998
1003
  }
999
1004
  function getColumnsToSelect({
1000
1005
  model,
1001
- select
1006
+ select,
1007
+ joins
1002
1008
  }) {
1003
1009
  let selectColumns;
1004
1010
  if (select) {
@@ -1015,6 +1021,7 @@ function getColumnsToSelect({
1015
1021
  }
1016
1022
  }
1017
1023
  }
1024
+ const baseColumnPrefix = getBaseColumnPrefix(model, joins);
1018
1025
  let query = "";
1019
1026
  for (const [index, propertyName] of Array.from(selectColumns).entries()) {
1020
1027
  const column = model.columnsByPropertyName[propertyName];
@@ -1025,9 +1032,9 @@ function getColumnsToSelect({
1025
1032
  query += ",";
1026
1033
  }
1027
1034
  if (column.name === propertyName) {
1028
- query += `"${propertyName}"`;
1035
+ query += `${baseColumnPrefix}"${propertyName}"`;
1029
1036
  } else {
1030
- query += `"${column.name}" AS "${propertyName}"`;
1037
+ query += `${baseColumnPrefix}"${column.name}" AS "${propertyName}"`;
1031
1038
  }
1032
1039
  }
1033
1040
  return query;
@@ -1120,7 +1127,7 @@ function buildSubqueryJoinClause({
1120
1127
  throw new QueryError(`Unable to find property "${mainColumn}" on model "${model.name}" for subquery join`, model);
1121
1128
  }
1122
1129
  assertValidSqlIdentifier(subqueryColumn, "subquery join ON column");
1123
- onConditions.push(`"${model.tableName}"."${column.name}"="${join.alias}"."${subqueryColumn}"`);
1130
+ onConditions.push(`${model.qualifiedTableName}."${column.name}"="${join.alias}"."${subqueryColumn}"`);
1124
1131
  }
1125
1132
  return ` ${joinType} (${subquerySQL}) AS "${join.alias}" ON ${onConditions.join(" AND ")}`;
1126
1133
  }
@@ -1321,7 +1328,7 @@ function buildOrderStatement({
1321
1328
  if (!column) {
1322
1329
  throw new QueryError(`Property (${propertyName}) not found in model (${model.name}).`, model);
1323
1330
  }
1324
- orderStatement += `"${column.name}"`;
1331
+ orderStatement += `${getBaseColumnPrefix(model, joins)}"${column.name}"`;
1325
1332
  }
1326
1333
  if (descending) {
1327
1334
  orderStatement += " DESC";
@@ -1496,7 +1503,7 @@ function buildWhere({
1496
1503
  params,
1497
1504
  repositoriesByModelNameLowered
1498
1505
  });
1499
- return `"${column.name}"${isNegated ? " NOT" : ""} IN (${subquerySQL})`;
1506
+ return `${getBaseColumnPrefix(model, joins)}"${column.name}"${isNegated ? " NOT" : ""} IN (${subquerySQL})`;
1500
1507
  }
1501
1508
  throw new QueryError(`Expected subquery value for 'in' operator. Property (${propertyName ?? ""}) in model (${model.name}).`, model);
1502
1509
  case "exists":
@@ -1573,7 +1580,7 @@ function buildWhere({
1573
1580
  if (arrayColumn) {
1574
1581
  const arrayColumnType = arrayColumn.type ? arrayColumn.type.toLowerCase() : "";
1575
1582
  if (arrayColumnType === "array" || arrayColumnType === "string[]" || arrayColumnType === "integer[]" || arrayColumnType === "float[]" || arrayColumnType === "boolean[]") {
1576
- return `"${arrayColumn.name}"${isNegated ? "<>" : "="}'{}'`;
1583
+ return `${getBaseColumnPrefix(model, joins)}"${arrayColumn.name}"${isNegated ? "<>" : "="}'{}'`;
1577
1584
  }
1578
1585
  }
1579
1586
  if (isNegated) {
@@ -1683,7 +1690,7 @@ function buildWhere({
1683
1690
  break;
1684
1691
  }
1685
1692
  params.push(valueWithoutNull);
1686
- orConstraints.push(`"${columnType.name}"${isNegated ? "<>ALL" : "=ANY"}($${params.length}${castType})`);
1693
+ orConstraints.push(`${getBaseColumnPrefix(model, joins)}"${columnType.name}"${isNegated ? "<>ALL" : "=ANY"}($${params.length}${castType})`);
1687
1694
  }
1688
1695
  }
1689
1696
  }
@@ -1744,6 +1751,7 @@ function buildWhere({
1744
1751
  if (parentColumn?.type?.toLowerCase() === "json") {
1745
1752
  andValues.push(
1746
1753
  buildJsonPropertyClause({
1754
+ tablePrefix: getBaseColumnPrefix(model, joins),
1747
1755
  columnName: parentColumn.name,
1748
1756
  path: [key],
1749
1757
  isNegated,
@@ -1952,7 +1960,7 @@ function buildArrayOrSingleStatement({
1952
1960
  throw new QueryError(`Unable to find property ${propertyName} on model ${model.name}`, model);
1953
1961
  }
1954
1962
  column = localColumn;
1955
- tablePrefix = "";
1963
+ tablePrefix = getBaseColumnPrefix(model, joins);
1956
1964
  }
1957
1965
  if (value === null) {
1958
1966
  return `${tablePrefix}"${column.name}" ${isNegated ? "IS NOT" : "IS"} NULL`;
@@ -2126,8 +2134,8 @@ function getTypeCastSuffix(value) {
2126
2134
  }
2127
2135
  return "";
2128
2136
  }
2129
- function buildJsonAccessor(columnName, path) {
2130
- let result = `"${columnName}"`;
2137
+ function buildJsonAccessor(tablePrefix, columnName, path) {
2138
+ let result = `${tablePrefix}"${columnName}"`;
2131
2139
  for (let i = 0; i < path.length; i++) {
2132
2140
  const arrow = i === path.length - 1 ? "->>" : "->";
2133
2141
  result += `${arrow}'${path[i]}'`;
@@ -2138,6 +2146,7 @@ function isJsonConstraintOperator(key) {
2138
2146
  return key === "!" || key === "<" || key === "<=" || key === ">" || key === ">=";
2139
2147
  }
2140
2148
  function buildJsonPropertyClause({
2149
+ tablePrefix,
2141
2150
  columnName,
2142
2151
  path,
2143
2152
  isNegated,
@@ -2148,10 +2157,10 @@ function buildJsonPropertyClause({
2148
2157
  assertValidSqlIdentifier(segment, `JSON property name "${segment}"`);
2149
2158
  }
2150
2159
  if (constraint === null) {
2151
- return `${buildJsonAccessor(columnName, path)} ${isNegated ? "IS NOT" : "IS"} NULL`;
2160
+ return `${buildJsonAccessor(tablePrefix, columnName, path)} ${isNegated ? "IS NOT" : "IS"} NULL`;
2152
2161
  }
2153
2162
  if (Array.isArray(constraint)) {
2154
- const accessor2 = buildJsonAccessor(columnName, path);
2163
+ const accessor2 = buildJsonAccessor(tablePrefix, columnName, path);
2155
2164
  params.push(constraint);
2156
2165
  return `${accessor2}${isNegated ? "<>ALL" : "=ANY"}($${params.length})`;
2157
2166
  }
@@ -2159,7 +2168,7 @@ function buildJsonPropertyClause({
2159
2168
  const entries = Object.entries(constraint);
2160
2169
  const firstKey = entries[0]?.[0];
2161
2170
  if (firstKey && isJsonConstraintOperator(firstKey)) {
2162
- const accessor2 = buildJsonAccessor(columnName, path);
2171
+ const accessor2 = buildJsonAccessor(tablePrefix, columnName, path);
2163
2172
  const negatedComparisonOperators = { "<": ">=", "<=": ">", ">": "<=", ">=": "<" };
2164
2173
  const clauses2 = [];
2165
2174
  for (const [operator, operatorValue] of entries) {
@@ -2186,6 +2195,7 @@ function buildJsonPropertyClause({
2186
2195
  for (const [nestedKey, nestedValue] of entries) {
2187
2196
  clauses.push(
2188
2197
  buildJsonPropertyClause({
2198
+ tablePrefix,
2189
2199
  columnName,
2190
2200
  path: [...path, nestedKey],
2191
2201
  isNegated,
@@ -2196,7 +2206,7 @@ function buildJsonPropertyClause({
2196
2206
  }
2197
2207
  return clauses.join(" AND ");
2198
2208
  }
2199
- const accessor = buildJsonAccessor(columnName, path);
2209
+ const accessor = buildJsonAccessor(tablePrefix, columnName, path);
2200
2210
  params.push(constraint);
2201
2211
  const castSuffix = getTypeCastSuffix(constraint);
2202
2212
  const castAccessor = castSuffix ? `(${accessor})${castSuffix}` : accessor;
@@ -2260,7 +2270,7 @@ function buildComparisonOperatorStatement({
2260
2270
  throw new QueryError(`Unable to find property ${propertyName} on model ${model.name}`, model);
2261
2271
  }
2262
2272
  column = localColumn;
2263
- tablePrefix = "";
2273
+ tablePrefix = getBaseColumnPrefix(model, joins);
2264
2274
  }
2265
2275
  if (value === null) {
2266
2276
  return `${tablePrefix}"${column.name}" ${isNegated ? "IS NOT" : "IS"} NULL`;
@@ -96,9 +96,9 @@ const products = await productRepository
96
96
  .find()
97
97
  .join('store')
98
98
  .where({ store: { name: 'Acme' } });
99
- // SQL: SELECT "products".* FROM "products"
100
- // INNER JOIN "stores" ON "products"."store_id"="stores"."id"
101
- // WHERE "stores"."name"=$1
99
+ // SQL: SELECT "products"."id","products"."name",… FROM "products"
100
+ // INNER JOIN "stores" AS "store" ON "products"."store_id"="store"."id"
101
+ // WHERE "store"."name"=$1
102
102
 
103
103
  // Left join
104
104
  const products = await productRepository
@@ -116,6 +116,9 @@ const products = await productRepository
116
116
  const products = await productRepository.find().leftJoin('store', 'activeStore', { isActive: true });
117
117
  ```
118
118
 
119
+ When a query includes any join, BigAl automatically qualifies the base table's own columns (in `SELECT`, `WHERE`, `ORDER BY`, and `DISTINCT ON`) with the base table name.
120
+ This keeps columns that share a name across the base and joined tables (most commonly `id`) from being ambiguous, so `.join()` is safe to use without falling back to raw SQL.
121
+
119
122
  ## Subquery joins
120
123
 
121
124
  Join to subquery results:
@@ -127,7 +130,7 @@ const productCounts = subquery(productRepository)
127
130
 
128
131
  // Inner join
129
132
  const stores = await storeRepository.find().join(productCounts, 'stats', { on: { id: 'store' } });
130
- // SQL: SELECT "stores".* FROM "stores"
133
+ // SQL: SELECT "stores"."id","stores"."name" FROM "stores"
131
134
  // INNER JOIN (
132
135
  // SELECT "store_id" AS "store", COUNT(*) AS "productCount"
133
136
  // FROM "products" GROUP BY "store_id"