postgres-array
Version:
Parse postgres array columns
122 lines (111 loc) • 3.83 kB
JavaScript
'use strict'
const BACKSLASH = '\\'
const DQUOT = '"'
const LBRACE = '{'
const RBRACE = '}'
const LBRACKET = '['
const EQUALS = '='
const COMMA = ','
/** When the raw value is this, it means a literal `null` */
const NULL_STRING = 'NULL'
/**
* Parses an array according to
* https://www.postgresql.org/docs/17/arrays.html#ARRAYS-IO
*
* Trusts the data (mostly), so only hook up to trusted Postgres servers.
*/
function makeParseArrayWithTransform (transform) {
const haveTransform = transform != null
return function parseArray (str) {
const rbraceIndex = str.length - 1
if (rbraceIndex === 1) {
return []
}
if (str[rbraceIndex] !== RBRACE) {
throw new Error('Invalid array text - must end with }')
}
// If starts with `[`, it is specifying the index boundas. Skip past first `=`.
let position = 0
if (str[position] === LBRACKET) {
position = str.indexOf(EQUALS) + 1
}
if (str[position++] !== LBRACE) {
throw new Error('Invalid array text - must start with {')
}
const output = []
let current = output
const stack = []
let currentStringStart = position
let currentString = ''
let expectValue = true
for (; position < rbraceIndex; ++position) {
let char = str[position]
// > The array output routine will put double quotes around element values if
// > they are empty strings, contain curly braces, delimiter characters, double
// > quotes, backslashes, or white space, or match the word NULL. Double quotes
// > and backslashes embedded in element values will be backslash-escaped.
if (char === DQUOT) {
// It's escaped
currentStringStart = ++position
let dquot = str.indexOf(DQUOT, currentStringStart)
let backSlash = str.indexOf(BACKSLASH, currentStringStart)
while (backSlash !== -1 && backSlash < dquot) {
position = backSlash
const part = str.slice(currentStringStart, position)
currentString += part
currentStringStart = ++position
if (dquot === position++) {
// This was an escaped doublequote; find the next one!
dquot = str.indexOf(DQUOT, position)
}
// Either way, find the next backslash
backSlash = str.indexOf(BACKSLASH, position)
}
position = dquot
const part = str.slice(currentStringStart, position)
currentString += part
current.push(haveTransform ? transform(currentString) : currentString)
currentString = ''
expectValue = false
} else if (char === LBRACE) {
const newArray = []
current.push(newArray)
stack.push(current)
current = newArray
currentStringStart = position + 1
expectValue = true
} else if (char === COMMA) {
expectValue = true
} else if (char === RBRACE) {
expectValue = false
const arr = stack.pop()
if (arr === undefined) {
throw new Error("Invalid array text - too many '}'")
}
current = arr
} else if (expectValue) {
currentStringStart = position
while (
(char = str[position]) !== COMMA &&
char !== RBRACE &&
position < rbraceIndex
) {
++position
}
const part = str.slice(currentStringStart, position--)
current.push(
part === NULL_STRING ? null : haveTransform ? transform(part) : part
)
expectValue = false
} else {
throw new Error('Was expecting delimeter')
}
}
return output
}
}
const parseArray = makeParseArrayWithTransform()
exports.parse = (source, transform) =>
transform != null
? makeParseArrayWithTransform(transform)(source)
: parseArray(source)