Skip to content

Commit e465335

Browse files
committed
Add sql.describe()
1 parent 806cc2c commit e465335

File tree

7 files changed

+88
-8
lines changed

7 files changed

+88
-8
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,20 @@ sql.file(path.join(__dirname, 'query.sql'), [], {
379379

380380
```
381381

382+
## Describe `sql.describe(stmt) -> Promise`
383+
384+
Describe the parameters and output columns of the given SQL statement.
385+
386+
```js
387+
388+
const { params, columns } = await sql.describe('select * from users where id = $1 and name like $2')
389+
390+
```
391+
392+
The resulting `params` is an array of Postgres data type ids (OIDs) for parameters in order `$1`, `$2`,
393+
etc. `columns` is an array of objects `{ name, type, parser }`, where `name` is the column name, `type`
394+
is the type OID, and `parser` is a function used to parse the value to JavaScript.
395+
382396
## Transactions
383397

384398

lib/backend.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,10 @@ function Backend({
155155
: console.log(parseError(x)) // eslint-disable-line
156156
}
157157

158-
function NoData() { /* No handling needed */ }
158+
function NoData() {
159+
if (backend.query.describe)
160+
backend.query.result.columns = []
161+
}
159162

160163
function Authentication(x) {
161164
const type = x.readInt32BE(5)
@@ -174,8 +177,17 @@ function Backend({
174177
}
175178

176179
/* c8 ignore next 3 */
177-
function ParameterDescription() {
178-
backend.error = errors.notSupported('ParameterDescription')
180+
function ParameterDescription(x) {
181+
const length = x.readInt16BE(5)
182+
let index = 7
183+
184+
backend.query.statement.params = Array(length)
185+
186+
for (let i = 0; i < length; ++i) {
187+
backend.query.statement.params[i] = x.readInt32BE(index)
188+
index += 4
189+
}
190+
backend.query.result.params = backend.query.statement.params
179191
}
180192

181193
function RowDescription(x) {

lib/connection.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ function Connection(options = {}) {
117117
query.str = str
118118
query.args = args
119119
query.result = []
120-
query.result.count = null
120+
if (!query.describe) query.result.count = null
121121
idle_timeout && clearTimeout(timer)
122122

123123
typeof options.debug === 'function' && options.debug(id, str, args)
@@ -163,7 +163,9 @@ function Connection(options = {}) {
163163
query.statement = { name: sig ? 'p' + uid + statement_id++ : '', sig }
164164
return Buffer.concat([
165165
frontend.Parse(query.statement.name, str, args),
166-
bind(query, args)
166+
query.describe
167+
? frontend.Describe(query.statement.name)
168+
: bind(query, args)
167169
])
168170
}
169171

lib/frontend.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ const { errors } = require('./errors.js')
55

66
const N = String.fromCharCode(0)
77
const empty = Buffer.alloc(0)
8+
const flushSync = Buffer.concat([
9+
bytes.H().end(),
10+
bytes.S().end()
11+
])
812
const execute = Buffer.concat([
913
bytes.D().str('P').str(N).end(),
1014
bytes.E().str(N).i32(0).end(),
11-
bytes.H().end(),
12-
bytes.S().end()
15+
flushSync,
1316
])
1417

1518
const authNames = {
@@ -39,6 +42,7 @@ module.exports = {
3942
auth,
4043
Bind,
4144
Parse,
45+
Describe,
4246
Query,
4347
Close,
4448
Execute
@@ -190,6 +194,13 @@ function Parse(name, str, args) {
190194
return bytes.end()
191195
}
192196

197+
function Describe(name) {
198+
return Buffer.concat([
199+
bytes.D().str('S').str(name).str(N).end(),
200+
flushSync,
201+
])
202+
}
203+
193204
function Execute(rows) {
194205
return Buffer.concat([
195206
bytes.E().str(N).i32(rows).end(),

lib/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ function Postgres(a, b) {
274274
unsafe,
275275
array,
276276
file,
277-
json
277+
json,
278+
describe,
278279
})
279280

280281
function notify(channel, payload) {
@@ -315,6 +316,10 @@ function Postgres(a, b) {
315316
return promise
316317
}
317318

319+
function describe(stmt) {
320+
return query({ raw: true, describe: true }, connection || getConnection(), stmt)
321+
}
322+
318323
options.types && entries(options.types).forEach(([name, type]) => {
319324
sql.types[name] = (x) => ({ type: type.to, value: x })
320325
})

tests/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,3 +1048,33 @@ t('Insert array in sql()', async() => {
10481048
await sql`drop table tester`
10491049
]
10501050
})
1051+
1052+
t('Describe a statement', async() => {
1053+
await sql`create table tester (name text, age int)`
1054+
const r = await sql.describe('select name, age from tester where name like $1 and age > $2')
1055+
return [
1056+
'25,23/name:25,age:23',
1057+
`${r.params.join(',')}/${r.columns.map(c => `${c.name}:${c.type}`).join(',')}`,
1058+
await sql`drop table tester`
1059+
]
1060+
})
1061+
1062+
t('Describe a statement without parameters', async() => {
1063+
await sql`create table tester (name text, age int)`
1064+
const r = await sql.describe('select name, age from tester')
1065+
return [
1066+
'0,2',
1067+
`${r.params.length},${r.columns.length}`,
1068+
await sql`drop table tester`
1069+
]
1070+
})
1071+
1072+
t('Describe a statement without columns', async () => {
1073+
await sql`create table tester (name text, age int)`
1074+
const r = await sql.describe('insert into tester (name, age) values ($1, $2)')
1075+
return [
1076+
'2,0',
1077+
`${r.params.length},${r.columns.length}`,
1078+
await sql`drop table tester`
1079+
]
1080+
})

types/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ declare namespace postgres {
268268
columns: ColumnList<U>;
269269
}
270270

271+
interface DescribeResult {
272+
params: number[]
273+
columns: ColumnList<string>
274+
}
275+
271276
type ExecutionResult<T> = [] & ResultQueryMeta<number, T>;
272277
type RowList<T extends readonly Row[]> = T & ResultQueryMeta<T['length'], keyof T[number]>;
273278

@@ -315,6 +320,7 @@ declare namespace postgres {
315320
PostgresError: typeof PostgresError;
316321

317322
array<T extends SerializableParameter[] = SerializableParameter[]>(value: T): ArrayParameter<T>;
323+
describe(stmt: string): Promise<DescribeResult>
318324
begin<T>(cb: (sql: TransactionSql<TTypes>) => T | Promise<T>): Promise<UnwrapPromiseArray<T>>;
319325
begin<T>(options: string, cb: (sql: TransactionSql<TTypes>) => T | Promise<T>): Promise<UnwrapPromiseArray<T>>;
320326
end(options?: { timeout?: number }): Promise<void>;

0 commit comments

Comments
 (0)