Skip to content

Commit fcbdcab

Browse files
committed
Implement better query helpers
1 parent 645a0a3 commit fcbdcab

File tree

2 files changed

+102
-34
lines changed

2 files changed

+102
-34
lines changed

lib/index.js

+80-34
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,21 @@ import {
1313
toCamel,
1414
toKebab,
1515
errors,
16+
escape,
1617
types
1718
} from './types.js'
1819

20+
const notPromise = {
21+
P: {},
22+
finally: notTagged,
23+
then: notTagged,
24+
catch: notTagged
25+
}
26+
27+
function notTagged() {
28+
throw errors.generic({ message: 'Query not called as a tagged template literal', code: 'NOT_TAGGED_CALL' })
29+
}
30+
1931
Object.assign(Postgres, {
2032
toPascal,
2133
toCamel,
@@ -37,6 +49,8 @@ export default function Postgres(url, options) {
3749
, listeners = {}
3850
, typeArrayMap = {}
3951
, files = {}
52+
, isInsert = /(^|\))\s*insert\s*into\s/i
53+
, isSelect = /(^|\))\s*select\s/i
4054

4155
function postgres(xs, ...args) {
4256
return query({}, getConnection(), xs, args)
@@ -138,7 +152,7 @@ export default function Postgres(url, options) {
138152

139153
function query(query, connection, xs, args) {
140154
if (!query.raw && (!Array.isArray(xs) || !Array.isArray(xs.raw)))
141-
throw errors.generic({ message: 'Query not called as a tagged template literal', code: 'NOT_TAGGED_CALL' })
155+
return nested(xs, args)
142156

143157
const promise = new Promise((resolve, reject) => {
144158
query.resolve = resolve
@@ -155,6 +169,13 @@ export default function Postgres(url, options) {
155169
return promise
156170
}
157171

172+
function nested(first, rest) {
173+
const o = Object.create(notPromise)
174+
o.first = first
175+
o.rest = rest
176+
return o
177+
}
178+
158179
function send(connection, query, xs, args) {
159180
connection
160181
? connection.send(query, query.raw ? parseRaw(xs, args) : parse(xs, args))
@@ -206,22 +227,6 @@ export default function Postgres(url, options) {
206227
}
207228
}
208229

209-
function rows(rows, ...args) {
210-
return {
211-
rows: typeof args[0] === 'string'
212-
? rows.map(x => Array.isArray(x) ? x : args.map(a => x[a])) // pluck
213-
: typeof args[0] === 'function'
214-
? rows.map(x => args[0](x)) // map
215-
: rows
216-
}
217-
}
218-
219-
function row(row, ...args) {
220-
return {
221-
row: args.map(a => row[a])
222-
}
223-
}
224-
225230
function array(value) {
226231
return {
227232
type: inferType(value) || 25,
@@ -267,9 +272,7 @@ export default function Postgres(url, options) {
267272
unsafe,
268273
array,
269274
file,
270-
json,
271-
rows,
272-
row
275+
json
273276
})
274277

275278
function notify(channel, payload) {
@@ -380,13 +383,9 @@ export default function Postgres(url, options) {
380383

381384
for (let i = 1; i < xs.length; i++) {
382385
arg = args[i - 1]
383-
str += (!arg
384-
? parseValue(arg, xargs, types)
385-
: arg.rows
386-
? parseRows(arg.rows, xargs, types)
387-
: arg.row
388-
? parseRow(arg.row, xargs, types)
389-
: parseValue(arg, xargs, types)
386+
str += (arg && arg.P === notPromise.P
387+
? parseHelper(str, arg, xargs, types)
388+
: parseValue(arg, xargs, types)
390389
) + xs[i]
391390
}
392391

@@ -397,19 +396,66 @@ export default function Postgres(url, options) {
397396
}
398397
}
399398

400-
function parseRows(rows, xargs, types) {
401-
xargs.dynamic = true
402-
return rows.map(row => parseRow(row, xargs, types)).join(',')
399+
function parseHelper(str, { first, rest }, xargs, types) {
400+
if (first !== null && typeof first === 'object' && typeof first[0] !== 'string') {
401+
if (isInsert.test(str))
402+
return insertHelper(first, rest, xargs, types)
403+
else if(isSelect.test(str))
404+
return selectHelper(first, rest, xargs, types)
405+
else if(!Array.isArray(first))
406+
return equalsHelper(first, rest, xargs, types)
407+
}
408+
409+
return escapeHelper(Array.isArray(first) ? first : [first].concat(rest))
403410
}
404411

405-
function parseRow(row, xargs, types) {
406-
return '(' + row.map(x => parseValue(x, xargs, types)).join(',') + ')'
412+
function selectHelper(first, columns, xargs, types) {
413+
return Object.entries(first).reduce((acc, [k, v]) =>
414+
acc + (!columns.length || columns.indexOf(k) > -1
415+
? (acc ? ', ' : '') + parseValue(v, xargs, types) + ' as ' + escape(k)
416+
: ''
417+
)
418+
, '')
419+
}
420+
421+
function insertHelper(first, columns, xargs, types) {
422+
first = Array.isArray(first) ? first : [first]
423+
return '(' + escapeHelper(Object.keys(first[0])) + ') values ' +
424+
first.map(row =>
425+
'(' + Object.entries(row).reduce((acc, [k, v]) =>
426+
acc + (!columns.length || columns.indexOf(k) > -1
427+
? (acc ? ', ' : '') + parseValue(v, xargs, types)
428+
: ''
429+
)
430+
, '') + ')'
431+
).join(', ')
432+
}
433+
434+
function equalsHelper(first, columns, xargs, types) {
435+
return Object.entries(first).reduce((acc, [k, v]) =>
436+
acc + (!columns.length || columns.indexOf(k) > -1
437+
? (acc ? ', ' : '') + escape(k) + ' = ' + parseValue(v, xargs, types)
438+
: ''
439+
)
440+
, '')
441+
}
442+
443+
function escapeHelper(xs) {
444+
return xs.reduce((acc, x) => acc + (acc ? ', ' : '') + escape(x), '')
407445
}
408446

409447
function parseValue(x, xargs, types) {
448+
return Array.isArray(x)
449+
? x.reduce((acc, x) => addValue(x, xargs, types), '')
450+
: addValue(x, xargs, types)
451+
}
452+
453+
function addValue(x, xargs, types) {
410454
const type = getType(x)
411-
types.push(type.type)
412-
return '$' + xargs.push(type)
455+
, i = types.push(type-type)
456+
457+
xargs.push(type)
458+
return '$' + i
413459
}
414460

415461
function getType(x) {

lib/types.js

+22
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ const type = {
6565
boolean: 16
6666
}
6767

68+
export function escape(str) {
69+
let result = ''
70+
let q = str[0] < 10 || str[0] === '$'
71+
let last = 0
72+
let char
73+
let code
74+
75+
for (let i = 0; i < str.length; i++) {
76+
const char = str[i]
77+
const code = char.charCodeAt(0)
78+
if (str[i] === '"') {
79+
q = true
80+
result += str.slice(last, i) + '"'
81+
last = i
82+
} else if (code === 96 || (code !== 36 && code <= 47) || (code >= 58 && code <= 64) || (code >= 91 && code <= 94) || (code >= 123 && code <= 128)) {
83+
q = true
84+
}
85+
}
86+
87+
return (q ? '"' : '') + (q ? result + str.slice(last, str.length) : str) + (q ? '"' : '')
88+
}
89+
6890
export function inferType(x) {
6991
return x.type || (x instanceof Date
7092
? 1184

0 commit comments

Comments
 (0)