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 +1 -0
- package/CHANGELOG.md +9 -1
- package/dist/index.cjs +28 -18
- package/dist/index.mjs +28 -18
- package/docs/guide/subqueries-and-joins.md +7 -4
- package/docs/package-lock.json +320 -300
- package/docs/package.json +1 -1
- package/package.json +17 -12
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
|
-
|
|
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
|
|
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 +=
|
|
1037
|
+
query += `${baseColumnPrefix}"${propertyName}"`;
|
|
1031
1038
|
} else {
|
|
1032
|
-
query +=
|
|
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(
|
|
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 +=
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 +=
|
|
1035
|
+
query += `${baseColumnPrefix}"${propertyName}"`;
|
|
1029
1036
|
} else {
|
|
1030
|
-
query +=
|
|
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(
|
|
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 +=
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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"
|
|
100
|
-
// INNER JOIN "stores" ON "products"."store_id"="
|
|
101
|
-
// WHERE "
|
|
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"
|
|
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"
|