mirror of
https://github.com/xfarrow/blink
synced 2025-06-27 09:03:02 +02:00
Change endpoint from persons to people
This commit is contained in:
25
backend/apis/nodejs/node_modules/pg-protocol/src/b.ts
generated
vendored
Normal file
25
backend/apis/nodejs/node_modules/pg-protocol/src/b.ts
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// file for microbenchmarking
|
||||
|
||||
import { BufferReader } from './buffer-reader'
|
||||
|
||||
const LOOPS = 1000
|
||||
let count = 0
|
||||
let start = Date.now()
|
||||
|
||||
const reader = new BufferReader()
|
||||
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0])
|
||||
|
||||
const run = () => {
|
||||
if (count > LOOPS) {
|
||||
console.log(Date.now() - start)
|
||||
return
|
||||
}
|
||||
count++
|
||||
for (let i = 0; i < LOOPS; i++) {
|
||||
reader.setBuffer(0, buffer)
|
||||
reader.cstring()
|
||||
}
|
||||
setImmediate(run)
|
||||
}
|
||||
|
||||
run()
|
59
backend/apis/nodejs/node_modules/pg-protocol/src/buffer-reader.ts
generated
vendored
Normal file
59
backend/apis/nodejs/node_modules/pg-protocol/src/buffer-reader.ts
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
const emptyBuffer = Buffer.allocUnsafe(0)
|
||||
|
||||
export class BufferReader {
|
||||
private buffer: Buffer = emptyBuffer
|
||||
|
||||
// TODO(bmc): support non-utf8 encoding?
|
||||
private encoding: string = 'utf-8'
|
||||
|
||||
constructor(private offset: number = 0) {}
|
||||
|
||||
public setBuffer(offset: number, buffer: Buffer): void {
|
||||
this.offset = offset
|
||||
this.buffer = buffer
|
||||
}
|
||||
|
||||
public int16(): number {
|
||||
const result = this.buffer.readInt16BE(this.offset)
|
||||
this.offset += 2
|
||||
return result
|
||||
}
|
||||
|
||||
public byte(): number {
|
||||
const result = this.buffer[this.offset]
|
||||
this.offset++
|
||||
return result
|
||||
}
|
||||
|
||||
public int32(): number {
|
||||
const result = this.buffer.readInt32BE(this.offset)
|
||||
this.offset += 4
|
||||
return result
|
||||
}
|
||||
|
||||
public uint32(): number {
|
||||
const result = this.buffer.readUInt32BE(this.offset)
|
||||
this.offset += 4
|
||||
return result
|
||||
}
|
||||
|
||||
public string(length: number): string {
|
||||
const result = this.buffer.toString(this.encoding, this.offset, this.offset + length)
|
||||
this.offset += length
|
||||
return result
|
||||
}
|
||||
|
||||
public cstring(): string {
|
||||
const start = this.offset
|
||||
let end = start
|
||||
while (this.buffer[end++] !== 0) {}
|
||||
this.offset = end
|
||||
return this.buffer.toString(this.encoding, start, end - 1)
|
||||
}
|
||||
|
||||
public bytes(length: number): Buffer {
|
||||
const result = this.buffer.slice(this.offset, this.offset + length)
|
||||
this.offset += length
|
||||
return result
|
||||
}
|
||||
}
|
85
backend/apis/nodejs/node_modules/pg-protocol/src/buffer-writer.ts
generated
vendored
Normal file
85
backend/apis/nodejs/node_modules/pg-protocol/src/buffer-writer.ts
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
//binary data writer tuned for encoding binary specific to the postgres binary protocol
|
||||
|
||||
export class Writer {
|
||||
private buffer: Buffer
|
||||
private offset: number = 5
|
||||
private headerPosition: number = 0
|
||||
constructor(private size = 256) {
|
||||
this.buffer = Buffer.allocUnsafe(size)
|
||||
}
|
||||
|
||||
private ensure(size: number): void {
|
||||
var remaining = this.buffer.length - this.offset
|
||||
if (remaining < size) {
|
||||
var oldBuffer = this.buffer
|
||||
// exponential growth factor of around ~ 1.5
|
||||
// https://stackoverflow.com/questions/2269063/buffer-growth-strategy
|
||||
var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size
|
||||
this.buffer = Buffer.allocUnsafe(newSize)
|
||||
oldBuffer.copy(this.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
public addInt32(num: number): Writer {
|
||||
this.ensure(4)
|
||||
this.buffer[this.offset++] = (num >>> 24) & 0xff
|
||||
this.buffer[this.offset++] = (num >>> 16) & 0xff
|
||||
this.buffer[this.offset++] = (num >>> 8) & 0xff
|
||||
this.buffer[this.offset++] = (num >>> 0) & 0xff
|
||||
return this
|
||||
}
|
||||
|
||||
public addInt16(num: number): Writer {
|
||||
this.ensure(2)
|
||||
this.buffer[this.offset++] = (num >>> 8) & 0xff
|
||||
this.buffer[this.offset++] = (num >>> 0) & 0xff
|
||||
return this
|
||||
}
|
||||
|
||||
public addCString(string: string): Writer {
|
||||
if (!string) {
|
||||
this.ensure(1)
|
||||
} else {
|
||||
var len = Buffer.byteLength(string)
|
||||
this.ensure(len + 1) // +1 for null terminator
|
||||
this.buffer.write(string, this.offset, 'utf-8')
|
||||
this.offset += len
|
||||
}
|
||||
|
||||
this.buffer[this.offset++] = 0 // null terminator
|
||||
return this
|
||||
}
|
||||
|
||||
public addString(string: string = ''): Writer {
|
||||
var len = Buffer.byteLength(string)
|
||||
this.ensure(len)
|
||||
this.buffer.write(string, this.offset)
|
||||
this.offset += len
|
||||
return this
|
||||
}
|
||||
|
||||
public add(otherBuffer: Buffer): Writer {
|
||||
this.ensure(otherBuffer.length)
|
||||
otherBuffer.copy(this.buffer, this.offset)
|
||||
this.offset += otherBuffer.length
|
||||
return this
|
||||
}
|
||||
|
||||
private join(code?: number): Buffer {
|
||||
if (code) {
|
||||
this.buffer[this.headerPosition] = code
|
||||
//length is everything in this packet minus the code
|
||||
const length = this.offset - (this.headerPosition + 1)
|
||||
this.buffer.writeInt32BE(length, this.headerPosition + 1)
|
||||
}
|
||||
return this.buffer.slice(code ? 0 : 5, this.offset)
|
||||
}
|
||||
|
||||
public flush(code?: number): Buffer {
|
||||
var result = this.join(code)
|
||||
this.offset = 5
|
||||
this.headerPosition = 0
|
||||
this.buffer = Buffer.allocUnsafe(this.size)
|
||||
return result
|
||||
}
|
||||
}
|
576
backend/apis/nodejs/node_modules/pg-protocol/src/inbound-parser.test.ts
generated
vendored
Normal file
576
backend/apis/nodejs/node_modules/pg-protocol/src/inbound-parser.test.ts
generated
vendored
Normal file
@ -0,0 +1,576 @@
|
||||
import buffers from './testing/test-buffers'
|
||||
import BufferList from './testing/buffer-list'
|
||||
import { parse } from '.'
|
||||
import assert from 'assert'
|
||||
import { PassThrough } from 'stream'
|
||||
import { BackendMessage } from './messages'
|
||||
|
||||
var authOkBuffer = buffers.authenticationOk()
|
||||
var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8')
|
||||
var readyForQueryBuffer = buffers.readyForQuery()
|
||||
var backendKeyDataBuffer = buffers.backendKeyData(1, 2)
|
||||
var commandCompleteBuffer = buffers.commandComplete('SELECT 3')
|
||||
var parseCompleteBuffer = buffers.parseComplete()
|
||||
var bindCompleteBuffer = buffers.bindComplete()
|
||||
var portalSuspendedBuffer = buffers.portalSuspended()
|
||||
|
||||
var row1 = {
|
||||
name: 'id',
|
||||
tableID: 1,
|
||||
attributeNumber: 2,
|
||||
dataTypeID: 3,
|
||||
dataTypeSize: 4,
|
||||
typeModifier: 5,
|
||||
formatCode: 0,
|
||||
}
|
||||
var oneRowDescBuff = buffers.rowDescription([row1])
|
||||
row1.name = 'bang'
|
||||
|
||||
var twoRowBuf = buffers.rowDescription([
|
||||
row1,
|
||||
{
|
||||
name: 'whoah',
|
||||
tableID: 10,
|
||||
attributeNumber: 11,
|
||||
dataTypeID: 12,
|
||||
dataTypeSize: 13,
|
||||
typeModifier: 14,
|
||||
formatCode: 0,
|
||||
},
|
||||
])
|
||||
|
||||
var rowWithBigOids = {
|
||||
name: 'bigoid',
|
||||
tableID: 3000000001,
|
||||
attributeNumber: 2,
|
||||
dataTypeID: 3000000003,
|
||||
dataTypeSize: 4,
|
||||
typeModifier: 5,
|
||||
formatCode: 0,
|
||||
}
|
||||
var bigOidDescBuff = buffers.rowDescription([rowWithBigOids])
|
||||
|
||||
var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D')
|
||||
|
||||
var emptyRowFieldBuf = buffers.dataRow([])
|
||||
|
||||
var oneFieldBuf = new BufferList()
|
||||
.addInt16(1) // number of fields
|
||||
.addInt32(5) // length of bytes of fields
|
||||
.addCString('test')
|
||||
.join(true, 'D')
|
||||
|
||||
var oneFieldBuf = buffers.dataRow(['test'])
|
||||
|
||||
var expectedAuthenticationOkayMessage = {
|
||||
name: 'authenticationOk',
|
||||
length: 8,
|
||||
}
|
||||
|
||||
var expectedParameterStatusMessage = {
|
||||
name: 'parameterStatus',
|
||||
parameterName: 'client_encoding',
|
||||
parameterValue: 'UTF8',
|
||||
length: 25,
|
||||
}
|
||||
|
||||
var expectedBackendKeyDataMessage = {
|
||||
name: 'backendKeyData',
|
||||
processID: 1,
|
||||
secretKey: 2,
|
||||
}
|
||||
|
||||
var expectedReadyForQueryMessage = {
|
||||
name: 'readyForQuery',
|
||||
length: 5,
|
||||
status: 'I',
|
||||
}
|
||||
|
||||
var expectedCommandCompleteMessage = {
|
||||
name: 'commandComplete',
|
||||
length: 13,
|
||||
text: 'SELECT 3',
|
||||
}
|
||||
var emptyRowDescriptionBuffer = new BufferList()
|
||||
.addInt16(0) // number of fields
|
||||
.join(true, 'T')
|
||||
|
||||
var expectedEmptyRowDescriptionMessage = {
|
||||
name: 'rowDescription',
|
||||
length: 6,
|
||||
fieldCount: 0,
|
||||
fields: [],
|
||||
}
|
||||
var expectedOneRowMessage = {
|
||||
name: 'rowDescription',
|
||||
length: 27,
|
||||
fieldCount: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'id',
|
||||
tableID: 1,
|
||||
columnID: 2,
|
||||
dataTypeID: 3,
|
||||
dataTypeSize: 4,
|
||||
dataTypeModifier: 5,
|
||||
format: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
var expectedTwoRowMessage = {
|
||||
name: 'rowDescription',
|
||||
length: 53,
|
||||
fieldCount: 2,
|
||||
fields: [
|
||||
{
|
||||
name: 'bang',
|
||||
tableID: 1,
|
||||
columnID: 2,
|
||||
dataTypeID: 3,
|
||||
dataTypeSize: 4,
|
||||
dataTypeModifier: 5,
|
||||
format: 'text',
|
||||
},
|
||||
{
|
||||
name: 'whoah',
|
||||
tableID: 10,
|
||||
columnID: 11,
|
||||
dataTypeID: 12,
|
||||
dataTypeSize: 13,
|
||||
dataTypeModifier: 14,
|
||||
format: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
var expectedBigOidMessage = {
|
||||
name: 'rowDescription',
|
||||
length: 31,
|
||||
fieldCount: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'bigoid',
|
||||
tableID: 3000000001,
|
||||
columnID: 2,
|
||||
dataTypeID: 3000000003,
|
||||
dataTypeSize: 4,
|
||||
dataTypeModifier: 5,
|
||||
format: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
var emptyParameterDescriptionBuffer = new BufferList()
|
||||
.addInt16(0) // number of parameters
|
||||
.join(true, 't')
|
||||
|
||||
var oneParameterDescBuf = buffers.parameterDescription([1111])
|
||||
|
||||
var twoParameterDescBuf = buffers.parameterDescription([2222, 3333])
|
||||
|
||||
var expectedEmptyParameterDescriptionMessage = {
|
||||
name: 'parameterDescription',
|
||||
length: 6,
|
||||
parameterCount: 0,
|
||||
dataTypeIDs: [],
|
||||
}
|
||||
|
||||
var expectedOneParameterMessage = {
|
||||
name: 'parameterDescription',
|
||||
length: 10,
|
||||
parameterCount: 1,
|
||||
dataTypeIDs: [1111],
|
||||
}
|
||||
|
||||
var expectedTwoParameterMessage = {
|
||||
name: 'parameterDescription',
|
||||
length: 14,
|
||||
parameterCount: 2,
|
||||
dataTypeIDs: [2222, 3333],
|
||||
}
|
||||
|
||||
var testForMessage = function (buffer: Buffer, expectedMessage: any) {
|
||||
it('receives and parses ' + expectedMessage.name, async () => {
|
||||
const messages = await parseBuffers([buffer])
|
||||
const [lastMessage] = messages
|
||||
|
||||
for (const key in expectedMessage) {
|
||||
assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var plainPasswordBuffer = buffers.authenticationCleartextPassword()
|
||||
var md5PasswordBuffer = buffers.authenticationMD5Password()
|
||||
var SASLBuffer = buffers.authenticationSASL()
|
||||
var SASLContinueBuffer = buffers.authenticationSASLContinue()
|
||||
var SASLFinalBuffer = buffers.authenticationSASLFinal()
|
||||
|
||||
var expectedPlainPasswordMessage = {
|
||||
name: 'authenticationCleartextPassword',
|
||||
}
|
||||
|
||||
var expectedMD5PasswordMessage = {
|
||||
name: 'authenticationMD5Password',
|
||||
salt: Buffer.from([1, 2, 3, 4]),
|
||||
}
|
||||
|
||||
var expectedSASLMessage = {
|
||||
name: 'authenticationSASL',
|
||||
mechanisms: ['SCRAM-SHA-256'],
|
||||
}
|
||||
|
||||
var expectedSASLContinueMessage = {
|
||||
name: 'authenticationSASLContinue',
|
||||
data: 'data',
|
||||
}
|
||||
|
||||
var expectedSASLFinalMessage = {
|
||||
name: 'authenticationSASLFinal',
|
||||
data: 'data',
|
||||
}
|
||||
|
||||
var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
|
||||
var expectedNotificationResponseMessage = {
|
||||
name: 'notification',
|
||||
processId: 4,
|
||||
channel: 'hi',
|
||||
payload: 'boom',
|
||||
}
|
||||
|
||||
const parseBuffers = async (buffers: Buffer[]): Promise<BackendMessage[]> => {
|
||||
const stream = new PassThrough()
|
||||
for (const buffer of buffers) {
|
||||
stream.write(buffer)
|
||||
}
|
||||
stream.end()
|
||||
const msgs: BackendMessage[] = []
|
||||
await parse(stream, (msg) => msgs.push(msg))
|
||||
return msgs
|
||||
}
|
||||
|
||||
describe('PgPacketStream', function () {
|
||||
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage)
|
||||
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage)
|
||||
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage)
|
||||
testForMessage(SASLBuffer, expectedSASLMessage)
|
||||
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage)
|
||||
|
||||
// this exercises a found bug in the parser:
|
||||
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
|
||||
// and adds a test which is deterministic, rather than relying on network packet chunking
|
||||
const extendedSASLContinueBuffer = Buffer.concat([SASLContinueBuffer, Buffer.from([1, 2, 3, 4])])
|
||||
testForMessage(extendedSASLContinueBuffer, expectedSASLContinueMessage)
|
||||
|
||||
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage)
|
||||
|
||||
// this exercises a found bug in the parser:
|
||||
// https://github.com/brianc/node-postgres/pull/2210#issuecomment-627626084
|
||||
// and adds a test which is deterministic, rather than relying on network packet chunking
|
||||
const extendedSASLFinalBuffer = Buffer.concat([SASLFinalBuffer, Buffer.from([1, 2, 4, 5])])
|
||||
testForMessage(extendedSASLFinalBuffer, expectedSASLFinalMessage)
|
||||
|
||||
testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
|
||||
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
|
||||
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage)
|
||||
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage)
|
||||
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage)
|
||||
testForMessage(buffers.emptyQuery(), {
|
||||
name: 'emptyQuery',
|
||||
length: 4,
|
||||
})
|
||||
|
||||
testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
|
||||
name: 'noData',
|
||||
})
|
||||
|
||||
describe('rowDescription messages', function () {
|
||||
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
|
||||
testForMessage(oneRowDescBuff, expectedOneRowMessage)
|
||||
testForMessage(twoRowBuf, expectedTwoRowMessage)
|
||||
testForMessage(bigOidDescBuff, expectedBigOidMessage)
|
||||
})
|
||||
|
||||
describe('parameterDescription messages', function () {
|
||||
testForMessage(emptyParameterDescriptionBuffer, expectedEmptyParameterDescriptionMessage)
|
||||
testForMessage(oneParameterDescBuf, expectedOneParameterMessage)
|
||||
testForMessage(twoParameterDescBuf, expectedTwoParameterMessage)
|
||||
})
|
||||
|
||||
describe('parsing rows', function () {
|
||||
describe('parsing empty row', function () {
|
||||
testForMessage(emptyRowFieldBuf, {
|
||||
name: 'dataRow',
|
||||
fieldCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
describe('parsing data row with fields', function () {
|
||||
testForMessage(oneFieldBuf, {
|
||||
name: 'dataRow',
|
||||
fieldCount: 1,
|
||||
fields: ['test'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('notice message', function () {
|
||||
// this uses the same logic as error message
|
||||
var buff = buffers.notice([{ type: 'C', value: 'code' }])
|
||||
testForMessage(buff, {
|
||||
name: 'notice',
|
||||
code: 'code',
|
||||
})
|
||||
})
|
||||
|
||||
testForMessage(buffers.error([]), {
|
||||
name: 'error',
|
||||
})
|
||||
|
||||
describe('with all the fields', function () {
|
||||
var buffer = buffers.error([
|
||||
{
|
||||
type: 'S',
|
||||
value: 'ERROR',
|
||||
},
|
||||
{
|
||||
type: 'C',
|
||||
value: 'code',
|
||||
},
|
||||
{
|
||||
type: 'M',
|
||||
value: 'message',
|
||||
},
|
||||
{
|
||||
type: 'D',
|
||||
value: 'details',
|
||||
},
|
||||
{
|
||||
type: 'H',
|
||||
value: 'hint',
|
||||
},
|
||||
{
|
||||
type: 'P',
|
||||
value: '100',
|
||||
},
|
||||
{
|
||||
type: 'p',
|
||||
value: '101',
|
||||
},
|
||||
{
|
||||
type: 'q',
|
||||
value: 'query',
|
||||
},
|
||||
{
|
||||
type: 'W',
|
||||
value: 'where',
|
||||
},
|
||||
{
|
||||
type: 'F',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
type: 'L',
|
||||
value: 'line',
|
||||
},
|
||||
{
|
||||
type: 'R',
|
||||
value: 'routine',
|
||||
},
|
||||
{
|
||||
type: 'Z', // ignored
|
||||
value: 'alsdkf',
|
||||
},
|
||||
])
|
||||
|
||||
testForMessage(buffer, {
|
||||
name: 'error',
|
||||
severity: 'ERROR',
|
||||
code: 'code',
|
||||
message: 'message',
|
||||
detail: 'details',
|
||||
hint: 'hint',
|
||||
position: '100',
|
||||
internalPosition: '101',
|
||||
internalQuery: 'query',
|
||||
where: 'where',
|
||||
file: 'file',
|
||||
line: 'line',
|
||||
routine: 'routine',
|
||||
})
|
||||
})
|
||||
|
||||
testForMessage(parseCompleteBuffer, {
|
||||
name: 'parseComplete',
|
||||
})
|
||||
|
||||
testForMessage(bindCompleteBuffer, {
|
||||
name: 'bindComplete',
|
||||
})
|
||||
|
||||
testForMessage(bindCompleteBuffer, {
|
||||
name: 'bindComplete',
|
||||
})
|
||||
|
||||
testForMessage(buffers.closeComplete(), {
|
||||
name: 'closeComplete',
|
||||
})
|
||||
|
||||
describe('parses portal suspended message', function () {
|
||||
testForMessage(portalSuspendedBuffer, {
|
||||
name: 'portalSuspended',
|
||||
})
|
||||
})
|
||||
|
||||
describe('parses replication start message', function () {
|
||||
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
|
||||
name: 'replicationStart',
|
||||
length: 4,
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy', () => {
|
||||
testForMessage(buffers.copyIn(0), {
|
||||
name: 'copyInResponse',
|
||||
length: 7,
|
||||
binary: false,
|
||||
columnTypes: [],
|
||||
})
|
||||
|
||||
testForMessage(buffers.copyIn(2), {
|
||||
name: 'copyInResponse',
|
||||
length: 11,
|
||||
binary: false,
|
||||
columnTypes: [0, 1],
|
||||
})
|
||||
|
||||
testForMessage(buffers.copyOut(0), {
|
||||
name: 'copyOutResponse',
|
||||
length: 7,
|
||||
binary: false,
|
||||
columnTypes: [],
|
||||
})
|
||||
|
||||
testForMessage(buffers.copyOut(3), {
|
||||
name: 'copyOutResponse',
|
||||
length: 13,
|
||||
binary: false,
|
||||
columnTypes: [0, 1, 2],
|
||||
})
|
||||
|
||||
testForMessage(buffers.copyDone(), {
|
||||
name: 'copyDone',
|
||||
length: 4,
|
||||
})
|
||||
|
||||
testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), {
|
||||
name: 'copyData',
|
||||
length: 7,
|
||||
chunk: Buffer.from([5, 6, 7]),
|
||||
})
|
||||
})
|
||||
|
||||
// since the data message on a stream can randomly divide the incomming
|
||||
// tcp packets anywhere, we need to make sure we can parse every single
|
||||
// split on a tcp message
|
||||
describe('split buffer, single message parsing', function () {
|
||||
var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
|
||||
|
||||
it('parses when full buffer comes in', async function () {
|
||||
const messages = await parseBuffers([fullBuffer])
|
||||
const message = messages[0] as any
|
||||
assert.equal(message.fields.length, 5)
|
||||
assert.equal(message.fields[0], null)
|
||||
assert.equal(message.fields[1], 'bang')
|
||||
assert.equal(message.fields[2], 'zug zug')
|
||||
assert.equal(message.fields[3], null)
|
||||
assert.equal(message.fields[4], '!')
|
||||
})
|
||||
|
||||
var testMessageReceivedAfterSplitAt = async function (split: number) {
|
||||
var firstBuffer = Buffer.alloc(fullBuffer.length - split)
|
||||
var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
|
||||
fullBuffer.copy(firstBuffer, 0, 0)
|
||||
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
|
||||
const messages = await parseBuffers([firstBuffer, secondBuffer])
|
||||
const message = messages[0] as any
|
||||
assert.equal(message.fields.length, 5)
|
||||
assert.equal(message.fields[0], null)
|
||||
assert.equal(message.fields[1], 'bang')
|
||||
assert.equal(message.fields[2], 'zug zug')
|
||||
assert.equal(message.fields[3], null)
|
||||
assert.equal(message.fields[4], '!')
|
||||
}
|
||||
|
||||
it('parses when split in the middle', function () {
|
||||
return testMessageReceivedAfterSplitAt(6)
|
||||
})
|
||||
|
||||
it('parses when split at end', function () {
|
||||
return testMessageReceivedAfterSplitAt(2)
|
||||
})
|
||||
|
||||
it('parses when split at beginning', function () {
|
||||
return Promise.all([
|
||||
testMessageReceivedAfterSplitAt(fullBuffer.length - 2),
|
||||
testMessageReceivedAfterSplitAt(fullBuffer.length - 1),
|
||||
testMessageReceivedAfterSplitAt(fullBuffer.length - 5),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('split buffer, multiple message parsing', function () {
|
||||
var dataRowBuffer = buffers.dataRow(['!'])
|
||||
var readyForQueryBuffer = buffers.readyForQuery()
|
||||
var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length)
|
||||
dataRowBuffer.copy(fullBuffer, 0, 0)
|
||||
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0)
|
||||
|
||||
var verifyMessages = function (messages: any[]) {
|
||||
assert.strictEqual(messages.length, 2)
|
||||
assert.deepEqual(messages[0], {
|
||||
name: 'dataRow',
|
||||
fieldCount: 1,
|
||||
length: 11,
|
||||
fields: ['!'],
|
||||
})
|
||||
assert.equal(messages[0].fields[0], '!')
|
||||
assert.deepEqual(messages[1], {
|
||||
name: 'readyForQuery',
|
||||
length: 5,
|
||||
status: 'I',
|
||||
})
|
||||
}
|
||||
// sanity check
|
||||
it('receives both messages when packet is not split', async function () {
|
||||
const messages = await parseBuffers([fullBuffer])
|
||||
verifyMessages(messages)
|
||||
})
|
||||
|
||||
var splitAndVerifyTwoMessages = async function (split: number) {
|
||||
var firstBuffer = Buffer.alloc(fullBuffer.length - split)
|
||||
var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
|
||||
fullBuffer.copy(firstBuffer, 0, 0)
|
||||
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
|
||||
const messages = await parseBuffers([firstBuffer, secondBuffer])
|
||||
verifyMessages(messages)
|
||||
}
|
||||
|
||||
describe('receives both messages when packet is split', function () {
|
||||
it('in the middle', function () {
|
||||
return splitAndVerifyTwoMessages(11)
|
||||
})
|
||||
it('at the front', function () {
|
||||
return Promise.all([
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 1),
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 4),
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 6),
|
||||
])
|
||||
})
|
||||
|
||||
it('at the end', function () {
|
||||
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
11
backend/apis/nodejs/node_modules/pg-protocol/src/index.ts
generated
vendored
Normal file
11
backend/apis/nodejs/node_modules/pg-protocol/src/index.ts
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { DatabaseError } from './messages'
|
||||
import { serialize } from './serializer'
|
||||
import { Parser, MessageCallback } from './parser'
|
||||
|
||||
export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise<void> {
|
||||
const parser = new Parser()
|
||||
stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback))
|
||||
return new Promise((resolve) => stream.on('end', () => resolve()))
|
||||
}
|
||||
|
||||
export { serialize, DatabaseError }
|
262
backend/apis/nodejs/node_modules/pg-protocol/src/messages.ts
generated
vendored
Normal file
262
backend/apis/nodejs/node_modules/pg-protocol/src/messages.ts
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
export type Mode = 'text' | 'binary'
|
||||
|
||||
export type MessageName =
|
||||
| 'parseComplete'
|
||||
| 'bindComplete'
|
||||
| 'closeComplete'
|
||||
| 'noData'
|
||||
| 'portalSuspended'
|
||||
| 'replicationStart'
|
||||
| 'emptyQuery'
|
||||
| 'copyDone'
|
||||
| 'copyData'
|
||||
| 'rowDescription'
|
||||
| 'parameterDescription'
|
||||
| 'parameterStatus'
|
||||
| 'backendKeyData'
|
||||
| 'notification'
|
||||
| 'readyForQuery'
|
||||
| 'commandComplete'
|
||||
| 'dataRow'
|
||||
| 'copyInResponse'
|
||||
| 'copyOutResponse'
|
||||
| 'authenticationOk'
|
||||
| 'authenticationMD5Password'
|
||||
| 'authenticationCleartextPassword'
|
||||
| 'authenticationSASL'
|
||||
| 'authenticationSASLContinue'
|
||||
| 'authenticationSASLFinal'
|
||||
| 'error'
|
||||
| 'notice'
|
||||
|
||||
export interface BackendMessage {
|
||||
name: MessageName
|
||||
length: number
|
||||
}
|
||||
|
||||
export const parseComplete: BackendMessage = {
|
||||
name: 'parseComplete',
|
||||
length: 5,
|
||||
}
|
||||
|
||||
export const bindComplete: BackendMessage = {
|
||||
name: 'bindComplete',
|
||||
length: 5,
|
||||
}
|
||||
|
||||
export const closeComplete: BackendMessage = {
|
||||
name: 'closeComplete',
|
||||
length: 5,
|
||||
}
|
||||
|
||||
export const noData: BackendMessage = {
|
||||
name: 'noData',
|
||||
length: 5,
|
||||
}
|
||||
|
||||
export const portalSuspended: BackendMessage = {
|
||||
name: 'portalSuspended',
|
||||
length: 5,
|
||||
}
|
||||
|
||||
export const replicationStart: BackendMessage = {
|
||||
name: 'replicationStart',
|
||||
length: 4,
|
||||
}
|
||||
|
||||
export const emptyQuery: BackendMessage = {
|
||||
name: 'emptyQuery',
|
||||
length: 4,
|
||||
}
|
||||
|
||||
export const copyDone: BackendMessage = {
|
||||
name: 'copyDone',
|
||||
length: 4,
|
||||
}
|
||||
|
||||
interface NoticeOrError {
|
||||
message: string | undefined
|
||||
severity: string | undefined
|
||||
code: string | undefined
|
||||
detail: string | undefined
|
||||
hint: string | undefined
|
||||
position: string | undefined
|
||||
internalPosition: string | undefined
|
||||
internalQuery: string | undefined
|
||||
where: string | undefined
|
||||
schema: string | undefined
|
||||
table: string | undefined
|
||||
column: string | undefined
|
||||
dataType: string | undefined
|
||||
constraint: string | undefined
|
||||
file: string | undefined
|
||||
line: string | undefined
|
||||
routine: string | undefined
|
||||
}
|
||||
|
||||
export class DatabaseError extends Error implements NoticeOrError {
|
||||
public severity: string | undefined
|
||||
public code: string | undefined
|
||||
public detail: string | undefined
|
||||
public hint: string | undefined
|
||||
public position: string | undefined
|
||||
public internalPosition: string | undefined
|
||||
public internalQuery: string | undefined
|
||||
public where: string | undefined
|
||||
public schema: string | undefined
|
||||
public table: string | undefined
|
||||
public column: string | undefined
|
||||
public dataType: string | undefined
|
||||
public constraint: string | undefined
|
||||
public file: string | undefined
|
||||
public line: string | undefined
|
||||
public routine: string | undefined
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly length: number,
|
||||
public readonly name: MessageName
|
||||
) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyDataMessage {
|
||||
public readonly name = 'copyData'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly chunk: Buffer
|
||||
) {}
|
||||
}
|
||||
|
||||
export class CopyResponse {
|
||||
public readonly columnTypes: number[]
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly name: MessageName,
|
||||
public readonly binary: boolean,
|
||||
columnCount: number
|
||||
) {
|
||||
this.columnTypes = new Array(columnCount)
|
||||
}
|
||||
}
|
||||
|
||||
export class Field {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly tableID: number,
|
||||
public readonly columnID: number,
|
||||
public readonly dataTypeID: number,
|
||||
public readonly dataTypeSize: number,
|
||||
public readonly dataTypeModifier: number,
|
||||
public readonly format: Mode
|
||||
) {}
|
||||
}
|
||||
|
||||
export class RowDescriptionMessage {
|
||||
public readonly name: MessageName = 'rowDescription'
|
||||
public readonly fields: Field[]
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly fieldCount: number
|
||||
) {
|
||||
this.fields = new Array(this.fieldCount)
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterDescriptionMessage {
|
||||
public readonly name: MessageName = 'parameterDescription'
|
||||
public readonly dataTypeIDs: number[]
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly parameterCount: number
|
||||
) {
|
||||
this.dataTypeIDs = new Array(this.parameterCount)
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterStatusMessage {
|
||||
public readonly name: MessageName = 'parameterStatus'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly parameterName: string,
|
||||
public readonly parameterValue: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class AuthenticationMD5Password implements BackendMessage {
|
||||
public readonly name: MessageName = 'authenticationMD5Password'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly salt: Buffer
|
||||
) {}
|
||||
}
|
||||
|
||||
export class BackendKeyDataMessage {
|
||||
public readonly name: MessageName = 'backendKeyData'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly processID: number,
|
||||
public readonly secretKey: number
|
||||
) {}
|
||||
}
|
||||
|
||||
export class NotificationResponseMessage {
|
||||
public readonly name: MessageName = 'notification'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly processId: number,
|
||||
public readonly channel: string,
|
||||
public readonly payload: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class ReadyForQueryMessage {
|
||||
public readonly name: MessageName = 'readyForQuery'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly status: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class CommandCompleteMessage {
|
||||
public readonly name: MessageName = 'commandComplete'
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly text: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class DataRowMessage {
|
||||
public readonly fieldCount: number
|
||||
public readonly name: MessageName = 'dataRow'
|
||||
constructor(
|
||||
public length: number,
|
||||
public fields: any[]
|
||||
) {
|
||||
this.fieldCount = fields.length
|
||||
}
|
||||
}
|
||||
|
||||
export class NoticeMessage implements BackendMessage, NoticeOrError {
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly message: string | undefined
|
||||
) {}
|
||||
public readonly name = 'notice'
|
||||
public severity: string | undefined
|
||||
public code: string | undefined
|
||||
public detail: string | undefined
|
||||
public hint: string | undefined
|
||||
public position: string | undefined
|
||||
public internalPosition: string | undefined
|
||||
public internalQuery: string | undefined
|
||||
public where: string | undefined
|
||||
public schema: string | undefined
|
||||
public table: string | undefined
|
||||
public column: string | undefined
|
||||
public dataType: string | undefined
|
||||
public constraint: string | undefined
|
||||
public file: string | undefined
|
||||
public line: string | undefined
|
||||
public routine: string | undefined
|
||||
}
|
272
backend/apis/nodejs/node_modules/pg-protocol/src/outbound-serializer.test.ts
generated
vendored
Normal file
272
backend/apis/nodejs/node_modules/pg-protocol/src/outbound-serializer.test.ts
generated
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
import assert from 'assert'
|
||||
import { serialize } from './serializer'
|
||||
import BufferList from './testing/buffer-list'
|
||||
|
||||
describe('serializer', () => {
|
||||
it('builds startup message', function () {
|
||||
const actual = serialize.startup({
|
||||
user: 'brian',
|
||||
database: 'bang',
|
||||
})
|
||||
assert.deepEqual(
|
||||
actual,
|
||||
new BufferList()
|
||||
.addInt16(3)
|
||||
.addInt16(0)
|
||||
.addCString('user')
|
||||
.addCString('brian')
|
||||
.addCString('database')
|
||||
.addCString('bang')
|
||||
.addCString('client_encoding')
|
||||
.addCString('UTF8')
|
||||
.addCString('')
|
||||
.join(true)
|
||||
)
|
||||
})
|
||||
|
||||
it('builds password message', function () {
|
||||
const actual = serialize.password('!')
|
||||
assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p'))
|
||||
})
|
||||
|
||||
it('builds request ssl message', function () {
|
||||
const actual = serialize.requestSsl()
|
||||
const expected = new BufferList().addInt32(80877103).join(true)
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('builds SASLInitialResponseMessage message', function () {
|
||||
const actual = serialize.sendSASLInitialResponseMessage('mech', 'data')
|
||||
assert.deepEqual(actual, new BufferList().addCString('mech').addInt32(4).addString('data').join(true, 'p'))
|
||||
})
|
||||
|
||||
it('builds SCRAMClientFinalMessage message', function () {
|
||||
const actual = serialize.sendSCRAMClientFinalMessage('data')
|
||||
assert.deepEqual(actual, new BufferList().addString('data').join(true, 'p'))
|
||||
})
|
||||
|
||||
it('builds query message', function () {
|
||||
var txt = 'select * from boom'
|
||||
const actual = serialize.query(txt)
|
||||
assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q'))
|
||||
})
|
||||
|
||||
describe('parse message', () => {
|
||||
it('builds parse message', function () {
|
||||
const actual = serialize.parse({ text: '!' })
|
||||
var expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('builds parse message with named query', function () {
|
||||
const actual = serialize.parse({
|
||||
name: 'boom',
|
||||
text: 'select * from boom',
|
||||
types: [],
|
||||
})
|
||||
var expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('with multiple parameters', function () {
|
||||
const actual = serialize.parse({
|
||||
name: 'force',
|
||||
text: 'select * from bang where name = $1',
|
||||
types: [1, 2, 3, 4],
|
||||
})
|
||||
var expected = new BufferList()
|
||||
.addCString('force')
|
||||
.addCString('select * from bang where name = $1')
|
||||
.addInt16(4)
|
||||
.addInt32(1)
|
||||
.addInt32(2)
|
||||
.addInt32(3)
|
||||
.addInt32(4)
|
||||
.join(true, 'P')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('bind messages', function () {
|
||||
it('with no values', function () {
|
||||
const actual = serialize.bind()
|
||||
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('')
|
||||
.addCString('')
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
|
||||
it('with named statement, portal, and values', function () {
|
||||
const actual = serialize.bind({
|
||||
portal: 'bang',
|
||||
statement: 'woo',
|
||||
values: ['1', 'hi', null, 'zing'],
|
||||
})
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('bang') // portal name
|
||||
.addCString('woo') // statement name
|
||||
.addInt16(4)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(4)
|
||||
.addInt32(1)
|
||||
.add(Buffer.from('1'))
|
||||
.addInt32(2)
|
||||
.add(Buffer.from('hi'))
|
||||
.addInt32(-1)
|
||||
.addInt32(4)
|
||||
.add(Buffer.from('zing'))
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
})
|
||||
|
||||
it('with custom valueMapper', function () {
|
||||
const actual = serialize.bind({
|
||||
portal: 'bang',
|
||||
statement: 'woo',
|
||||
values: ['1', 'hi', null, 'zing'],
|
||||
valueMapper: () => null,
|
||||
})
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('bang') // portal name
|
||||
.addCString('woo') // statement name
|
||||
.addInt16(4)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(4)
|
||||
.addInt32(-1)
|
||||
.addInt32(-1)
|
||||
.addInt32(-1)
|
||||
.addInt32(-1)
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
|
||||
it('with named statement, portal, and buffer value', function () {
|
||||
const actual = serialize.bind({
|
||||
portal: 'bang',
|
||||
statement: 'woo',
|
||||
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')],
|
||||
})
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('bang') // portal name
|
||||
.addCString('woo') // statement name
|
||||
.addInt16(4) // value count
|
||||
.addInt16(0) // string
|
||||
.addInt16(0) // string
|
||||
.addInt16(0) // string
|
||||
.addInt16(1) // binary
|
||||
.addInt16(4)
|
||||
.addInt32(1)
|
||||
.add(Buffer.from('1'))
|
||||
.addInt32(2)
|
||||
.add(Buffer.from('hi'))
|
||||
.addInt32(-1)
|
||||
.addInt32(4)
|
||||
.add(Buffer.from('zing', 'utf-8'))
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
|
||||
describe('builds execute message', function () {
|
||||
it('for unamed portal with no row limit', function () {
|
||||
const actual = serialize.execute()
|
||||
var expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
|
||||
it('for named portal with row limit', function () {
|
||||
const actual = serialize.execute({
|
||||
portal: 'my favorite portal',
|
||||
rows: 100,
|
||||
})
|
||||
var expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
})
|
||||
|
||||
it('builds flush command', function () {
|
||||
const actual = serialize.flush()
|
||||
var expected = new BufferList().join(true, 'H')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('builds sync command', function () {
|
||||
const actual = serialize.sync()
|
||||
var expected = new BufferList().join(true, 'S')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('builds end command', function () {
|
||||
const actual = serialize.end()
|
||||
var expected = Buffer.from([0x58, 0, 0, 0, 4])
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
describe('builds describe command', function () {
|
||||
it('describe statement', function () {
|
||||
const actual = serialize.describe({ type: 'S', name: 'bang' })
|
||||
var expected = new BufferList().addChar('S').addCString('bang').join(true, 'D')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('describe unnamed portal', function () {
|
||||
const actual = serialize.describe({ type: 'P' })
|
||||
var expected = new BufferList().addChar('P').addCString('').join(true, 'D')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('builds close command', function () {
|
||||
it('describe statement', function () {
|
||||
const actual = serialize.close({ type: 'S', name: 'bang' })
|
||||
var expected = new BufferList().addChar('S').addCString('bang').join(true, 'C')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('describe unnamed portal', function () {
|
||||
const actual = serialize.close({ type: 'P' })
|
||||
var expected = new BufferList().addChar('P').addCString('').join(true, 'C')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('copy messages', function () {
|
||||
it('builds copyFromChunk', () => {
|
||||
const actual = serialize.copyData(Buffer.from([1, 2, 3]))
|
||||
const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('builds copy fail', () => {
|
||||
const actual = serialize.copyFail('err!')
|
||||
const expected = new BufferList().addCString('err!').join(true, 'f')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
it('builds copy done', () => {
|
||||
const actual = serialize.copyDone()
|
||||
const expected = new BufferList().join(true, 'c')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('builds cancel message', () => {
|
||||
const actual = serialize.cancel(3, 4)
|
||||
const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true)
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
388
backend/apis/nodejs/node_modules/pg-protocol/src/parser.ts
generated
vendored
Normal file
388
backend/apis/nodejs/node_modules/pg-protocol/src/parser.ts
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
||||
import { TransformOptions } from 'stream'
|
||||
import {
|
||||
Mode,
|
||||
bindComplete,
|
||||
parseComplete,
|
||||
closeComplete,
|
||||
noData,
|
||||
portalSuspended,
|
||||
copyDone,
|
||||
replicationStart,
|
||||
emptyQuery,
|
||||
ReadyForQueryMessage,
|
||||
CommandCompleteMessage,
|
||||
CopyDataMessage,
|
||||
CopyResponse,
|
||||
NotificationResponseMessage,
|
||||
RowDescriptionMessage,
|
||||
ParameterDescriptionMessage,
|
||||
Field,
|
||||
DataRowMessage,
|
||||
ParameterStatusMessage,
|
||||
BackendKeyDataMessage,
|
||||
DatabaseError,
|
||||
BackendMessage,
|
||||
MessageName,
|
||||
AuthenticationMD5Password,
|
||||
NoticeMessage,
|
||||
} from './messages'
|
||||
import { BufferReader } from './buffer-reader'
|
||||
|
||||
// every message is prefixed with a single bye
|
||||
const CODE_LENGTH = 1
|
||||
// every message has an int32 length which includes itself but does
|
||||
// NOT include the code in the length
|
||||
const LEN_LENGTH = 4
|
||||
|
||||
const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH
|
||||
|
||||
export type Packet = {
|
||||
code: number
|
||||
packet: Buffer
|
||||
}
|
||||
|
||||
const emptyBuffer = Buffer.allocUnsafe(0)
|
||||
|
||||
type StreamOptions = TransformOptions & {
|
||||
mode: Mode
|
||||
}
|
||||
|
||||
const enum MessageCodes {
|
||||
DataRow = 0x44, // D
|
||||
ParseComplete = 0x31, // 1
|
||||
BindComplete = 0x32, // 2
|
||||
CloseComplete = 0x33, // 3
|
||||
CommandComplete = 0x43, // C
|
||||
ReadyForQuery = 0x5a, // Z
|
||||
NoData = 0x6e, // n
|
||||
NotificationResponse = 0x41, // A
|
||||
AuthenticationResponse = 0x52, // R
|
||||
ParameterStatus = 0x53, // S
|
||||
BackendKeyData = 0x4b, // K
|
||||
ErrorMessage = 0x45, // E
|
||||
NoticeMessage = 0x4e, // N
|
||||
RowDescriptionMessage = 0x54, // T
|
||||
ParameterDescriptionMessage = 0x74, // t
|
||||
PortalSuspended = 0x73, // s
|
||||
ReplicationStart = 0x57, // W
|
||||
EmptyQuery = 0x49, // I
|
||||
CopyIn = 0x47, // G
|
||||
CopyOut = 0x48, // H
|
||||
CopyDone = 0x63, // c
|
||||
CopyData = 0x64, // d
|
||||
}
|
||||
|
||||
export type MessageCallback = (msg: BackendMessage) => void
|
||||
|
||||
export class Parser {
|
||||
private buffer: Buffer = emptyBuffer
|
||||
private bufferLength: number = 0
|
||||
private bufferOffset: number = 0
|
||||
private reader = new BufferReader()
|
||||
private mode: Mode
|
||||
|
||||
constructor(opts?: StreamOptions) {
|
||||
if (opts?.mode === 'binary') {
|
||||
throw new Error('Binary mode not supported yet')
|
||||
}
|
||||
this.mode = opts?.mode || 'text'
|
||||
}
|
||||
|
||||
public parse(buffer: Buffer, callback: MessageCallback) {
|
||||
this.mergeBuffer(buffer)
|
||||
const bufferFullLength = this.bufferOffset + this.bufferLength
|
||||
let offset = this.bufferOffset
|
||||
while (offset + HEADER_LENGTH <= bufferFullLength) {
|
||||
// code is 1 byte long - it identifies the message type
|
||||
const code = this.buffer[offset]
|
||||
// length is 1 Uint32BE - it is the length of the message EXCLUDING the code
|
||||
const length = this.buffer.readUInt32BE(offset + CODE_LENGTH)
|
||||
const fullMessageLength = CODE_LENGTH + length
|
||||
if (fullMessageLength + offset <= bufferFullLength) {
|
||||
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, this.buffer)
|
||||
callback(message)
|
||||
offset += fullMessageLength
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (offset === bufferFullLength) {
|
||||
// No more use for the buffer
|
||||
this.buffer = emptyBuffer
|
||||
this.bufferLength = 0
|
||||
this.bufferOffset = 0
|
||||
} else {
|
||||
// Adjust the cursors of remainingBuffer
|
||||
this.bufferLength = bufferFullLength - offset
|
||||
this.bufferOffset = offset
|
||||
}
|
||||
}
|
||||
|
||||
private mergeBuffer(buffer: Buffer): void {
|
||||
if (this.bufferLength > 0) {
|
||||
const newLength = this.bufferLength + buffer.byteLength
|
||||
const newFullLength = newLength + this.bufferOffset
|
||||
if (newFullLength > this.buffer.byteLength) {
|
||||
// We can't concat the new buffer with the remaining one
|
||||
let newBuffer: Buffer
|
||||
if (newLength <= this.buffer.byteLength && this.bufferOffset >= this.bufferLength) {
|
||||
// We can move the relevant part to the beginning of the buffer instead of allocating a new buffer
|
||||
newBuffer = this.buffer
|
||||
} else {
|
||||
// Allocate a new larger buffer
|
||||
let newBufferLength = this.buffer.byteLength * 2
|
||||
while (newLength >= newBufferLength) {
|
||||
newBufferLength *= 2
|
||||
}
|
||||
newBuffer = Buffer.allocUnsafe(newBufferLength)
|
||||
}
|
||||
// Move the remaining buffer to the new one
|
||||
this.buffer.copy(newBuffer, 0, this.bufferOffset, this.bufferOffset + this.bufferLength)
|
||||
this.buffer = newBuffer
|
||||
this.bufferOffset = 0
|
||||
}
|
||||
// Concat the new buffer with the remaining one
|
||||
buffer.copy(this.buffer, this.bufferOffset + this.bufferLength)
|
||||
this.bufferLength = newLength
|
||||
} else {
|
||||
this.buffer = buffer
|
||||
this.bufferOffset = 0
|
||||
this.bufferLength = buffer.byteLength
|
||||
}
|
||||
}
|
||||
|
||||
private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage {
|
||||
switch (code) {
|
||||
case MessageCodes.BindComplete:
|
||||
return bindComplete
|
||||
case MessageCodes.ParseComplete:
|
||||
return parseComplete
|
||||
case MessageCodes.CloseComplete:
|
||||
return closeComplete
|
||||
case MessageCodes.NoData:
|
||||
return noData
|
||||
case MessageCodes.PortalSuspended:
|
||||
return portalSuspended
|
||||
case MessageCodes.CopyDone:
|
||||
return copyDone
|
||||
case MessageCodes.ReplicationStart:
|
||||
return replicationStart
|
||||
case MessageCodes.EmptyQuery:
|
||||
return emptyQuery
|
||||
case MessageCodes.DataRow:
|
||||
return this.parseDataRowMessage(offset, length, bytes)
|
||||
case MessageCodes.CommandComplete:
|
||||
return this.parseCommandCompleteMessage(offset, length, bytes)
|
||||
case MessageCodes.ReadyForQuery:
|
||||
return this.parseReadyForQueryMessage(offset, length, bytes)
|
||||
case MessageCodes.NotificationResponse:
|
||||
return this.parseNotificationMessage(offset, length, bytes)
|
||||
case MessageCodes.AuthenticationResponse:
|
||||
return this.parseAuthenticationResponse(offset, length, bytes)
|
||||
case MessageCodes.ParameterStatus:
|
||||
return this.parseParameterStatusMessage(offset, length, bytes)
|
||||
case MessageCodes.BackendKeyData:
|
||||
return this.parseBackendKeyData(offset, length, bytes)
|
||||
case MessageCodes.ErrorMessage:
|
||||
return this.parseErrorMessage(offset, length, bytes, 'error')
|
||||
case MessageCodes.NoticeMessage:
|
||||
return this.parseErrorMessage(offset, length, bytes, 'notice')
|
||||
case MessageCodes.RowDescriptionMessage:
|
||||
return this.parseRowDescriptionMessage(offset, length, bytes)
|
||||
case MessageCodes.ParameterDescriptionMessage:
|
||||
return this.parseParameterDescriptionMessage(offset, length, bytes)
|
||||
case MessageCodes.CopyIn:
|
||||
return this.parseCopyInMessage(offset, length, bytes)
|
||||
case MessageCodes.CopyOut:
|
||||
return this.parseCopyOutMessage(offset, length, bytes)
|
||||
case MessageCodes.CopyData:
|
||||
return this.parseCopyData(offset, length, bytes)
|
||||
default:
|
||||
return new DatabaseError('received invalid response: ' + code.toString(16), length, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const status = this.reader.string(1)
|
||||
return new ReadyForQueryMessage(length, status)
|
||||
}
|
||||
|
||||
private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const text = this.reader.cstring()
|
||||
return new CommandCompleteMessage(length, text)
|
||||
}
|
||||
|
||||
private parseCopyData(offset: number, length: number, bytes: Buffer) {
|
||||
const chunk = bytes.slice(offset, offset + (length - 4))
|
||||
return new CopyDataMessage(length, chunk)
|
||||
}
|
||||
|
||||
private parseCopyInMessage(offset: number, length: number, bytes: Buffer) {
|
||||
return this.parseCopyMessage(offset, length, bytes, 'copyInResponse')
|
||||
}
|
||||
|
||||
private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) {
|
||||
return this.parseCopyMessage(offset, length, bytes, 'copyOutResponse')
|
||||
}
|
||||
|
||||
private parseCopyMessage(offset: number, length: number, bytes: Buffer, messageName: MessageName) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const isBinary = this.reader.byte() !== 0
|
||||
const columnCount = this.reader.int16()
|
||||
const message = new CopyResponse(length, messageName, isBinary, columnCount)
|
||||
for (let i = 0; i < columnCount; i++) {
|
||||
message.columnTypes[i] = this.reader.int16()
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
private parseNotificationMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const processId = this.reader.int32()
|
||||
const channel = this.reader.cstring()
|
||||
const payload = this.reader.cstring()
|
||||
return new NotificationResponseMessage(length, processId, channel, payload)
|
||||
}
|
||||
|
||||
private parseRowDescriptionMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const fieldCount = this.reader.int16()
|
||||
const message = new RowDescriptionMessage(length, fieldCount)
|
||||
for (let i = 0; i < fieldCount; i++) {
|
||||
message.fields[i] = this.parseField()
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
private parseField(): Field {
|
||||
const name = this.reader.cstring()
|
||||
const tableID = this.reader.uint32()
|
||||
const columnID = this.reader.int16()
|
||||
const dataTypeID = this.reader.uint32()
|
||||
const dataTypeSize = this.reader.int16()
|
||||
const dataTypeModifier = this.reader.int32()
|
||||
const mode = this.reader.int16() === 0 ? 'text' : 'binary'
|
||||
return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode)
|
||||
}
|
||||
|
||||
private parseParameterDescriptionMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const parameterCount = this.reader.int16()
|
||||
const message = new ParameterDescriptionMessage(length, parameterCount)
|
||||
for (let i = 0; i < parameterCount; i++) {
|
||||
message.dataTypeIDs[i] = this.reader.int32()
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
private parseDataRowMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const fieldCount = this.reader.int16()
|
||||
const fields: any[] = new Array(fieldCount)
|
||||
for (let i = 0; i < fieldCount; i++) {
|
||||
const len = this.reader.int32()
|
||||
// a -1 for length means the value of the field is null
|
||||
fields[i] = len === -1 ? null : this.reader.string(len)
|
||||
}
|
||||
return new DataRowMessage(length, fields)
|
||||
}
|
||||
|
||||
private parseParameterStatusMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const name = this.reader.cstring()
|
||||
const value = this.reader.cstring()
|
||||
return new ParameterStatusMessage(length, name, value)
|
||||
}
|
||||
|
||||
private parseBackendKeyData(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const processID = this.reader.int32()
|
||||
const secretKey = this.reader.int32()
|
||||
return new BackendKeyDataMessage(length, processID, secretKey)
|
||||
}
|
||||
|
||||
public parseAuthenticationResponse(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const code = this.reader.int32()
|
||||
// TODO(bmc): maybe better types here
|
||||
const message: BackendMessage & any = {
|
||||
name: 'authenticationOk',
|
||||
length,
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 0: // AuthenticationOk
|
||||
break
|
||||
case 3: // AuthenticationCleartextPassword
|
||||
if (message.length === 8) {
|
||||
message.name = 'authenticationCleartextPassword'
|
||||
}
|
||||
break
|
||||
case 5: // AuthenticationMD5Password
|
||||
if (message.length === 12) {
|
||||
message.name = 'authenticationMD5Password'
|
||||
const salt = this.reader.bytes(4)
|
||||
return new AuthenticationMD5Password(length, salt)
|
||||
}
|
||||
break
|
||||
case 10: // AuthenticationSASL
|
||||
message.name = 'authenticationSASL'
|
||||
message.mechanisms = []
|
||||
let mechanism: string
|
||||
do {
|
||||
mechanism = this.reader.cstring()
|
||||
|
||||
if (mechanism) {
|
||||
message.mechanisms.push(mechanism)
|
||||
}
|
||||
} while (mechanism)
|
||||
break
|
||||
case 11: // AuthenticationSASLContinue
|
||||
message.name = 'authenticationSASLContinue'
|
||||
message.data = this.reader.string(length - 8)
|
||||
break
|
||||
case 12: // AuthenticationSASLFinal
|
||||
message.name = 'authenticationSASLFinal'
|
||||
message.data = this.reader.string(length - 8)
|
||||
break
|
||||
default:
|
||||
throw new Error('Unknown authenticationOk message type ' + code)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
private parseErrorMessage(offset: number, length: number, bytes: Buffer, name: MessageName) {
|
||||
this.reader.setBuffer(offset, bytes)
|
||||
const fields: Record<string, string> = {}
|
||||
let fieldType = this.reader.string(1)
|
||||
while (fieldType !== '\0') {
|
||||
fields[fieldType] = this.reader.cstring()
|
||||
fieldType = this.reader.string(1)
|
||||
}
|
||||
|
||||
const messageValue = fields.M
|
||||
|
||||
const message =
|
||||
name === 'notice' ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name)
|
||||
|
||||
message.severity = fields.S
|
||||
message.code = fields.C
|
||||
message.detail = fields.D
|
||||
message.hint = fields.H
|
||||
message.position = fields.P
|
||||
message.internalPosition = fields.p
|
||||
message.internalQuery = fields.q
|
||||
message.where = fields.W
|
||||
message.schema = fields.s
|
||||
message.table = fields.t
|
||||
message.column = fields.c
|
||||
message.dataType = fields.d
|
||||
message.constraint = fields.n
|
||||
message.file = fields.F
|
||||
message.line = fields.L
|
||||
message.routine = fields.R
|
||||
return message
|
||||
}
|
||||
}
|
274
backend/apis/nodejs/node_modules/pg-protocol/src/serializer.ts
generated
vendored
Normal file
274
backend/apis/nodejs/node_modules/pg-protocol/src/serializer.ts
generated
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
import { Writer } from './buffer-writer'
|
||||
|
||||
const enum code {
|
||||
startup = 0x70,
|
||||
query = 0x51,
|
||||
parse = 0x50,
|
||||
bind = 0x42,
|
||||
execute = 0x45,
|
||||
flush = 0x48,
|
||||
sync = 0x53,
|
||||
end = 0x58,
|
||||
close = 0x43,
|
||||
describe = 0x44,
|
||||
copyFromChunk = 0x64,
|
||||
copyDone = 0x63,
|
||||
copyFail = 0x66,
|
||||
}
|
||||
|
||||
const writer = new Writer()
|
||||
|
||||
const startup = (opts: Record<string, string>): Buffer => {
|
||||
// protocol version
|
||||
writer.addInt16(3).addInt16(0)
|
||||
for (const key of Object.keys(opts)) {
|
||||
writer.addCString(key).addCString(opts[key])
|
||||
}
|
||||
|
||||
writer.addCString('client_encoding').addCString('UTF8')
|
||||
|
||||
var bodyBuffer = writer.addCString('').flush()
|
||||
// this message is sent without a code
|
||||
|
||||
var length = bodyBuffer.length + 4
|
||||
|
||||
return new Writer().addInt32(length).add(bodyBuffer).flush()
|
||||
}
|
||||
|
||||
const requestSsl = (): Buffer => {
|
||||
const response = Buffer.allocUnsafe(8)
|
||||
response.writeInt32BE(8, 0)
|
||||
response.writeInt32BE(80877103, 4)
|
||||
return response
|
||||
}
|
||||
|
||||
const password = (password: string): Buffer => {
|
||||
return writer.addCString(password).flush(code.startup)
|
||||
}
|
||||
|
||||
const sendSASLInitialResponseMessage = function (mechanism: string, initialResponse: string): Buffer {
|
||||
// 0x70 = 'p'
|
||||
writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse)
|
||||
|
||||
return writer.flush(code.startup)
|
||||
}
|
||||
|
||||
const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer {
|
||||
return writer.addString(additionalData).flush(code.startup)
|
||||
}
|
||||
|
||||
const query = (text: string): Buffer => {
|
||||
return writer.addCString(text).flush(code.query)
|
||||
}
|
||||
|
||||
type ParseOpts = {
|
||||
name?: string
|
||||
types?: number[]
|
||||
text: string
|
||||
}
|
||||
|
||||
const emptyArray: any[] = []
|
||||
|
||||
const parse = (query: ParseOpts): Buffer => {
|
||||
// expect something like this:
|
||||
// { name: 'queryName',
|
||||
// text: 'select * from blah',
|
||||
// types: ['int8', 'bool'] }
|
||||
|
||||
// normalize missing query names to allow for null
|
||||
const name = query.name || ''
|
||||
if (name.length > 63) {
|
||||
/* eslint-disable no-console */
|
||||
console.error('Warning! Postgres only supports 63 characters for query names.')
|
||||
console.error('You supplied %s (%s)', name, name.length)
|
||||
console.error('This can cause conflicts and silent errors executing queries')
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
const types = query.types || emptyArray
|
||||
|
||||
var len = types.length
|
||||
|
||||
var buffer = writer
|
||||
.addCString(name) // name of query
|
||||
.addCString(query.text) // actual query text
|
||||
.addInt16(len)
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
buffer.addInt32(types[i])
|
||||
}
|
||||
|
||||
return writer.flush(code.parse)
|
||||
}
|
||||
|
||||
type ValueMapper = (param: any, index: number) => any
|
||||
|
||||
type BindOpts = {
|
||||
portal?: string
|
||||
binary?: boolean
|
||||
statement?: string
|
||||
values?: any[]
|
||||
// optional map from JS value to postgres value per parameter
|
||||
valueMapper?: ValueMapper
|
||||
}
|
||||
|
||||
const paramWriter = new Writer()
|
||||
|
||||
// make this a const enum so typescript will inline the value
|
||||
const enum ParamType {
|
||||
STRING = 0,
|
||||
BINARY = 1,
|
||||
}
|
||||
|
||||
const writeValues = function (values: any[], valueMapper?: ValueMapper): void {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const mappedVal = valueMapper ? valueMapper(values[i], i) : values[i]
|
||||
if (mappedVal == null) {
|
||||
// add the param type (string) to the writer
|
||||
writer.addInt16(ParamType.STRING)
|
||||
// write -1 to the param writer to indicate null
|
||||
paramWriter.addInt32(-1)
|
||||
} else if (mappedVal instanceof Buffer) {
|
||||
// add the param type (binary) to the writer
|
||||
writer.addInt16(ParamType.BINARY)
|
||||
// add the buffer to the param writer
|
||||
paramWriter.addInt32(mappedVal.length)
|
||||
paramWriter.add(mappedVal)
|
||||
} else {
|
||||
// add the param type (string) to the writer
|
||||
writer.addInt16(ParamType.STRING)
|
||||
paramWriter.addInt32(Buffer.byteLength(mappedVal))
|
||||
paramWriter.addString(mappedVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bind = (config: BindOpts = {}): Buffer => {
|
||||
// normalize config
|
||||
const portal = config.portal || ''
|
||||
const statement = config.statement || ''
|
||||
const binary = config.binary || false
|
||||
const values = config.values || emptyArray
|
||||
const len = values.length
|
||||
|
||||
writer.addCString(portal).addCString(statement)
|
||||
writer.addInt16(len)
|
||||
|
||||
writeValues(values, config.valueMapper)
|
||||
|
||||
writer.addInt16(len)
|
||||
writer.add(paramWriter.flush())
|
||||
|
||||
// format code
|
||||
writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING)
|
||||
return writer.flush(code.bind)
|
||||
}
|
||||
|
||||
type ExecOpts = {
|
||||
portal?: string
|
||||
rows?: number
|
||||
}
|
||||
|
||||
const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
const execute = (config?: ExecOpts): Buffer => {
|
||||
// this is the happy path for most queries
|
||||
if (!config || (!config.portal && !config.rows)) {
|
||||
return emptyExecute
|
||||
}
|
||||
|
||||
const portal = config.portal || ''
|
||||
const rows = config.rows || 0
|
||||
|
||||
const portalLength = Buffer.byteLength(portal)
|
||||
const len = 4 + portalLength + 1 + 4
|
||||
// one extra bit for code
|
||||
const buff = Buffer.allocUnsafe(1 + len)
|
||||
buff[0] = code.execute
|
||||
buff.writeInt32BE(len, 1)
|
||||
buff.write(portal, 5, 'utf-8')
|
||||
buff[portalLength + 5] = 0 // null terminate portal cString
|
||||
buff.writeUInt32BE(rows, buff.length - 4)
|
||||
return buff
|
||||
}
|
||||
|
||||
const cancel = (processID: number, secretKey: number): Buffer => {
|
||||
const buffer = Buffer.allocUnsafe(16)
|
||||
buffer.writeInt32BE(16, 0)
|
||||
buffer.writeInt16BE(1234, 4)
|
||||
buffer.writeInt16BE(5678, 6)
|
||||
buffer.writeInt32BE(processID, 8)
|
||||
buffer.writeInt32BE(secretKey, 12)
|
||||
return buffer
|
||||
}
|
||||
|
||||
type PortalOpts = {
|
||||
type: 'S' | 'P'
|
||||
name?: string
|
||||
}
|
||||
|
||||
const cstringMessage = (code: code, string: string): Buffer => {
|
||||
const stringLen = Buffer.byteLength(string)
|
||||
const len = 4 + stringLen + 1
|
||||
// one extra bit for code
|
||||
const buffer = Buffer.allocUnsafe(1 + len)
|
||||
buffer[0] = code
|
||||
buffer.writeInt32BE(len, 1)
|
||||
buffer.write(string, 5, 'utf-8')
|
||||
buffer[len] = 0 // null terminate cString
|
||||
return buffer
|
||||
}
|
||||
|
||||
const emptyDescribePortal = writer.addCString('P').flush(code.describe)
|
||||
const emptyDescribeStatement = writer.addCString('S').flush(code.describe)
|
||||
|
||||
const describe = (msg: PortalOpts): Buffer => {
|
||||
return msg.name
|
||||
? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`)
|
||||
: msg.type === 'P'
|
||||
? emptyDescribePortal
|
||||
: emptyDescribeStatement
|
||||
}
|
||||
|
||||
const close = (msg: PortalOpts): Buffer => {
|
||||
const text = `${msg.type}${msg.name || ''}`
|
||||
return cstringMessage(code.close, text)
|
||||
}
|
||||
|
||||
const copyData = (chunk: Buffer): Buffer => {
|
||||
return writer.add(chunk).flush(code.copyFromChunk)
|
||||
}
|
||||
|
||||
const copyFail = (message: string): Buffer => {
|
||||
return cstringMessage(code.copyFail, message)
|
||||
}
|
||||
|
||||
const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04])
|
||||
|
||||
const flushBuffer = codeOnlyBuffer(code.flush)
|
||||
const syncBuffer = codeOnlyBuffer(code.sync)
|
||||
const endBuffer = codeOnlyBuffer(code.end)
|
||||
const copyDoneBuffer = codeOnlyBuffer(code.copyDone)
|
||||
|
||||
const serialize = {
|
||||
startup,
|
||||
password,
|
||||
requestSsl,
|
||||
sendSASLInitialResponseMessage,
|
||||
sendSCRAMClientFinalMessage,
|
||||
query,
|
||||
parse,
|
||||
bind,
|
||||
execute,
|
||||
describe,
|
||||
close,
|
||||
flush: () => flushBuffer,
|
||||
sync: () => syncBuffer,
|
||||
end: () => endBuffer,
|
||||
copyData,
|
||||
copyDone: () => copyDoneBuffer,
|
||||
copyFail,
|
||||
cancel,
|
||||
}
|
||||
|
||||
export { serialize }
|
67
backend/apis/nodejs/node_modules/pg-protocol/src/testing/buffer-list.ts
generated
vendored
Normal file
67
backend/apis/nodejs/node_modules/pg-protocol/src/testing/buffer-list.ts
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
export default class BufferList {
|
||||
constructor(public buffers: Buffer[] = []) {}
|
||||
|
||||
public add(buffer: Buffer, front?: boolean) {
|
||||
this.buffers[front ? 'unshift' : 'push'](buffer)
|
||||
return this
|
||||
}
|
||||
|
||||
public addInt16(val: number, front?: boolean) {
|
||||
return this.add(Buffer.from([val >>> 8, val >>> 0]), front)
|
||||
}
|
||||
|
||||
public getByteLength() {
|
||||
return this.buffers.reduce(function (previous, current) {
|
||||
return previous + current.length
|
||||
}, 0)
|
||||
}
|
||||
|
||||
public addInt32(val: number, first?: boolean) {
|
||||
return this.add(
|
||||
Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]),
|
||||
first
|
||||
)
|
||||
}
|
||||
|
||||
public addCString(val: string, front?: boolean) {
|
||||
var len = Buffer.byteLength(val)
|
||||
var buffer = Buffer.alloc(len + 1)
|
||||
buffer.write(val)
|
||||
buffer[len] = 0
|
||||
return this.add(buffer, front)
|
||||
}
|
||||
|
||||
public addString(val: string, front?: boolean) {
|
||||
var len = Buffer.byteLength(val)
|
||||
var buffer = Buffer.alloc(len)
|
||||
buffer.write(val)
|
||||
return this.add(buffer, front)
|
||||
}
|
||||
|
||||
public addChar(char: string, first?: boolean) {
|
||||
return this.add(Buffer.from(char, 'utf8'), first)
|
||||
}
|
||||
|
||||
public addByte(byte: number) {
|
||||
return this.add(Buffer.from([byte]))
|
||||
}
|
||||
|
||||
public join(appendLength?: boolean, char?: string): Buffer {
|
||||
var length = this.getByteLength()
|
||||
if (appendLength) {
|
||||
this.addInt32(length + 4, true)
|
||||
return this.join(false, char)
|
||||
}
|
||||
if (char) {
|
||||
this.addChar(char, true)
|
||||
length++
|
||||
}
|
||||
var result = Buffer.alloc(length)
|
||||
var index = 0
|
||||
this.buffers.forEach(function (buffer) {
|
||||
buffer.copy(result, index, 0)
|
||||
index += buffer.length
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
166
backend/apis/nodejs/node_modules/pg-protocol/src/testing/test-buffers.ts
generated
vendored
Normal file
166
backend/apis/nodejs/node_modules/pg-protocol/src/testing/test-buffers.ts
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
// https://www.postgresql.org/docs/current/protocol-message-formats.html
|
||||
import BufferList from './buffer-list'
|
||||
|
||||
const buffers = {
|
||||
readyForQuery: function () {
|
||||
return new BufferList().add(Buffer.from('I')).join(true, 'Z')
|
||||
},
|
||||
|
||||
authenticationOk: function () {
|
||||
return new BufferList().addInt32(0).join(true, 'R')
|
||||
},
|
||||
|
||||
authenticationCleartextPassword: function () {
|
||||
return new BufferList().addInt32(3).join(true, 'R')
|
||||
},
|
||||
|
||||
authenticationMD5Password: function () {
|
||||
return new BufferList()
|
||||
.addInt32(5)
|
||||
.add(Buffer.from([1, 2, 3, 4]))
|
||||
.join(true, 'R')
|
||||
},
|
||||
|
||||
authenticationSASL: function () {
|
||||
return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R')
|
||||
},
|
||||
|
||||
authenticationSASLContinue: function () {
|
||||
return new BufferList().addInt32(11).addString('data').join(true, 'R')
|
||||
},
|
||||
|
||||
authenticationSASLFinal: function () {
|
||||
return new BufferList().addInt32(12).addString('data').join(true, 'R')
|
||||
},
|
||||
|
||||
parameterStatus: function (name: string, value: string) {
|
||||
return new BufferList().addCString(name).addCString(value).join(true, 'S')
|
||||
},
|
||||
|
||||
backendKeyData: function (processID: number, secretKey: number) {
|
||||
return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K')
|
||||
},
|
||||
|
||||
commandComplete: function (string: string) {
|
||||
return new BufferList().addCString(string).join(true, 'C')
|
||||
},
|
||||
|
||||
rowDescription: function (fields: any[]) {
|
||||
fields = fields || []
|
||||
var buf = new BufferList()
|
||||
buf.addInt16(fields.length)
|
||||
fields.forEach(function (field) {
|
||||
buf
|
||||
.addCString(field.name)
|
||||
.addInt32(field.tableID || 0)
|
||||
.addInt16(field.attributeNumber || 0)
|
||||
.addInt32(field.dataTypeID || 0)
|
||||
.addInt16(field.dataTypeSize || 0)
|
||||
.addInt32(field.typeModifier || 0)
|
||||
.addInt16(field.formatCode || 0)
|
||||
})
|
||||
return buf.join(true, 'T')
|
||||
},
|
||||
|
||||
parameterDescription: function (dataTypeIDs: number[]) {
|
||||
dataTypeIDs = dataTypeIDs || []
|
||||
var buf = new BufferList()
|
||||
buf.addInt16(dataTypeIDs.length)
|
||||
dataTypeIDs.forEach(function (dataTypeID) {
|
||||
buf.addInt32(dataTypeID)
|
||||
})
|
||||
return buf.join(true, 't')
|
||||
},
|
||||
|
||||
dataRow: function (columns: any[]) {
|
||||
columns = columns || []
|
||||
var buf = new BufferList()
|
||||
buf.addInt16(columns.length)
|
||||
columns.forEach(function (col) {
|
||||
if (col == null) {
|
||||
buf.addInt32(-1)
|
||||
} else {
|
||||
var strBuf = Buffer.from(col, 'utf8')
|
||||
buf.addInt32(strBuf.length)
|
||||
buf.add(strBuf)
|
||||
}
|
||||
})
|
||||
return buf.join(true, 'D')
|
||||
},
|
||||
|
||||
error: function (fields: any) {
|
||||
return buffers.errorOrNotice(fields).join(true, 'E')
|
||||
},
|
||||
|
||||
notice: function (fields: any) {
|
||||
return buffers.errorOrNotice(fields).join(true, 'N')
|
||||
},
|
||||
|
||||
errorOrNotice: function (fields: any) {
|
||||
fields = fields || []
|
||||
var buf = new BufferList()
|
||||
fields.forEach(function (field: any) {
|
||||
buf.addChar(field.type)
|
||||
buf.addCString(field.value)
|
||||
})
|
||||
return buf.add(Buffer.from([0])) // terminator
|
||||
},
|
||||
|
||||
parseComplete: function () {
|
||||
return new BufferList().join(true, '1')
|
||||
},
|
||||
|
||||
bindComplete: function () {
|
||||
return new BufferList().join(true, '2')
|
||||
},
|
||||
|
||||
notification: function (id: number, channel: string, payload: string) {
|
||||
return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A')
|
||||
},
|
||||
|
||||
emptyQuery: function () {
|
||||
return new BufferList().join(true, 'I')
|
||||
},
|
||||
|
||||
portalSuspended: function () {
|
||||
return new BufferList().join(true, 's')
|
||||
},
|
||||
|
||||
closeComplete: function () {
|
||||
return new BufferList().join(true, '3')
|
||||
},
|
||||
|
||||
copyIn: function (cols: number) {
|
||||
const list = new BufferList()
|
||||
// text mode
|
||||
.addByte(0)
|
||||
// column count
|
||||
.addInt16(cols)
|
||||
for (let i = 0; i < cols; i++) {
|
||||
list.addInt16(i)
|
||||
}
|
||||
return list.join(true, 'G')
|
||||
},
|
||||
|
||||
copyOut: function (cols: number) {
|
||||
const list = new BufferList()
|
||||
// text mode
|
||||
.addByte(0)
|
||||
// column count
|
||||
.addInt16(cols)
|
||||
for (let i = 0; i < cols; i++) {
|
||||
list.addInt16(i)
|
||||
}
|
||||
return list.join(true, 'H')
|
||||
},
|
||||
|
||||
copyData: function (bytes: Buffer) {
|
||||
return new BufferList().add(bytes).join(true, 'd')
|
||||
},
|
||||
|
||||
copyDone: function () {
|
||||
return new BufferList().join(true, 'c')
|
||||
},
|
||||
}
|
||||
|
||||
export default buffers
|
1
backend/apis/nodejs/node_modules/pg-protocol/src/types/chunky.d.ts
generated
vendored
Normal file
1
backend/apis/nodejs/node_modules/pg-protocol/src/types/chunky.d.ts
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'chunky'
|
Reference in New Issue
Block a user