Skip to content

Commit 04c3b79

Browse files
committed
feat(query): add identifier fn for templating table, column names
Signed-off-by: Vladislav Polyakov <[email protected]>
1 parent 5ec6454 commit 04c3b79

File tree

2 files changed

+24
-13
lines changed

2 files changed

+24
-13
lines changed

packages/query/src/index.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { defaultRetryConfig, isRetryableError, retry } from '@ydbjs/retry'
88

99
import { Query } from './query.js'
1010
import { ctx } from './ctx.js'
11-
import { yql } from './yql.js'
11+
import { UnsafeString, identifier, yql } from './yql.js'
1212

1313
export type SQL = <T extends any[] = unknown[], P extends any[] = unknown[]>(
1414
strings: string | TemplateStringsArray,
@@ -48,6 +48,20 @@ export interface QueryClient extends SQL, AsyncDisposable {
4848

4949
transaction<T = unknown>(fn: TransactionContextCallback<T>): Promise<T>
5050
transaction<T = unknown>(options: TransactionExecuteOptions, fn: TransactionContextCallback<T>): Promise<T>
51+
52+
/**
53+
* Create an UnsafeString that represents a DB identifier (table, column).
54+
* When used in a query, the identifier will be escaped.
55+
*
56+
* **WARNING: This function does not offer any protection against SQL injections,
57+
* so you must validate any user input beforehand.**
58+
*
59+
* @example ```ts
60+
* const query = sql`SELECT * FROM ${sql.identifier('my-table')}`;
61+
* // 'SELECT * FROM `my-table`'
62+
* ```
63+
*/
64+
identifier(value: string | { toString(): string }): UnsafeString
5165
}
5266

5367
const doImpl = function <T = unknown>(): Promise<T> {
@@ -191,6 +205,7 @@ export function query(driver: Driver): QueryClient {
191205
do: doImpl,
192206
begin: txIml,
193207
transaction: txIml,
208+
identifier: identifier,
194209
async [Symbol.asyncDispose]() { },
195210
}
196211
)

packages/query/src/yql.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { fromJs, type Value } from "@ydbjs/value"
1+
import { type Value, fromJs } from "@ydbjs/value"
22

3-
const unsafe = Symbol("unsafe")
3+
const SymbolUnsafe = Symbol("unsafe")
44

55
function isObject(value: unknown): boolean {
66
return typeof value === 'object' && value !== null && !Array.isArray(value)
77
}
88

99
export class UnsafeString extends String {
10-
constructor(public value: string) {
11-
super(value)
12-
}
13-
14-
[unsafe] = true
10+
[SymbolUnsafe] = true
1511
}
1612

1713
export function yql<P extends any[] = unknown[]>(
@@ -23,7 +19,7 @@ export function yql<P extends any[] = unknown[]>(
2319

2420
if (Array.isArray(values)) {
2521
values.forEach((value, i) => {
26-
if (value[unsafe]) {
22+
if (value[SymbolUnsafe]) {
2723
return
2824
}
2925

@@ -41,17 +37,17 @@ export function yql<P extends any[] = unknown[]>(
4137
text += strings.reduce((prev, curr, i) => {
4238
let value = values[i]
4339

44-
return prev + curr + (value ? value[unsafe] ? value.toString() : `$p${i}` : '')
40+
return prev + curr + (value ? value[SymbolUnsafe] ? value.toString() : `$p${i}` : '')
4541
}, '')
4642
}
4743

4844
return { text, params }
4945
}
5046

51-
export function usafe(value: string | { toString(): string }) {
47+
export function unsafe(value: string | { toString(): string }) {
5248
return new UnsafeString(value.toString())
5349
}
5450

55-
export function table(path: string) {
56-
return usafe("`" + path + "`")
51+
export function identifier(path: string) {
52+
return unsafe("`" + path + "`")
5753
}

0 commit comments

Comments
 (0)