Skip to content

Commit b977e9c

Browse files
committed
support raw based holder
1 parent 40ce6e8 commit b977e9c

File tree

3 files changed

+166
-13
lines changed

3 files changed

+166
-13
lines changed

src/connections/postgre/postgresql.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,16 @@ import { QueryResult } from '..';
33
import { Query } from '../../query';
44
import { AbstractDialect } from './../../query-builder';
55
import { PostgresDialect } from './../../query-builder/dialects/postgres';
6-
import { QueryType } from './../../query-params';
76
import {
87
createErrorResult,
98
transformArrayBasedResult,
109
} from './../../utils/transformer';
1110
import { PostgreBaseConnection } from './base';
1211

13-
function replacePlaceholders(query: string): string {
14-
let index = 1;
15-
return query.replace(/\?/g, () => `$${index++}`);
16-
}
17-
1812
export class PostgreSQLConnection extends PostgreBaseConnection {
1913
client: Client;
2014
dialect: AbstractDialect = new PostgresDialect();
21-
queryType: QueryType = QueryType.positional;
15+
protected numberedPlaceholder = true;
2216

2317
constructor(pgClient: any) {
2418
super();
@@ -38,10 +32,7 @@ export class PostgreSQLConnection extends PostgreBaseConnection {
3832
): Promise<QueryResult<T>> {
3933
try {
4034
const { rows, fields } = await this.client.query({
41-
text:
42-
query.parameters?.length === 0
43-
? query.query
44-
: replacePlaceholders(query.query),
35+
text: query.query,
4536
rowMode: 'array',
4637
values: query.parameters as unknown[],
4738
});

src/connections/sql-base.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import {
77
} from '..';
88
import { AbstractDialect, ColumnDataType } from './../query-builder';
99
import { TableColumn, TableColumnDefinition } from './../models/database';
10+
import {
11+
namedPlaceholder,
12+
toNumberedPlaceholders,
13+
} from './../utils/placeholder';
1014

1115
export abstract class SqlConnection extends Connection {
1216
abstract dialect: AbstractDialect;
17+
protected numberedPlaceholder = false;
1318

1419
abstract query<T = Record<string, unknown>>(
1520
query: Query
@@ -21,8 +26,32 @@ export abstract class SqlConnection extends Connection {
2126
return dataType;
2227
}
2328

24-
async raw(query: string): Promise<QueryResult> {
25-
return await this.query({ query });
29+
async raw(
30+
query: string,
31+
params?: Record<string, unknown> | unknown[]
32+
): Promise<QueryResult> {
33+
if (!params) return await this.query({ query });
34+
35+
// Positional placeholder
36+
if (Array.isArray(params)) {
37+
if (this.numberedPlaceholder) {
38+
const { query: newQuery, bindings } = toNumberedPlaceholders(
39+
query,
40+
params
41+
);
42+
43+
return await this.query({
44+
query: newQuery,
45+
parameters: bindings,
46+
});
47+
}
48+
49+
return await this.query({ query, parameters: params });
50+
}
51+
52+
// Named placeholder
53+
const { query: newQuery, bindings } = namedPlaceholder(query, params!);
54+
return await this.query({ query: newQuery, parameters: bindings });
2655
}
2756

2857
async select(

src/utils/placeholder.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const RE_PARAM = /(?:\?)|(?::(\d+|(?:[a-zA-Z][a-zA-Z0-9_]*)))/g,
2+
DQUOTE = 34,
3+
SQUOTE = 39,
4+
BSLASH = 92;
5+
6+
/**
7+
* This code is based on https://github.com/mscdex/node-mariasql/blob/master/lib/Client.js#L296-L420
8+
* License: https://github.com/mscdex/node-mariasql/blob/master/LICENSE
9+
*
10+
* @param query
11+
* @returns
12+
*/
13+
function parse(query: string): [string] | [string[], (string | number)[]] {
14+
let ppos = RE_PARAM.exec(query);
15+
let curpos = 0;
16+
let start = 0;
17+
let end;
18+
const parts = [];
19+
let inQuote = false;
20+
let escape = false;
21+
let qchr;
22+
const tokens = [];
23+
let qcnt = 0;
24+
let lastTokenEndPos = 0;
25+
let i;
26+
27+
if (ppos) {
28+
do {
29+
for (i = curpos, end = ppos.index; i < end; ++i) {
30+
let chr = query.charCodeAt(i);
31+
console.log(i, query[i], inQuote, qchr);
32+
if (chr === BSLASH) escape = !escape;
33+
else {
34+
if (escape) {
35+
escape = false;
36+
continue;
37+
}
38+
if (inQuote && chr === qchr) {
39+
if (query.charCodeAt(i + 1) === qchr) {
40+
// quote escaped via "" or ''
41+
++i;
42+
continue;
43+
}
44+
inQuote = false;
45+
} else if (!inQuote && (chr === DQUOTE || chr === SQUOTE)) {
46+
inQuote = true;
47+
qchr = chr;
48+
}
49+
}
50+
}
51+
if (!inQuote) {
52+
parts.push(query.substring(start, end));
53+
tokens.push(ppos[0].length === 1 ? qcnt++ : ppos[1]);
54+
start = end + ppos[0].length;
55+
lastTokenEndPos = start;
56+
}
57+
curpos = end + ppos[0].length;
58+
} while ((ppos = RE_PARAM.exec(query)));
59+
60+
if (tokens.length) {
61+
if (curpos < query.length) {
62+
parts.push(query.substring(lastTokenEndPos));
63+
}
64+
return [parts, tokens];
65+
}
66+
}
67+
return [query];
68+
}
69+
70+
export function namedPlaceholder(
71+
query: string,
72+
params: Record<string, unknown>,
73+
numbered = false
74+
): { query: string; bindings: unknown[] } {
75+
const parts = parse(query);
76+
77+
if (parts.length === 1) {
78+
return { query, bindings: [] };
79+
}
80+
81+
const bindings = [];
82+
let newQuery = '';
83+
84+
const [sqlFragments, placeholders] = parts;
85+
86+
for (let i = 0; i < sqlFragments.length; i++) {
87+
newQuery += sqlFragments[i];
88+
89+
if (i < placeholders.length) {
90+
const key = placeholders[i];
91+
92+
if (numbered) {
93+
newQuery += `$${key}`;
94+
} else {
95+
newQuery += `?`;
96+
}
97+
98+
bindings.push(params[key]);
99+
}
100+
}
101+
102+
return { query: newQuery, bindings };
103+
}
104+
105+
export function toNumberedPlaceholders(
106+
query: string,
107+
params: unknown[]
108+
): {
109+
query: string;
110+
bindings: unknown[];
111+
} {
112+
const parts = parse(query);
113+
114+
if (parts.length === 1) {
115+
return { query, bindings: [] };
116+
}
117+
118+
const bindings = [];
119+
let newQuery = '';
120+
121+
const [sqlFragments, placeholders] = parts;
122+
123+
for (let i = 0; i < sqlFragments.length; i++) {
124+
newQuery += sqlFragments[i];
125+
126+
if (i < placeholders.length) {
127+
newQuery += `$${i + 1}`;
128+
bindings.push(params[i]);
129+
}
130+
}
131+
132+
return { query: newQuery, bindings };
133+
}

0 commit comments

Comments
 (0)