Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support named parameter replacement #23

Merged
merged 2 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ const results = await conn.execute('select 1 from dual where 1=?', [42])
console.log(results)
```

Named replacement parameters are supported with a colon prefix.

```ts
const results = await conn.execute('select 1 from dual where 1=:id', { id: 42 })
```

## Development

```
Expand Down
19 changes: 18 additions & 1 deletion __tests__/sanitization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@ import { format } from '../src/sanitization'

describe('sanitization', () => {
describe('format', () => {
test('does nothing with empty values', () => {
test('does no replacement for missing object key', () => {
const query = 'select 1 from user where id=:id'
expect(format(query, {})).toEqual(query)
})

test('replaces named parameters', () => {
const query = 'select 1 from user where state in (:state) and deleted_at=:deleted_at'
const expected = "select 1 from user where state in ('active', 'inactive') and deleted_at=true"
expect(format(query, { state: ['active', 'inactive'], deleted_at: true })).toEqual(expected)
})

test('replaces duplicate named parameters', () => {
const query = 'select 1 from user where id=:id or actor_id=:id'
const expected = 'select 1 from user where id=42 or actor_id=42'
expect(format(query, { id: 42 })).toEqual(expected)
})

test('does nothing with empty values list', () => {
const query = 'select 1 from user where id=?'
expect(format(query, [])).toEqual(query)
})
Expand Down
13 changes: 12 additions & 1 deletion src/sanitization.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
type Stringable = { toString: () => string }
type Value = null | undefined | number | boolean | string | Array<Value> | Date | Stringable

export function format(query: string, values: Value[]): string {
export function format(query: string, values: Value[] | Record<string, Value>): string {
return Array.isArray(values) ? replacePosition(query, values) : replaceNamed(query, values)
}

function replacePosition(query: string, values: Value[]): string {
let index = 0
return query.replace(/\?/g, (match) => {
return index < values.length ? sanitize(values[index++]) : match
})
}

function replaceNamed(query: string, values: Record<string, Value>): string {
const names = new Set(Object.keys(values))
return query.replace(/:(\w+)/g, (match, name) => {
return names.has(name) ? sanitize(values[name]) : match
})
}

function sanitize(value: Value): string {
if (value == null) {
return 'null'
Expand Down