@ogc/postgres-tmp
Version:
Fastest full featured PostgreSQL client for Node.js
379 lines (324 loc) • 11.7 kB
JavaScript
const { Query } = require('./query.js')
const { Errors } = require('./errors.js')
const defaultArrayTypes = {
boolean: 1000,
number: 1021,
string: 1009,
bigint: 1016
}
const inferArrayType = module.exports.inferArrayType = function inferArrayType(x) {
return defaultArrayTypes[typeof x[0]] || inferType(x[0])
}
const types = module.exports.types = {
string: {
to: 25,
from: null, // defaults to string
serialize: x => '' + x
},
number: {
to: 0,
from: [21, 23, 26, 700, 701],
serialize: x => '' + x,
parse: x => +x
},
json: {
to: 114,
from: [114, 3802],
serialize: x => JSON.stringify(x),
parse: x => JSON.parse(x)
},
boolean: {
to: 16,
from: 16,
serialize: x => x === true ? 't' : 'f',
parse: x => x === 't'
},
date: {
to: 1184,
from: [1082, 1114, 1184],
serialize: x => (x instanceof Date ? x : new Date(x)).toISOString(),
parse: x => new Date(x)
},
bytea: {
to: 17,
from: 17,
serialize: x => '\\x' + Buffer.from(x).toString('hex'),
parse: x => Buffer.from(x.slice(2), 'hex')
}
}
class NotTagged { then() { notTagged() } catch() { notTagged() } finally() { notTagged() }}
const Identifier = module.exports.Identifier = class Identifier extends NotTagged {
constructor(value) {
super()
this.value = escapeIdentifier(value)
}
}
const Parameter = module.exports.Parameter = class Parameter extends NotTagged {
constructor(value, type, array) {
super()
this.value = value
this.type = type
this.array = array
}
}
const Builder = module.exports.Builder = class Builder extends NotTagged {
constructor(first, rest) {
super()
this.first = first
this.rest = rest
}
build(before, parameters, types, options) {
const keyword = builders.map(([x, fn]) => ({ fn, i: before.search(x) })).sort((a, b) => a.i - b.i).pop()
return keyword.i === -1
? escapeIdentifiers(this.first, options)
: keyword.fn(this.first, this.rest, parameters, types, options)
}
}
module.exports.handleValue = handleValue;function handleValue(x, parameters, types, options) {
let value = x instanceof Parameter ? x.value : x
if (value === undefined) {
x instanceof Parameter
? x.value = options.transform.undefined
: value = x = options.transform.undefined
if (value === undefined)
throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
}
return '$' + (types.push(
x instanceof Parameter
? (parameters.push(x.value), x.array
? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
: x.type
)
: (parameters.push(x), inferType(x))
))
}
const defaultHandlers = typeHandlers(types)
module.exports.stringify = stringify;function stringify(q, string, value, parameters, types, options) { // eslint-disable-line
for (let i = 1; i < q.strings.length; i++) {
string += (stringifyValue(string, value, parameters, types, options)) + q.strings[i]
value = q.args[i]
}
return string
}
function stringifyValue(string, value, parameters, types, o) {
return (
value instanceof Builder ? value.build(string, parameters, types, o) :
value instanceof Query ? fragment(value, parameters, types, o) :
value instanceof Identifier ? value.value :
value && value[0] instanceof Query ? value.reduce((acc, x) => acc + ' ' + fragment(x, parameters, types, o), '') :
handleValue(value, parameters, types, o)
)
}
function fragment(q, parameters, types, options) {
q.fragment = true
return stringify(q, q.strings[0], q.args[0], parameters, types, options)
}
function valuesBuilder(first, parameters, types, columns, options) {
return first.map(row =>
'(' + columns.map(column =>
stringifyValue('values', row[column], parameters, types, options)
).join(',') + ')'
).join(',')
}
function values(first, rest, parameters, types, options) {
const multi = Array.isArray(first[0])
const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first)
return valuesBuilder(multi ? first : [first], parameters, types, columns, options)
}
function select(first, rest, parameters, types, options) {
typeof first === 'string' && (first = [first].concat(rest))
if (Array.isArray(first))
return escapeIdentifiers(first, options)
let value
const columns = rest.length ? rest.flat() : Object.keys(first)
return columns.map(x => {
value = first[x]
return (
value instanceof Query ? fragment(value, parameters, types, options) :
value instanceof Identifier ? value.value :
handleValue(value, parameters, types, options)
) + ' as ' + escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x)
}).join(',')
}
const builders = Object.entries({
values,
in: (...xs) => {
const x = values(...xs)
return x === '()' ? '(null)' : x
},
select,
as: select,
returning: select,
'\\(': select,
update(first, rest, parameters, types, options) {
return (rest.length ? rest.flat() : Object.keys(first)).map(x =>
escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) +
'=' + stringifyValue('values', first[x], parameters, types, options)
)
},
insert(first, rest, parameters, types, options) {
const columns = rest.length ? rest.flat() : Object.keys(Array.isArray(first) ? first[0] : first)
return '(' + escapeIdentifiers(columns, options) + ')values' +
valuesBuilder(Array.isArray(first) ? first : [first], parameters, types, columns, options)
}
}).map(([x, fn]) => ([new RegExp('((?:^|[\\s(])' + x + '(?:$|[\\s(]))(?![\\s\\S]*\\1)', 'i'), fn]))
function notTagged() {
throw Errors.generic('NOT_TAGGED_CALL', 'Query not called as a tagged template literal')
}
const serializers = module.exports.serializers = defaultHandlers.serializers
const parsers = module.exports.parsers = defaultHandlers.parsers
const END = module.exports.END = {}
function firstIsString(x) {
if (Array.isArray(x))
return firstIsString(x[0])
return typeof x === 'string' ? 1009 : 0
}
const mergeUserTypes = module.exports.mergeUserTypes = function(types) {
const user = typeHandlers(types || {})
return {
serializers: Object.assign({}, serializers, user.serializers),
parsers: Object.assign({}, parsers, user.parsers)
}
}
function typeHandlers(types) {
return Object.keys(types).reduce((acc, k) => {
types[k].from && [].concat(types[k].from).forEach(x => acc.parsers[x] = types[k].parse)
if (types[k].serialize) {
acc.serializers[types[k].to] = types[k].serialize
types[k].from && [].concat(types[k].from).forEach(x => acc.serializers[x] = types[k].serialize)
}
return acc
}, { parsers: {}, serializers: {} })
}
function escapeIdentifiers(xs, { transform: { column } }) {
return xs.map(x => escapeIdentifier(column.to ? column.to(x) : x)).join(',')
}
const escapeIdentifier = module.exports.escapeIdentifier = function escape(str) {
return '"' + str.replace(/"/g, '""').replace(/\./g, '"."') + '"'
}
const inferType = module.exports.inferType = function inferType(x) {
return (
x instanceof Parameter ? x.type :
x instanceof Date ? 1184 :
x instanceof Uint8Array ? 17 :
(x === true || x === false) ? 16 :
typeof x === 'bigint' ? 20 :
Array.isArray(x) ? inferArrayType(x) :
0
)
}
const escapeBackslash = /\\/g
const escapeQuote = /"/g
function arrayEscape(x) {
return x
.replace(escapeBackslash, '\\\\')
.replace(escapeQuote, '\\"')
}
const arraySerializer = module.exports.arraySerializer = function arraySerializer(xs, serializer, options, typarray) {
if (Array.isArray(xs) === false)
return xs
if (!xs.length)
return '{}'
const first = xs[0]
// Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
const delimiter = typarray === 1020 ? ';' : ','
if (Array.isArray(first) && !first.type)
return '{' + xs.map(x => arraySerializer(x, serializer, options, typarray)).join(delimiter) + '}'
return '{' + xs.map(x => {
if (x === undefined) {
x = options.transform.undefined
if (x === undefined)
throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
}
return x === null
? 'null'
: '"' + arrayEscape(serializer ? serializer(x.type ? x.value : x) : '' + x) + '"'
}).join(delimiter) + '}'
}
const arrayParserState = {
i: 0,
char: null,
str: '',
quoted: false,
last: 0
}
const arrayParser = module.exports.arrayParser = function arrayParser(x, parser, typarray) {
arrayParserState.i = arrayParserState.last = 0
return arrayParserLoop(arrayParserState, x, parser, typarray)
}
function arrayParserLoop(s, x, parser, typarray) {
const xs = []
// Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
const delimiter = typarray === 1020 ? ';' : ','
for (; s.i < x.length; s.i++) {
s.char = x[s.i]
if (s.quoted) {
if (s.char === '\\') {
s.str += x[++s.i]
} else if (s.char === '"') {
xs.push(parser ? parser(s.str) : s.str)
s.str = ''
s.quoted = x[s.i + 1] === '"'
s.last = s.i + 2
} else {
s.str += s.char
}
} else if (s.char === '"') {
s.quoted = true
} else if (s.char === '{') {
s.last = ++s.i
xs.push(arrayParserLoop(s, x, parser, typarray))
} else if (s.char === '}') {
s.quoted = false
s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
s.last = s.i + 1
break
} else if (s.char === delimiter && s.p !== '}' && s.p !== '"') {
xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
s.last = s.i + 1
}
s.p = s.char
}
s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i + 1)) : x.slice(s.last, s.i + 1))
return xs
}
const toCamel = module.exports.toCamel = x => {
let str = x[0]
for (let i = 1; i < x.length; i++)
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
return str
}
const toPascal = module.exports.toPascal = x => {
let str = x[0].toUpperCase()
for (let i = 1; i < x.length; i++)
str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
return str
}
const toKebab = module.exports.toKebab = x => x.replace(/_/g, '-')
const fromCamel = module.exports.fromCamel = x => x.replace(/([A-Z])/g, '_$1').toLowerCase()
const fromPascal = module.exports.fromPascal = x => (x.slice(0, 1) + x.slice(1).replace(/([A-Z])/g, '_$1')).toLowerCase()
const fromKebab = module.exports.fromKebab = x => x.replace(/-/g, '_')
function createJsonTransform(fn) {
return function jsonTransform(x, column) {
return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802)
? Array.isArray(x)
? x.map(x => jsonTransform(x, column))
: Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {})
: x
}
}
toCamel.column = { from: toCamel }
toCamel.value = { from: createJsonTransform(toCamel) }
fromCamel.column = { to: fromCamel }
const camel = module.exports.camel = { ...toCamel }
camel.column.to = fromCamel
toPascal.column = { from: toPascal }
toPascal.value = { from: createJsonTransform(toPascal) }
fromPascal.column = { to: fromPascal }
const pascal = module.exports.pascal = { ...toPascal }
pascal.column.to = fromPascal
toKebab.column = { from: toKebab }
toKebab.value = { from: createJsonTransform(toKebab) }
fromKebab.column = { to: fromKebab }
const kebab = module.exports.kebab = { ...toKebab }
kebab.column.to = fromKebab