Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
cjs
254 changes: 254 additions & 0 deletions deno/lib/backend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import { errors } from './errors.js'
import { entries, errorFields } from './types.js'

const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
, N = '\u0000'

export default Backend

function Backend({
onparse,
onparameter,
onsuspended,
oncomplete,
onerror,
parsers,
onauth,
onready,
oncopy,
ondata,
transform,
onnotice,
onnotify
}) {
let rows = 0

const backend = entries({
1: ParseComplete,
2: BindComplete,
3: CloseComplete,
A: NotificationResponse,
C: CommandComplete,
c: CopyDone,
D: DataRow,
d: CopyData,
E: ErrorResponse,
G: CopyInResponse,
H: CopyOutResponse,
I: EmptyQueryResponse,
K: BackendKeyData,
N: NoticeResponse,
n: NoData,
R: Authentication,
S: ParameterStatus,
s: PortalSuspended,
T: RowDescription,
t: ParameterDescription,
V: FunctionCallResponse,
v: NegotiateProtocolVersion,
W: CopyBothResponse,
Z: ReadyForQuery
}).reduce(char, {})

const state = backend.state = {
status : 'I',
pid : null,
secret : null
}

function ParseComplete() {
onparse()
}

/* c8 ignore next 2 */
function BindComplete() { /* No handling needed */ }
function CloseComplete() { /* No handling needed */ }

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)
)
}

function CommandComplete(x) {
if (!backend.query)
return

for (let i = x.length - 1; i > 0; i--) {
if (x[i] === 32 && x[i + 1] < 58 && backend.query.result.count === null)
backend.query.result.count = +x.toString('utf8', i + 1, x.length - 1)
if (x[i - 1] >= 65) {
backend.query.result.command = x.toString('utf8', 5, i)
backend.query.result.state = state
break
}
}

oncomplete()
}

/* c8 ignore next 3 */
function CopyDone() {
backend.query.readable.push(null)
}

function DataRow(x) {
let index = 7
let length
let column
let value

const row = backend.query.raw ? new Array(backend.query.statement.columns.length) : {}
for (let i = 0; i < backend.query.statement.columns.length; i++) {
column = backend.query.statement.columns[i]
length = x.readInt32BE(index)
index += 4

value = length === -1
? null
: backend.query.raw
? x.slice(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))

backend.query.raw
? (row[i] = value)
: (row[column.name] = transform.value.from ? transform.value.from(value) : value)
}

backend.query.stream
? backend.query.stream(transform.row.from ? transform.row.from(row) : row, backend.query.result)
: (backend.query.result[rows++] = transform.row.from ? transform.row.from(row) : row)
}

/* c8 ignore next 3 */
function CopyData(x) {
ondata(x.slice(5))
}

function ErrorResponse(x) {
onerror(errors.postgres(parseError(x)))
}

/* c8 ignore next 3 */
function CopyInResponse() {
oncopy()
}

/* c8 ignore next 3 */
function CopyOutResponse() { /* No handling needed */ }

/* c8 ignore next 3 */
function EmptyQueryResponse() { /* No handling needed */ }

function BackendKeyData(x) {
state.pid = x.readInt32BE(5)
state.secret = x.readInt32BE(9)
}

function NoticeResponse(x) {
onnotice
? onnotice(parseError(x))
: console.log(parseError(x)) // eslint-disable-line
}

function NoData() { /* No handling needed */ }

function Authentication(x) {
const type = x.readInt32BE(5)
type !== 0 && onauth(type, x, onerror)
}

function ParameterStatus(x) {
const [k, v] = x.toString('utf8', 5, x.length - 1).split(N)
onparameter(k, v)
}

function PortalSuspended() {
onsuspended(backend.query.result)
backend.query.result = []
rows = 0
}

/* c8 ignore next 3 */
function ParameterDescription() {
backend.error = errors.notSupported('ParameterDescription')
}

function RowDescription(x) {
if (backend.query.result.command) {
backend.query.results = backend.query.results || [backend.query.result]
backend.query.results.push(backend.query.result = [])
backend.query.result.count = null
backend.query.statement.columns = null
}

rows = 0

if (backend.query.statement.columns)
return backend.query.result.columns = backend.query.statement.columns

const length = x.readInt16BE(5)
let index = 7
let start

backend.query.statement.columns = Array(length)

for (let i = 0; i < length; ++i) {
start = index
while (x[index++] !== 0);
const type = x.readInt32BE(index + 6)
backend.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],
type
}
index += 18
}
backend.query.result.columns = backend.query.statement.columns
}

/* c8 ignore next 3 */
function FunctionCallResponse() {
backend.error = errors.notSupported('FunctionCallResponse')
}

/* c8 ignore next 3 */
function NegotiateProtocolVersion() {
backend.error = errors.notSupported('NegotiateProtocolVersion')
}

/* c8 ignore next 3 */
function CopyBothResponse() {
oncopy()
}

function ReadyForQuery() {
onready(backend.error)
}

return backend
}

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
}
73 changes: 73 additions & 0 deletions deno/lib/bytes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Buffer } from 'https://deno.land/[email protected]/node/buffer.ts'
const size = 256
let buffer = Buffer.allocUnsafe(size)

const messages = ['B', 'C', 'Q', 'P', 'F', 'p', 'D', 'E', 'H', 'S', 'd', 'c', 'f', 'X'].reduce((acc, x) => {
const v = x.charCodeAt(0)
acc[x] = () => {
buffer[0] = v
b.i = 5
return b
}
return acc
}, {})

const b = Object.assign(messages, {
i: 0,
inc(x) {
b.i += x
return b
},
str(x) {
const length = Buffer.byteLength(x)
fit(length)
b.i += buffer.write(x, b.i, length, 'utf8')
return b
},
i16(x) {
fit(2)
buffer.writeUInt16BE(x, b.i)
b.i += 2
return b
},
i32(x, i) {
if (i || i === 0) {
buffer.writeUInt32BE(x, i)
return b
}
fit(4)
buffer.writeUInt32BE(x, b.i)
b.i += 4
return b
},
z(x) {
fit(x)
buffer.fill(0, b.i, b.i + x)
b.i += x
return b
},
raw(x) {
buffer = Buffer.concat([buffer.slice(0, b.i), x])
b.i = buffer.length
return b
},
end(at = 1) {
buffer.writeUInt32BE(b.i - at, at)
const out = buffer.slice(0, b.i)
b.i = 0
buffer = Buffer.allocUnsafe(size)
return out
}
})

export default b

function fit(x) {
if (buffer.length - b.i < x) {
const prev = buffer
, length = prev.length

buffer = Buffer.allocUnsafe(length + (length >> 1) + x)
prev.copy(buffer)
}
}
Loading