Spaces:
Runtime error
Runtime error
| import net from 'net' | |
| import tls from 'tls' | |
| import crypto from 'crypto' | |
| import Stream from 'stream' | |
| import { performance } from 'perf_hooks' | |
| import { stringify, handleValue, arrayParser, arraySerializer } from './types.js' | |
| import { Errors } from './errors.js' | |
| import Result from './result.js' | |
| import Queue from './queue.js' | |
| import { Query, CLOSE } from './query.js' | |
| import b from './bytes.js' | |
| export default Connection | |
| let uid = 1 | |
| const Sync = b().S().end() | |
| , Flush = b().H().end() | |
| , SSLRequest = b().i32(8).i32(80877103).end(8) | |
| , ExecuteUnnamed = Buffer.concat([b().E().str(b.N).i32(0).end(), Sync]) | |
| , DescribeUnnamed = b().D().str('S').str(b.N).end() | |
| , noop = () => { /* noop */ } | |
| const retryRoutines = new Set([ | |
| 'FetchPreparedStatement', | |
| 'RevalidateCachedQuery', | |
| 'transformAssignedExpr' | |
| ]) | |
| const errorFields = { | |
| 83 : 'severity_local', // S | |
| 86 : 'severity', // V | |
| 67 : 'code', // C | |
| 77 : 'message', // M | |
| 68 : 'detail', // D | |
| 72 : 'hint', // H | |
| 80 : 'position', // P | |
| 112 : 'internal_position', // p | |
| 113 : 'internal_query', // q | |
| 87 : 'where', // W | |
| 115 : 'schema_name', // s | |
| 116 : 'table_name', // t | |
| 99 : 'column_name', // c | |
| 100 : 'data type_name', // d | |
| 110 : 'constraint_name', // n | |
| 70 : 'file', // F | |
| 76 : 'line', // L | |
| 82 : 'routine' // R | |
| } | |
| function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose = noop } = {}) { | |
| const { | |
| sslnegotiation, | |
| ssl, | |
| max, | |
| user, | |
| host, | |
| port, | |
| database, | |
| parsers, | |
| transform, | |
| onnotice, | |
| onnotify, | |
| onparameter, | |
| max_pipeline, | |
| keep_alive, | |
| backoff, | |
| target_session_attrs | |
| } = options | |
| const sent = Queue() | |
| , id = uid++ | |
| , backend = { pid: null, secret: null } | |
| , idleTimer = timer(end, options.idle_timeout) | |
| , lifeTimer = timer(end, options.max_lifetime) | |
| , connectTimer = timer(connectTimedOut, options.connect_timeout) | |
| let socket = null | |
| , cancelMessage | |
| , errorResponse = null | |
| , result = new Result() | |
| , incoming = Buffer.alloc(0) | |
| , needsTypes = options.fetch_types | |
| , backendParameters = {} | |
| , statements = {} | |
| , statementId = Math.random().toString(36).slice(2) | |
| , statementCount = 1 | |
| , closedTime = 0 | |
| , remaining = 0 | |
| , hostIndex = 0 | |
| , retries = 0 | |
| , length = 0 | |
| , delay = 0 | |
| , rows = 0 | |
| , serverSignature = null | |
| , nextWriteTimer = null | |
| , terminated = false | |
| , incomings = null | |
| , results = null | |
| , initial = null | |
| , ending = null | |
| , stream = null | |
| , chunk = null | |
| , ended = null | |
| , nonce = null | |
| , query = null | |
| , final = null | |
| const connection = { | |
| queue: queues.closed, | |
| idleTimer, | |
| connect(query) { | |
| initial = query | |
| reconnect() | |
| }, | |
| terminate, | |
| execute, | |
| cancel, | |
| end, | |
| count: 0, | |
| id | |
| } | |
| queues.closed && queues.closed.push(connection) | |
| return connection | |
| async function createSocket() { | |
| let x | |
| try { | |
| x = options.socket | |
| ? (await Promise.resolve(options.socket(options))) | |
| : new net.Socket() | |
| } catch (e) { | |
| error(e) | |
| return | |
| } | |
| x.on('error', error) | |
| x.on('close', closed) | |
| x.on('drain', drain) | |
| return x | |
| } | |
| async function cancel({ pid, secret }, resolve, reject) { | |
| try { | |
| cancelMessage = b().i32(16).i32(80877102).i32(pid).i32(secret).end(16) | |
| await connect() | |
| socket.once('error', reject) | |
| socket.once('close', resolve) | |
| } catch (error) { | |
| reject(error) | |
| } | |
| } | |
| function execute(q) { | |
| if (terminated) | |
| return queryError(q, Errors.connection('CONNECTION_DESTROYED', options)) | |
| if (stream) | |
| return queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy')) | |
| if (q.cancelled) | |
| return | |
| try { | |
| q.state = backend | |
| query | |
| ? sent.push(q) | |
| : (query = q, query.active = true) | |
| build(q) | |
| return write(toBuffer(q)) | |
| && !q.describeFirst | |
| && !q.cursorFn | |
| && sent.length < max_pipeline | |
| && (!q.options.onexecute || q.options.onexecute(connection)) | |
| } catch (error) { | |
| sent.length === 0 && write(Sync) | |
| errored(error) | |
| return true | |
| } | |
| } | |
| function toBuffer(q) { | |
| if (q.parameters.length >= 65534) | |
| throw Errors.generic('MAX_PARAMETERS_EXCEEDED', 'Max number of parameters (65534) exceeded') | |
| return q.options.simple | |
| ? b().Q().str(q.statement.string + b.N).end() | |
| : q.describeFirst | |
| ? Buffer.concat([describe(q), Flush]) | |
| : q.prepare | |
| ? q.prepared | |
| ? prepared(q) | |
| : Buffer.concat([describe(q), prepared(q)]) | |
| : unnamed(q) | |
| } | |
| function describe(q) { | |
| return Buffer.concat([ | |
| Parse(q.statement.string, q.parameters, q.statement.types, q.statement.name), | |
| Describe('S', q.statement.name) | |
| ]) | |
| } | |
| function prepared(q) { | |
| return Buffer.concat([ | |
| Bind(q.parameters, q.statement.types, q.statement.name, q.cursorName), | |
| q.cursorFn | |
| ? Execute('', q.cursorRows) | |
| : ExecuteUnnamed | |
| ]) | |
| } | |
| function unnamed(q) { | |
| return Buffer.concat([ | |
| Parse(q.statement.string, q.parameters, q.statement.types), | |
| DescribeUnnamed, | |
| prepared(q) | |
| ]) | |
| } | |
| function build(q) { | |
| const parameters = [] | |
| , types = [] | |
| const string = stringify(q, q.strings[0], q.args[0], parameters, types, options) | |
| !q.tagged && q.args.forEach(x => handleValue(x, parameters, types, options)) | |
| q.prepare = options.prepare && ('prepare' in q.options ? q.options.prepare : true) | |
| q.string = string | |
| q.signature = q.prepare && types + string | |
| q.onlyDescribe && (delete statements[q.signature]) | |
| q.parameters = q.parameters || parameters | |
| q.prepared = q.prepare && q.signature in statements | |
| q.describeFirst = q.onlyDescribe || (parameters.length && !q.prepared) | |
| q.statement = q.prepared | |
| ? statements[q.signature] | |
| : { string, types, name: q.prepare ? statementId + statementCount++ : '' } | |
| typeof options.debug === 'function' && options.debug(id, string, parameters, types) | |
| } | |
| function write(x, fn) { | |
| chunk = chunk ? Buffer.concat([chunk, x]) : Buffer.from(x) | |
| if (fn || chunk.length >= 1024) | |
| return nextWrite(fn) | |
| nextWriteTimer === null && (nextWriteTimer = setImmediate(nextWrite)) | |
| return true | |
| } | |
| function nextWrite(fn) { | |
| const x = socket.write(chunk, fn) | |
| nextWriteTimer !== null && clearImmediate(nextWriteTimer) | |
| chunk = nextWriteTimer = null | |
| return x | |
| } | |
| function connectTimedOut() { | |
| errored(Errors.connection('CONNECT_TIMEOUT', options, socket)) | |
| socket.destroy() | |
| } | |
| async function secure() { | |
| if (sslnegotiation !== 'direct') { | |
| write(SSLRequest) | |
| const canSSL = await new Promise(r => socket.once('data', x => r(x[0] === 83))) // S | |
| if (!canSSL && ssl === 'prefer') | |
| return connected() | |
| } | |
| const options = { | |
| socket, | |
| servername: net.isIP(socket.host) ? undefined : socket.host | |
| } | |
| if (sslnegotiation === 'direct') | |
| options.ALPNProtocols = ['postgresql'] | |
| if (ssl === 'require' || ssl === 'allow' || ssl === 'prefer') | |
| options.rejectUnauthorized = false | |
| else if (typeof ssl === 'object') | |
| Object.assign(options, ssl) | |
| socket.removeAllListeners() | |
| socket = tls.connect(options) | |
| socket.on('secureConnect', connected) | |
| socket.on('error', error) | |
| socket.on('close', closed) | |
| socket.on('drain', drain) | |
| } | |
| /* c8 ignore next 3 */ | |
| function drain() { | |
| !query && onopen(connection) | |
| } | |
| function data(x) { | |
| if (incomings) { | |
| incomings.push(x) | |
| remaining -= x.length | |
| if (remaining > 0) | |
| return | |
| } | |
| incoming = incomings | |
| ? Buffer.concat(incomings, length - remaining) | |
| : incoming.length === 0 | |
| ? x | |
| : Buffer.concat([incoming, x], incoming.length + x.length) | |
| while (incoming.length > 4) { | |
| length = incoming.readUInt32BE(1) | |
| if (length >= incoming.length) { | |
| remaining = length - incoming.length | |
| incomings = [incoming] | |
| break | |
| } | |
| try { | |
| handle(incoming.subarray(0, length + 1)) | |
| } catch (e) { | |
| query && (query.cursorFn || query.describeFirst) && write(Sync) | |
| errored(e) | |
| } | |
| incoming = incoming.subarray(length + 1) | |
| remaining = 0 | |
| incomings = null | |
| } | |
| } | |
| async function connect() { | |
| terminated = false | |
| backendParameters = {} | |
| socket || (socket = await createSocket()) | |
| if (!socket) | |
| return | |
| connectTimer.start() | |
| if (options.socket) | |
| return ssl ? secure() : connected() | |
| socket.on('connect', ssl ? secure : connected) | |
| if (options.path) | |
| return socket.connect(options.path) | |
| socket.ssl = ssl | |
| socket.connect(port[hostIndex], host[hostIndex]) | |
| socket.host = host[hostIndex] | |
| socket.port = port[hostIndex] | |
| hostIndex = (hostIndex + 1) % port.length | |
| } | |
| function reconnect() { | |
| setTimeout(connect, closedTime ? Math.max(0, closedTime + delay - performance.now()) : 0) | |
| } | |
| function connected() { | |
| try { | |
| statements = {} | |
| needsTypes = options.fetch_types | |
| statementId = Math.random().toString(36).slice(2) | |
| statementCount = 1 | |
| lifeTimer.start() | |
| socket.on('data', data) | |
| keep_alive && socket.setKeepAlive && socket.setKeepAlive(true, 1000 * keep_alive) | |
| const s = StartupMessage() | |
| write(s) | |
| } catch (err) { | |
| error(err) | |
| } | |
| } | |
| function error(err) { | |
| if (connection.queue === queues.connecting && options.host[retries + 1]) | |
| return | |
| errored(err) | |
| while (sent.length) | |
| queryError(sent.shift(), err) | |
| } | |
| function errored(err) { | |
| stream && (stream.destroy(err), stream = null) | |
| query && queryError(query, err) | |
| initial && (queryError(initial, err), initial = null) | |
| } | |
| function queryError(query, err) { | |
| if (query.reserve) | |
| return query.reject(err) | |
| if (!err || typeof err !== 'object') | |
| err = new Error(err) | |
| 'query' in err || 'parameters' in err || Object.defineProperties(err, { | |
| stack: { value: err.stack + query.origin.replace(/.*\n/, '\n'), enumerable: options.debug }, | |
| query: { value: query.string, enumerable: options.debug }, | |
| parameters: { value: query.parameters, enumerable: options.debug }, | |
| args: { value: query.args, enumerable: options.debug }, | |
| types: { value: query.statement && query.statement.types, enumerable: options.debug } | |
| }) | |
| query.reject(err) | |
| } | |
| function end() { | |
| return ending || ( | |
| !connection.reserved && onend(connection), | |
| !connection.reserved && !initial && !query && sent.length === 0 | |
| ? (terminate(), new Promise(r => socket && socket.readyState !== 'closed' ? socket.once('close', r) : r())) | |
| : ending = new Promise(r => ended = r) | |
| ) | |
| } | |
| function terminate() { | |
| terminated = true | |
| if (stream || query || initial || sent.length) | |
| error(Errors.connection('CONNECTION_DESTROYED', options)) | |
| clearImmediate(nextWriteTimer) | |
| if (socket) { | |
| socket.removeListener('data', data) | |
| socket.removeListener('connect', connected) | |
| socket.readyState === 'open' && socket.end(b().X().end()) | |
| } | |
| ended && (ended(), ending = ended = null) | |
| } | |
| async function closed(hadError) { | |
| incoming = Buffer.alloc(0) | |
| remaining = 0 | |
| incomings = null | |
| clearImmediate(nextWriteTimer) | |
| socket.removeListener('data', data) | |
| socket.removeListener('connect', connected) | |
| idleTimer.cancel() | |
| lifeTimer.cancel() | |
| connectTimer.cancel() | |
| socket.removeAllListeners() | |
| socket = null | |
| if (initial) | |
| return reconnect() | |
| !hadError && (query || sent.length) && error(Errors.connection('CONNECTION_CLOSED', options, socket)) | |
| closedTime = performance.now() | |
| hadError && options.shared.retries++ | |
| delay = (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000 | |
| onclose(connection, Errors.connection('CONNECTION_CLOSED', options, socket)) | |
| } | |
| /* Handlers */ | |
| function handle(xs, x = xs[0]) { | |
| ( | |
| x === 68 ? DataRow : // D | |
| x === 100 ? CopyData : // d | |
| x === 65 ? NotificationResponse : // A | |
| x === 83 ? ParameterStatus : // S | |
| x === 90 ? ReadyForQuery : // Z | |
| x === 67 ? CommandComplete : // C | |
| x === 50 ? BindComplete : // 2 | |
| x === 49 ? ParseComplete : // 1 | |
| x === 116 ? ParameterDescription : // t | |
| x === 84 ? RowDescription : // T | |
| x === 82 ? Authentication : // R | |
| x === 110 ? NoData : // n | |
| x === 75 ? BackendKeyData : // K | |
| x === 69 ? ErrorResponse : // E | |
| x === 115 ? PortalSuspended : // s | |
| x === 51 ? CloseComplete : // 3 | |
| x === 71 ? CopyInResponse : // G | |
| x === 78 ? NoticeResponse : // N | |
| x === 72 ? CopyOutResponse : // H | |
| x === 99 ? CopyDone : // c | |
| x === 73 ? EmptyQueryResponse : // I | |
| x === 86 ? FunctionCallResponse : // V | |
| x === 118 ? NegotiateProtocolVersion : // v | |
| x === 87 ? CopyBothResponse : // W | |
| /* c8 ignore next */ | |
| UnknownMessage | |
| )(xs) | |
| } | |
| function DataRow(x) { | |
| let index = 7 | |
| let length | |
| let column | |
| let value | |
| const row = query.isRaw ? new Array(query.statement.columns.length) : {} | |
| for (let i = 0; i < query.statement.columns.length; i++) { | |
| column = query.statement.columns[i] | |
| length = x.readInt32BE(index) | |
| index += 4 | |
| value = length === -1 | |
| ? null | |
| : query.isRaw === true | |
| ? x.subarray(index, index += length) | |
| : column.parser === undefined | |
| ? x.toString('utf8', index, index += length) | |
| : column.parser.array === true | |
| ? column.parser(x.toString('utf8', index + 1, index += length)) | |
| : column.parser(x.toString('utf8', index, index += length)) | |
| query.isRaw | |
| ? (row[i] = query.isRaw === true | |
| ? value | |
| : transform.value.from ? transform.value.from(value, column) : value) | |
| : (row[column.name] = transform.value.from ? transform.value.from(value, column) : value) | |
| } | |
| query.forEachFn | |
| ? query.forEachFn(transform.row.from ? transform.row.from(row) : row, result) | |
| : (result[rows++] = transform.row.from ? transform.row.from(row) : row) | |
| } | |
| function ParameterStatus(x) { | |
| const [k, v] = x.toString('utf8', 5, x.length - 1).split(b.N) | |
| backendParameters[k] = v | |
| if (options.parameters[k] !== v) { | |
| options.parameters[k] = v | |
| onparameter && onparameter(k, v) | |
| } | |
| } | |
| function ReadyForQuery(x) { | |
| if (query) { | |
| if (errorResponse) { | |
| query.retried | |
| ? errored(query.retried) | |
| : query.prepared && retryRoutines.has(errorResponse.routine) | |
| ? retry(query, errorResponse) | |
| : errored(errorResponse) | |
| } else { | |
| query.resolve(results || result) | |
| } | |
| } else if (errorResponse) { | |
| errored(errorResponse) | |
| } | |
| query = results = errorResponse = null | |
| result = new Result() | |
| connectTimer.cancel() | |
| if (initial) { | |
| if (target_session_attrs) { | |
| if (!backendParameters.in_hot_standby || !backendParameters.default_transaction_read_only) | |
| return fetchState() | |
| else if (tryNext(target_session_attrs, backendParameters)) | |
| return terminate() | |
| } | |
| if (needsTypes) { | |
| initial.reserve && (initial = null) | |
| return fetchArrayTypes() | |
| } | |
| initial && !initial.reserve && execute(initial) | |
| options.shared.retries = retries = 0 | |
| initial = null | |
| return | |
| } | |
| while (sent.length && (query = sent.shift()) && (query.active = true, query.cancelled)) | |
| Connection(options).cancel(query.state, query.cancelled.resolve, query.cancelled.reject) | |
| if (query) | |
| return // Consider opening if able and sent.length < 50 | |
| connection.reserved | |
| ? !connection.reserved.release && x[5] === 73 // I | |
| ? ending | |
| ? terminate() | |
| : (connection.reserved = null, onopen(connection)) | |
| : connection.reserved() | |
| : ending | |
| ? terminate() | |
| : onopen(connection) | |
| } | |
| function CommandComplete(x) { | |
| rows = 0 | |
| for (let i = x.length - 1; i > 0; i--) { | |
| if (x[i] === 32 && x[i + 1] < 58 && result.count === null) | |
| result.count = +x.toString('utf8', i + 1, x.length - 1) | |
| if (x[i - 1] >= 65) { | |
| result.command = x.toString('utf8', 5, i) | |
| result.state = backend | |
| break | |
| } | |
| } | |
| final && (final(), final = null) | |
| if (result.command === 'BEGIN' && max !== 1 && !connection.reserved) | |
| return errored(Errors.generic('UNSAFE_TRANSACTION', 'Only use sql.begin, sql.reserved or max: 1')) | |
| if (query.options.simple) | |
| return BindComplete() | |
| if (query.cursorFn) { | |
| result.count && query.cursorFn(result) | |
| write(Sync) | |
| } | |
| } | |
| function ParseComplete() { | |
| query.parsing = false | |
| } | |
| function BindComplete() { | |
| !result.statement && (result.statement = query.statement) | |
| result.columns = query.statement.columns | |
| } | |
| function ParameterDescription(x) { | |
| const length = x.readUInt16BE(5) | |
| for (let i = 0; i < length; ++i) | |
| !query.statement.types[i] && (query.statement.types[i] = x.readUInt32BE(7 + i * 4)) | |
| query.prepare && (statements[query.signature] = query.statement) | |
| query.describeFirst && !query.onlyDescribe && (write(prepared(query)), query.describeFirst = false) | |
| } | |
| function RowDescription(x) { | |
| if (result.command) { | |
| results = results || [result] | |
| results.push(result = new Result()) | |
| result.count = null | |
| query.statement.columns = null | |
| } | |
| const length = x.readUInt16BE(5) | |
| let index = 7 | |
| let start | |
| query.statement.columns = Array(length) | |
| for (let i = 0; i < length; ++i) { | |
| start = index | |
| while (x[index++] !== 0); | |
| const table = x.readUInt32BE(index) | |
| const number = x.readUInt16BE(index + 4) | |
| const type = x.readUInt32BE(index + 6) | |
| query.statement.columns[i] = { | |
| name: transform.column.from | |
| ? transform.column.from(x.toString('utf8', start, index - 1)) | |
| : x.toString('utf8', start, index - 1), | |
| parser: parsers[type], | |
| table, | |
| number, | |
| type | |
| } | |
| index += 18 | |
| } | |
| result.statement = query.statement | |
| if (query.onlyDescribe) | |
| return (query.resolve(query.statement), write(Sync)) | |
| } | |
| async function Authentication(x, type = x.readUInt32BE(5)) { | |
| ( | |
| type === 3 ? AuthenticationCleartextPassword : | |
| type === 5 ? AuthenticationMD5Password : | |
| type === 10 ? SASL : | |
| type === 11 ? SASLContinue : | |
| type === 12 ? SASLFinal : | |
| type !== 0 ? UnknownAuth : | |
| noop | |
| )(x, type) | |
| } | |
| /* c8 ignore next 5 */ | |
| async function AuthenticationCleartextPassword() { | |
| const payload = await Pass() | |
| write( | |
| b().p().str(payload).z(1).end() | |
| ) | |
| } | |
| async function AuthenticationMD5Password(x) { | |
| const payload = 'md5' + ( | |
| await md5( | |
| Buffer.concat([ | |
| Buffer.from(await md5((await Pass()) + user)), | |
| x.subarray(9) | |
| ]) | |
| ) | |
| ) | |
| write( | |
| b().p().str(payload).z(1).end() | |
| ) | |
| } | |
| async function SASL() { | |
| nonce = (await crypto.randomBytes(18)).toString('base64') | |
| b().p().str('SCRAM-SHA-256' + b.N) | |
| const i = b.i | |
| write(b.inc(4).str('n,,n=*,r=' + nonce).i32(b.i - i - 4, i).end()) | |
| } | |
| async function SASLContinue(x) { | |
| const res = x.toString('utf8', 9).split(',').reduce((acc, x) => (acc[x[0]] = x.slice(2), acc), {}) | |
| const saltedPassword = await crypto.pbkdf2Sync( | |
| await Pass(), | |
| Buffer.from(res.s, 'base64'), | |
| parseInt(res.i), 32, | |
| 'sha256' | |
| ) | |
| const clientKey = await hmac(saltedPassword, 'Client Key') | |
| const auth = 'n=*,r=' + nonce + ',' | |
| + 'r=' + res.r + ',s=' + res.s + ',i=' + res.i | |
| + ',c=biws,r=' + res.r | |
| serverSignature = (await hmac(await hmac(saltedPassword, 'Server Key'), auth)).toString('base64') | |
| const payload = 'c=biws,r=' + res.r + ',p=' + xor( | |
| clientKey, Buffer.from(await hmac(await sha256(clientKey), auth)) | |
| ).toString('base64') | |
| write( | |
| b().p().str(payload).end() | |
| ) | |
| } | |
| function SASLFinal(x) { | |
| if (x.toString('utf8', 9).split(b.N, 1)[0].slice(2) === serverSignature) | |
| return | |
| /* c8 ignore next 5 */ | |
| errored(Errors.generic('SASL_SIGNATURE_MISMATCH', 'The server did not return the correct signature')) | |
| socket.destroy() | |
| } | |
| function Pass() { | |
| return Promise.resolve(typeof options.pass === 'function' | |
| ? options.pass() | |
| : options.pass | |
| ) | |
| } | |
| function NoData() { | |
| result.statement = query.statement | |
| result.statement.columns = [] | |
| if (query.onlyDescribe) | |
| return (query.resolve(query.statement), write(Sync)) | |
| } | |
| function BackendKeyData(x) { | |
| backend.pid = x.readUInt32BE(5) | |
| backend.secret = x.readUInt32BE(9) | |
| } | |
| async function fetchArrayTypes() { | |
| needsTypes = false | |
| const types = await new Query([` | |
| select b.oid, b.typarray | |
| from pg_catalog.pg_type a | |
| left join pg_catalog.pg_type b on b.oid = a.typelem | |
| where a.typcategory = 'A' | |
| group by b.oid, b.typarray | |
| order by b.oid | |
| `], [], execute) | |
| types.forEach(({ oid, typarray }) => addArrayType(oid, typarray)) | |
| } | |
| function addArrayType(oid, typarray) { | |
| if (!!options.parsers[typarray] && !!options.serializers[typarray]) return | |
| const parser = options.parsers[oid] | |
| options.shared.typeArrayMap[oid] = typarray | |
| options.parsers[typarray] = (xs) => arrayParser(xs, parser, typarray) | |
| options.parsers[typarray].array = true | |
| options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid], options, typarray) | |
| } | |
| function tryNext(x, xs) { | |
| return ( | |
| (x === 'read-write' && xs.default_transaction_read_only === 'on') || | |
| (x === 'read-only' && xs.default_transaction_read_only === 'off') || | |
| (x === 'primary' && xs.in_hot_standby === 'on') || | |
| (x === 'standby' && xs.in_hot_standby === 'off') || | |
| (x === 'prefer-standby' && xs.in_hot_standby === 'off' && options.host[retries]) | |
| ) | |
| } | |
| function fetchState() { | |
| const query = new Query([` | |
| show transaction_read_only; | |
| select pg_catalog.pg_is_in_recovery() | |
| `], [], execute, null, { simple: true }) | |
| query.resolve = ([[a], [b]]) => { | |
| backendParameters.default_transaction_read_only = a.transaction_read_only | |
| backendParameters.in_hot_standby = b.pg_is_in_recovery ? 'on' : 'off' | |
| } | |
| query.execute() | |
| } | |
| function ErrorResponse(x) { | |
| if (query) { | |
| (query.cursorFn || query.describeFirst) && write(Sync) | |
| errorResponse = Errors.postgres(parseError(x)) | |
| } else { | |
| errored(Errors.postgres(parseError(x))) | |
| } | |
| } | |
| function retry(q, error) { | |
| delete statements[q.signature] | |
| q.retried = error | |
| execute(q) | |
| } | |
| function NotificationResponse(x) { | |
| if (!onnotify) | |
| return | |
| let index = 9 | |
| while (x[index++] !== 0); | |
| onnotify( | |
| x.toString('utf8', 9, index - 1), | |
| x.toString('utf8', index, x.length - 1) | |
| ) | |
| } | |
| async function PortalSuspended() { | |
| try { | |
| const x = await Promise.resolve(query.cursorFn(result)) | |
| rows = 0 | |
| x === CLOSE | |
| ? write(Close(query.portal)) | |
| : (result = new Result(), write(Execute('', query.cursorRows))) | |
| } catch (err) { | |
| write(Sync) | |
| query.reject(err) | |
| } | |
| } | |
| function CloseComplete() { | |
| result.count && query.cursorFn(result) | |
| query.resolve(result) | |
| } | |
| function CopyInResponse() { | |
| stream = new Stream.Writable({ | |
| autoDestroy: true, | |
| write(chunk, encoding, callback) { | |
| socket.write(b().d().raw(chunk).end(), callback) | |
| }, | |
| destroy(error, callback) { | |
| callback(error) | |
| socket.write(b().f().str(error + b.N).end()) | |
| stream = null | |
| }, | |
| final(callback) { | |
| socket.write(b().c().end()) | |
| final = callback | |
| stream = null | |
| } | |
| }) | |
| query.resolve(stream) | |
| } | |
| function CopyOutResponse() { | |
| stream = new Stream.Readable({ | |
| read() { socket.resume() } | |
| }) | |
| query.resolve(stream) | |
| } | |
| /* c8 ignore next 3 */ | |
| function CopyBothResponse() { | |
| stream = new Stream.Duplex({ | |
| autoDestroy: true, | |
| read() { socket.resume() }, | |
| /* c8 ignore next 11 */ | |
| write(chunk, encoding, callback) { | |
| socket.write(b().d().raw(chunk).end(), callback) | |
| }, | |
| destroy(error, callback) { | |
| callback(error) | |
| socket.write(b().f().str(error + b.N).end()) | |
| stream = null | |
| }, | |
| final(callback) { | |
| socket.write(b().c().end()) | |
| final = callback | |
| } | |
| }) | |
| query.resolve(stream) | |
| } | |
| function CopyData(x) { | |
| stream && (stream.push(x.subarray(5)) || socket.pause()) | |
| } | |
| function CopyDone() { | |
| stream && stream.push(null) | |
| stream = null | |
| } | |
| function NoticeResponse(x) { | |
| onnotice | |
| ? onnotice(parseError(x)) | |
| : console.log(parseError(x)) // eslint-disable-line | |
| } | |
| /* c8 ignore next 3 */ | |
| function EmptyQueryResponse() { | |
| /* noop */ | |
| } | |
| /* c8 ignore next 3 */ | |
| function FunctionCallResponse() { | |
| errored(Errors.notSupported('FunctionCallResponse')) | |
| } | |
| /* c8 ignore next 3 */ | |
| function NegotiateProtocolVersion() { | |
| errored(Errors.notSupported('NegotiateProtocolVersion')) | |
| } | |
| /* c8 ignore next 3 */ | |
| function UnknownMessage(x) { | |
| console.error('Postgres.js : Unknown Message:', x[0]) // eslint-disable-line | |
| } | |
| /* c8 ignore next 3 */ | |
| function UnknownAuth(x, type) { | |
| console.error('Postgres.js : Unknown Auth:', type) // eslint-disable-line | |
| } | |
| /* Messages */ | |
| function Bind(parameters, types, statement = '', portal = '') { | |
| let prev | |
| , type | |
| b().B().str(portal + b.N).str(statement + b.N).i16(0).i16(parameters.length) | |
| parameters.forEach((x, i) => { | |
| if (x === null) | |
| return b.i32(0xFFFFFFFF) | |
| type = types[i] | |
| parameters[i] = x = type in options.serializers | |
| ? options.serializers[type](x) | |
| : '' + x | |
| prev = b.i | |
| b.inc(4).str(x).i32(b.i - prev - 4, prev) | |
| }) | |
| b.i16(0) | |
| return b.end() | |
| } | |
| function Parse(str, parameters, types, name = '') { | |
| b().P().str(name + b.N).str(str + b.N).i16(parameters.length) | |
| parameters.forEach((x, i) => b.i32(types[i] || 0)) | |
| return b.end() | |
| } | |
| function Describe(x, name = '') { | |
| return b().D().str(x).str(name + b.N).end() | |
| } | |
| function Execute(portal = '', rows = 0) { | |
| return Buffer.concat([ | |
| b().E().str(portal + b.N).i32(rows).end(), | |
| Flush | |
| ]) | |
| } | |
| function Close(portal = '') { | |
| return Buffer.concat([ | |
| b().C().str('P').str(portal + b.N).end(), | |
| b().S().end() | |
| ]) | |
| } | |
| function StartupMessage() { | |
| return cancelMessage || b().inc(4).i16(3).z(2).str( | |
| Object.entries(Object.assign({ | |
| user, | |
| database, | |
| client_encoding: 'UTF8' | |
| }, | |
| options.connection | |
| )).filter(([, v]) => v).map(([k, v]) => k + b.N + v).join(b.N) | |
| ).z(2).end(0) | |
| } | |
| } | |
| function parseError(x) { | |
| const error = {} | |
| let start = 5 | |
| for (let i = 5; i < x.length - 1; i++) { | |
| if (x[i] === 0) { | |
| error[errorFields[x[start]]] = x.toString('utf8', start + 1, i) | |
| start = i + 1 | |
| } | |
| } | |
| return error | |
| } | |
| function md5(x) { | |
| return crypto.createHash('md5').update(x).digest('hex') | |
| } | |
| function hmac(key, x) { | |
| return crypto.createHmac('sha256', key).update(x).digest() | |
| } | |
| function sha256(x) { | |
| return crypto.createHash('sha256').update(x).digest() | |
| } | |
| function xor(a, b) { | |
| const length = Math.max(a.length, b.length) | |
| const buffer = Buffer.allocUnsafe(length) | |
| for (let i = 0; i < length; i++) | |
| buffer[i] = a[i] ^ b[i] | |
| return buffer | |
| } | |
| function timer(fn, seconds) { | |
| seconds = typeof seconds === 'function' ? seconds() : seconds | |
| if (!seconds) | |
| return { cancel: noop, start: noop } | |
| let timer | |
| return { | |
| cancel() { | |
| timer && (clearTimeout(timer), timer = null) | |
| }, | |
| start() { | |
| timer && clearTimeout(timer) | |
| timer = setTimeout(done, seconds * 1000, arguments) | |
| } | |
| } | |
| function done(args) { | |
| fn.apply(null, args) | |
| timer = null | |
| } | |
| } | |