Skip to content

Commit 6c918ea

Browse files
authored
fix: resolve issue queryBuilder makes different parameter identifiers for same parameter (#10327)
Closes: #7308
1 parent bfc1cc5 commit 6c918ea

File tree

9 files changed

+146
-6
lines changed

9 files changed

+146
-6
lines changed

src/driver/Driver.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ export interface Driver {
112112
*/
113113
mappedDataTypes: MappedColumnTypes
114114

115+
/**
116+
* The prefix used for the parameters
117+
*/
118+
parametersPrefix?: string
119+
115120
/**
116121
* Max length allowed by the DBMS for aliases (execution of queries).
117122
*/

src/driver/cockroachdb/CockroachDriver.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,11 @@ export class CockroachDriver implements Driver {
224224
metadataValue: "string",
225225
}
226226

227+
/**
228+
* The prefix used for the parameters
229+
*/
230+
parametersPrefix: string = "$"
231+
227232
/**
228233
* Default values of length, precision and scale depends on column data type.
229234
* Used in the cases when length/precision/scale is not specified by user.
@@ -509,13 +514,18 @@ export class CockroachDriver implements Driver {
509514
if (!parameters || !Object.keys(parameters).length)
510515
return [sql, escapedParameters]
511516

517+
const parameterIndexMap = new Map<string, number>()
512518
sql = sql.replace(
513519
/:(\.\.\.)?([A-Za-z0-9_.]+)/g,
514520
(full, isArray: string, key: string): string => {
515521
if (!parameters.hasOwnProperty(key)) {
516522
return full
517523
}
518524

525+
if (parameterIndexMap.has(key)) {
526+
return this.parametersPrefix + parameterIndexMap.get(key)
527+
}
528+
519529
let value: any = parameters[key]
520530

521531
if (isArray) {
@@ -535,6 +545,7 @@ export class CockroachDriver implements Driver {
535545
}
536546

537547
escapedParameters.push(value)
548+
parameterIndexMap.set(key, escapedParameters.length)
538549
return this.createParameter(key, escapedParameters.length - 1)
539550
},
540551
) // todo: make replace only in value statements, otherwise problems
@@ -961,7 +972,7 @@ export class CockroachDriver implements Driver {
961972
* Creates an escaped parameter.
962973
*/
963974
createParameter(parameterName: string, index: number): string {
964-
return "$" + (index + 1)
975+
return this.parametersPrefix + (index + 1)
965976
}
966977

967978
// -------------------------------------------------------------------------

src/driver/oracle/OracleDriver.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ export class OracleDriver implements Driver {
215215
metadataValue: "clob",
216216
}
217217

218+
/**
219+
* The prefix used for the parameters
220+
*/
221+
parametersPrefix: string = ":"
222+
218223
/**
219224
* Default values of length, precision and scale depends on column data type.
220225
* Used in the cases when length/precision/scale is not specified by user.
@@ -378,13 +383,18 @@ export class OracleDriver implements Driver {
378383
if (!parameters || !Object.keys(parameters).length)
379384
return [sql, escapedParameters]
380385

386+
const parameterIndexMap = new Map<string, number>()
381387
sql = sql.replace(
382388
/:(\.\.\.)?([A-Za-z0-9_.]+)/g,
383389
(full, isArray: string, key: string): string => {
384390
if (!parameters.hasOwnProperty(key)) {
385391
return full
386392
}
387393

394+
if (parameterIndexMap.has(key)) {
395+
return this.parametersPrefix + parameterIndexMap.get(key)
396+
}
397+
388398
let value: any = parameters[key]
389399

390400
if (isArray) {
@@ -408,6 +418,7 @@ export class OracleDriver implements Driver {
408418
}
409419

410420
escapedParameters.push(value)
421+
parameterIndexMap.set(key, escapedParameters.length)
411422
return this.createParameter(key, escapedParameters.length - 1)
412423
},
413424
) // todo: make replace only in value statements, otherwise problems
@@ -928,7 +939,7 @@ export class OracleDriver implements Driver {
928939
* Creates an escaped parameter.
929940
*/
930941
createParameter(parameterName: string, index: number): string {
931-
return ":" + (index + 1)
942+
return this.parametersPrefix + (index + 1)
932943
}
933944

934945
/**

src/driver/postgres/PostgresDriver.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ export class PostgresDriver implements Driver {
257257
metadataValue: "text",
258258
}
259259

260+
/**
261+
* The prefix used for the parameters
262+
*/
263+
parametersPrefix: string = "$"
264+
260265
/**
261266
* Default values of length, precision and scale depends on column data type.
262267
* Used in the cases when length/precision/scale is not specified by user.
@@ -830,13 +835,18 @@ export class PostgresDriver implements Driver {
830835
if (!parameters || !Object.keys(parameters).length)
831836
return [sql, escapedParameters]
832837

838+
const parameterIndexMap = new Map<string, number>()
833839
sql = sql.replace(
834840
/:(\.\.\.)?([A-Za-z0-9_.]+)/g,
835841
(full, isArray: string, key: string): string => {
836842
if (!parameters.hasOwnProperty(key)) {
837843
return full
838844
}
839845

846+
if (parameterIndexMap.has(key)) {
847+
return this.parametersPrefix + parameterIndexMap.get(key)
848+
}
849+
840850
let value: any = parameters[key]
841851

842852
if (isArray) {
@@ -856,6 +866,7 @@ export class PostgresDriver implements Driver {
856866
}
857867

858868
escapedParameters.push(value)
869+
parameterIndexMap.set(key, escapedParameters.length)
859870
return this.createParameter(key, escapedParameters.length - 1)
860871
},
861872
) // todo: make replace only in value statements, otherwise problems
@@ -1399,7 +1410,7 @@ export class PostgresDriver implements Driver {
13991410
* Creates an escaped parameter.
14001411
*/
14011412
createParameter(parameterName: string, index: number): string {
1402-
return "$" + (index + 1)
1413+
return this.parametersPrefix + (index + 1)
14031414
}
14041415

14051416
// -------------------------------------------------------------------------

src/driver/spanner/SpannerDriver.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ export class SpannerDriver implements Driver {
157157
metadataValue: "string",
158158
}
159159

160+
/**
161+
* The prefix used for the parameters
162+
*/
163+
parametersPrefix: string = "@param"
164+
160165
/**
161166
* Default values of length, precision and scale depends on column data type.
162167
* Used in the cases when length/precision/scale is not specified by user.
@@ -251,13 +256,18 @@ export class SpannerDriver implements Driver {
251256
if (!parameters || !Object.keys(parameters).length)
252257
return [sql, escapedParameters]
253258

259+
const parameterIndexMap = new Map<string, number>()
254260
sql = sql.replace(
255261
/:(\.\.\.)?([A-Za-z0-9_.]+)/g,
256262
(full, isArray: string, key: string): string => {
257263
if (!parameters.hasOwnProperty(key)) {
258264
return full
259265
}
260266

267+
if (parameterIndexMap.has(key)) {
268+
return this.parametersPrefix + parameterIndexMap.get(key)
269+
}
270+
261271
let value: any = parameters[key]
262272

263273
if (value === null) {
@@ -279,7 +289,9 @@ export class SpannerDriver implements Driver {
279289
if (value instanceof Function) {
280290
return value()
281291
}
292+
282293
escapedParameters.push(value)
294+
parameterIndexMap.set(key, escapedParameters.length - 1)
283295
return this.createParameter(key, escapedParameters.length - 1)
284296
},
285297
) // todo: make replace only in value statements, otherwise problems
@@ -717,7 +729,7 @@ export class SpannerDriver implements Driver {
717729
* Creates an escaped parameter.
718730
*/
719731
createParameter(parameterName: string, index: number): string {
720-
return "@param" + index
732+
return this.parametersPrefix + index
721733
}
722734

723735
// -------------------------------------------------------------------------

src/driver/sqlserver/SqlServerDriver.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ export class SqlServerDriver implements Driver {
211211
metadataValue: "nvarchar(MAX)" as any,
212212
}
213213

214+
/**
215+
* The prefix used for the parameters
216+
*/
217+
parametersPrefix: string = "@"
218+
214219
/**
215220
* Default values of length, precision and scale depends on column data type.
216221
* Used in the cases when length/precision/scale is not specified by user.
@@ -371,13 +376,18 @@ export class SqlServerDriver implements Driver {
371376
if (!parameters || !Object.keys(parameters).length)
372377
return [sql, escapedParameters]
373378

379+
const parameterIndexMap = new Map<string, number>()
374380
sql = sql.replace(
375381
/:(\.\.\.)?([A-Za-z0-9_.]+)/g,
376382
(full, isArray: string, key: string): string => {
377383
if (!parameters.hasOwnProperty(key)) {
378384
return full
379385
}
380386

387+
if (parameterIndexMap.has(key)) {
388+
return this.parametersPrefix + parameterIndexMap.get(key)
389+
}
390+
381391
let value: any = parameters[key]
382392

383393
if (isArray) {
@@ -397,6 +407,7 @@ export class SqlServerDriver implements Driver {
397407
}
398408

399409
escapedParameters.push(value)
410+
parameterIndexMap.set(key, escapedParameters.length - 1)
400411
return this.createParameter(key, escapedParameters.length - 1)
401412
},
402413
) // todo: make replace only in value statements, otherwise problems
@@ -908,7 +919,7 @@ export class SqlServerDriver implements Driver {
908919
* Creates an escaped parameter.
909920
*/
910921
createParameter(parameterName: string, index: number): string {
911-
return "@" + index
922+
return this.parametersPrefix + index
912923
}
913924

914925
// -------------------------------------------------------------------------

test/functional/repository/aggregate-methods/repository-aggregate-methods.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { LessThan } from "../../../../src"
1010
import { expect } from "chai"
1111

1212
describe("repository > aggregate methods", () => {
13-
debugger
1413
let connections: DataSource[]
1514
let repository: Repository<Post>
1615

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Column, Entity, PrimaryColumn } from "../../../../src"
2+
3+
@Entity()
4+
export class Weather {
5+
@PrimaryColumn()
6+
id: string
7+
8+
@Column({ type: "float" })
9+
temperature: number
10+
}

test/github-issues/7308/issue-7308.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import "reflect-metadata"
2+
import {
3+
createTestingConnections,
4+
closeTestingConnections,
5+
reloadTestingDatabases,
6+
} from "../../utils/test-utils"
7+
import { DataSource } from "../../../src/data-source/DataSource"
8+
import { Weather } from "./entity/weather"
9+
import { expect } from "chai"
10+
11+
describe("github issues > #7308 queryBuilder makes different parameter identifiers for same parameter, causing problems with groupby", () => {
12+
describe("Postgres & cockroachdb", () => {
13+
let dataSources: DataSource[]
14+
before(
15+
async () =>
16+
(dataSources = await createTestingConnections({
17+
entities: [Weather],
18+
enabledDrivers: [
19+
"postgres",
20+
"cockroachdb",
21+
"spanner",
22+
"mssql",
23+
"oracle",
24+
],
25+
schemaCreate: true,
26+
dropSchema: true,
27+
})),
28+
)
29+
30+
beforeEach(() => reloadTestingDatabases(dataSources))
31+
after(() => closeTestingConnections(dataSources))
32+
33+
it("should not create different parameters identifiers for the same parameter", () =>
34+
Promise.all(
35+
dataSources.map(async (dataSource) => {
36+
const [query, parameters] = dataSource
37+
.getRepository(Weather)
38+
.createQueryBuilder()
39+
.select("round(temperature, :floatNumber)")
40+
.addSelect("count(*)", "count")
41+
.groupBy("round(temperature, :floatNumber)")
42+
.setParameters({ floatNumber: 2.4 })
43+
.getQueryAndParameters()
44+
query.should.not.be.undefined
45+
46+
if (
47+
dataSource.driver.options.type === "postgres" ||
48+
dataSource.driver.options.type === "cockroachdb"
49+
) {
50+
expect(query).to.equal(
51+
'SELECT round(temperature, $1), count(*) AS "count" FROM "weather" "Weather" GROUP BY round(temperature, $1)',
52+
)
53+
} else if (dataSource.driver.options.type === "spanner") {
54+
expect(query).to.equal(
55+
'SELECT round(temperature, @param0), count(*) AS "count" FROM "weather" "Weather" GROUP BY round(temperature, @param0)',
56+
)
57+
} else if (dataSource.driver.options.type === "oracle") {
58+
expect(query).to.equal(
59+
'SELECT round(temperature, :1), count(*) AS "count" FROM "weather" "Weather" GROUP BY round(temperature, :1)',
60+
)
61+
} else if (dataSource.driver.options.type === "mssql") {
62+
expect(query).to.equal(
63+
'SELECT round(temperature, @0), count(*) AS "count" FROM "weather" "Weather" GROUP BY round(temperature, @0)',
64+
)
65+
}
66+
return parameters.length.should.eql(1)
67+
}),
68+
))
69+
})
70+
})

0 commit comments

Comments
 (0)