mirror of
https://github.com/brianc/node-postgres.git
synced 2025-12-08 20:16:25 +00:00
lint pg-protcol
This commit is contained in:
parent
cb928ded2a
commit
6adbcabf50
@ -2,14 +2,15 @@
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"plugin:prettier/recommended"
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"**/*.ts",
|
||||
"node_modules",
|
||||
"packages/pg",
|
||||
"packages/pg-protocol",
|
||||
"packages/pg-protocol/dist/**/*",
|
||||
"packages/pg-pool"
|
||||
],
|
||||
"parserOptions": {
|
||||
|
||||
@ -13,10 +13,11 @@
|
||||
"test": "yarn lerna exec yarn test",
|
||||
"build": "yarn lerna exec --scope pg-protocol yarn build",
|
||||
"pretest": "yarn build",
|
||||
"lint": "yarn lerna exec --parallel yarn lint"
|
||||
"lint": "eslint '*/**/*.{js,ts,tsx}'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
||||
"@typescript-eslint/parser": "^2.27.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
// file for microbenchmarking
|
||||
// file for microbenchmarking
|
||||
|
||||
import { Writer } from './buffer-writer'
|
||||
import { serialize } from './index'
|
||||
import { BufferReader } from './buffer-reader'
|
||||
import { Writer } from './buffer-writer';
|
||||
import { serialize } from './index';
|
||||
import { BufferReader } from './buffer-reader';
|
||||
|
||||
const LOOPS = 1000
|
||||
let count = 0
|
||||
let start = Date.now()
|
||||
const writer = new Writer()
|
||||
const LOOPS = 1000;
|
||||
let count = 0;
|
||||
let start = Date.now();
|
||||
const writer = new Writer();
|
||||
|
||||
const reader = new BufferReader()
|
||||
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0])
|
||||
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)
|
||||
console.log(Date.now() - start);
|
||||
return;
|
||||
}
|
||||
count++
|
||||
for(let i = 0; i < LOOPS; i++) {
|
||||
reader.setBuffer(0, buffer)
|
||||
reader.cstring()
|
||||
count++;
|
||||
for (let i = 0; i < LOOPS; i++) {
|
||||
reader.setBuffer(0, buffer);
|
||||
reader.cstring();
|
||||
}
|
||||
setImmediate(run)
|
||||
}
|
||||
setImmediate(run);
|
||||
};
|
||||
|
||||
run()
|
||||
run();
|
||||
|
||||
@ -6,8 +6,7 @@ export class BufferReader {
|
||||
// TODO(bmc): support non-utf8 encoding?
|
||||
private encoding: string = 'utf-8';
|
||||
|
||||
constructor(private offset: number = 0) {
|
||||
}
|
||||
constructor(private offset: number = 0) {}
|
||||
|
||||
public setBuffer(offset: number, buffer: Buffer): void {
|
||||
this.offset = offset;
|
||||
@ -40,8 +39,8 @@ export class BufferReader {
|
||||
|
||||
public cstring(): string {
|
||||
const start = this.offset;
|
||||
let end = start
|
||||
while(this.buffer[end++] !== 0) { };
|
||||
let end = start;
|
||||
while (this.buffer[end++] !== 0) {}
|
||||
this.offset = end;
|
||||
return this.buffer.toString(this.encoding, start, end - 1);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ export class Writer {
|
||||
private offset: number = 5;
|
||||
private headerPosition: number = 0;
|
||||
constructor(private size = 256) {
|
||||
this.buffer = Buffer.alloc(size)
|
||||
this.buffer = Buffer.alloc(size);
|
||||
}
|
||||
|
||||
private ensure(size: number): void {
|
||||
@ -22,28 +22,27 @@ export class Writer {
|
||||
|
||||
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);
|
||||
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);
|
||||
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.buffer.write(string, this.offset, 'utf-8');
|
||||
this.offset += len;
|
||||
}
|
||||
|
||||
@ -51,7 +50,7 @@ export class Writer {
|
||||
return this;
|
||||
}
|
||||
|
||||
public addString(string: string = ""): Writer {
|
||||
public addString(string: string = ''): Writer {
|
||||
var len = Buffer.byteLength(string);
|
||||
this.ensure(len);
|
||||
this.buffer.write(string, this.offset);
|
||||
@ -70,8 +69,8 @@ export class Writer {
|
||||
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)
|
||||
const length = this.offset - (this.headerPosition + 1);
|
||||
this.buffer.writeInt32BE(length, this.headerPosition + 1);
|
||||
}
|
||||
return this.buffer.slice(code ? 0 : 5, this.offset);
|
||||
}
|
||||
@ -80,8 +79,7 @@ export class Writer {
|
||||
var result = this.join(code);
|
||||
this.offset = 5;
|
||||
this.headerPosition = 0;
|
||||
this.buffer = Buffer.allocUnsafe(this.size)
|
||||
this.buffer = Buffer.allocUnsafe(this.size);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
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'
|
||||
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 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 addRow = function (bufferList: BufferList, name: string, offset: number) {
|
||||
return bufferList.addCString(name) // field name
|
||||
return bufferList
|
||||
.addCString(name) // field name
|
||||
.addInt32(offset++) // table id
|
||||
.addInt16(offset++) // attribute of column number
|
||||
.addInt32(offset++) // objectId of field's data type
|
||||
.addInt16(offset++) // datatype size
|
||||
.addInt32(offset++) // type modifier
|
||||
.addInt16(0) // format code, 0 => text
|
||||
}
|
||||
.addInt16(0); // format code, 0 => text
|
||||
};
|
||||
|
||||
var row1 = {
|
||||
name: 'id',
|
||||
@ -31,274 +32,291 @@ var row1 = {
|
||||
dataTypeID: 3,
|
||||
dataTypeSize: 4,
|
||||
typeModifier: 5,
|
||||
formatCode: 0
|
||||
}
|
||||
var oneRowDescBuff = buffers.rowDescription([row1])
|
||||
row1.name = 'bang'
|
||||
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 twoRowBuf = buffers.rowDescription([
|
||||
row1,
|
||||
{
|
||||
name: 'whoah',
|
||||
tableID: 10,
|
||||
attributeNumber: 11,
|
||||
dataTypeID: 12,
|
||||
dataTypeSize: 13,
|
||||
typeModifier: 14,
|
||||
formatCode: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
var emptyRowFieldBuf = new BufferList()
|
||||
.addInt16(0)
|
||||
.join(true, 'D')
|
||||
var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D');
|
||||
|
||||
var emptyRowFieldBuf = buffers.dataRow([])
|
||||
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')
|
||||
.join(true, 'D');
|
||||
|
||||
var oneFieldBuf = buffers.dataRow(['test'])
|
||||
var oneFieldBuf = buffers.dataRow(['test']);
|
||||
|
||||
var expectedAuthenticationOkayMessage = {
|
||||
name: 'authenticationOk',
|
||||
length: 8
|
||||
}
|
||||
length: 8,
|
||||
};
|
||||
|
||||
var expectedParameterStatusMessage = {
|
||||
name: 'parameterStatus',
|
||||
parameterName: 'client_encoding',
|
||||
parameterValue: 'UTF8',
|
||||
length: 25
|
||||
}
|
||||
length: 25,
|
||||
};
|
||||
|
||||
var expectedBackendKeyDataMessage = {
|
||||
name: 'backendKeyData',
|
||||
processID: 1,
|
||||
secretKey: 2
|
||||
}
|
||||
secretKey: 2,
|
||||
};
|
||||
|
||||
var expectedReadyForQueryMessage = {
|
||||
name: 'readyForQuery',
|
||||
length: 5,
|
||||
status: 'I'
|
||||
}
|
||||
status: 'I',
|
||||
};
|
||||
|
||||
var expectedCommandCompleteMessage = {
|
||||
name: 'commandComplete',
|
||||
length: 13,
|
||||
text: 'SELECT 3'
|
||||
}
|
||||
text: 'SELECT 3',
|
||||
};
|
||||
var emptyRowDescriptionBuffer = new BufferList()
|
||||
.addInt16(0) // number of fields
|
||||
.join(true, 'T')
|
||||
.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'
|
||||
}]
|
||||
}
|
||||
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'
|
||||
}]
|
||||
}
|
||||
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 testForMessage = function (buffer: Buffer, expectedMessage: any) {
|
||||
it('recieves and parses ' + expectedMessage.name, async () => {
|
||||
const messages = await parseBuffers([buffer])
|
||||
const messages = await parseBuffers([buffer]);
|
||||
const [lastMessage] = messages;
|
||||
|
||||
for (const key in expectedMessage) {
|
||||
assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
|
||||
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 plainPasswordBuffer = buffers.authenticationCleartextPassword();
|
||||
var md5PasswordBuffer = buffers.authenticationMD5Password();
|
||||
var SASLBuffer = buffers.authenticationSASL();
|
||||
var SASLContinueBuffer = buffers.authenticationSASLContinue();
|
||||
var SASLFinalBuffer = buffers.authenticationSASLFinal();
|
||||
|
||||
var expectedPlainPasswordMessage = {
|
||||
name: 'authenticationCleartextPassword'
|
||||
}
|
||||
name: 'authenticationCleartextPassword',
|
||||
};
|
||||
|
||||
var expectedMD5PasswordMessage = {
|
||||
name: 'authenticationMD5Password',
|
||||
salt: Buffer.from([1, 2, 3, 4])
|
||||
}
|
||||
salt: Buffer.from([1, 2, 3, 4]),
|
||||
};
|
||||
|
||||
var expectedSASLMessage = {
|
||||
name: 'authenticationSASL',
|
||||
mechanisms: ['SCRAM-SHA-256']
|
||||
}
|
||||
mechanisms: ['SCRAM-SHA-256'],
|
||||
};
|
||||
|
||||
var expectedSASLContinueMessage = {
|
||||
name: 'authenticationSASLContinue',
|
||||
data: 'data',
|
||||
}
|
||||
};
|
||||
|
||||
var expectedSASLFinalMessage = {
|
||||
name: 'authenticationSASLFinal',
|
||||
data: 'data',
|
||||
}
|
||||
};
|
||||
|
||||
var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
|
||||
var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom');
|
||||
var expectedNotificationResponseMessage = {
|
||||
name: 'notification',
|
||||
processId: 4,
|
||||
channel: 'hi',
|
||||
payload: 'boom'
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage)
|
||||
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage);
|
||||
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage);
|
||||
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage);
|
||||
testForMessage(SASLBuffer, expectedSASLMessage);
|
||||
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage);
|
||||
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage);
|
||||
|
||||
testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
|
||||
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
|
||||
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage)
|
||||
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage)
|
||||
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage)
|
||||
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'
|
||||
})
|
||||
name: 'noData',
|
||||
});
|
||||
|
||||
describe('rowDescription messages', function () {
|
||||
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
|
||||
testForMessage(oneRowDescBuff, expectedOneRowMessage)
|
||||
testForMessage(twoRowBuf, expectedTwoRowMessage)
|
||||
})
|
||||
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage);
|
||||
testForMessage(oneRowDescBuff, expectedOneRowMessage);
|
||||
testForMessage(twoRowBuf, expectedTwoRowMessage);
|
||||
});
|
||||
|
||||
describe('parsing rows', function () {
|
||||
describe('parsing empty row', function () {
|
||||
testForMessage(emptyRowFieldBuf, {
|
||||
name: 'dataRow',
|
||||
fieldCount: 0
|
||||
})
|
||||
})
|
||||
fieldCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
describe('parsing data row with fields', function () {
|
||||
testForMessage(oneFieldBuf, {
|
||||
name: 'dataRow',
|
||||
fieldCount: 1,
|
||||
fields: ['test']
|
||||
})
|
||||
})
|
||||
})
|
||||
fields: ['test'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('notice message', function () {
|
||||
// this uses the same logic as error message
|
||||
var buff = buffers.notice([{ type: 'C', value: 'code' }])
|
||||
var buff = buffers.notice([{ type: 'C', value: 'code' }]);
|
||||
testForMessage(buff, {
|
||||
name: 'notice',
|
||||
code: 'code'
|
||||
})
|
||||
})
|
||||
code: 'code',
|
||||
});
|
||||
});
|
||||
|
||||
testForMessage(buffers.error([]), {
|
||||
name: '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'
|
||||
}])
|
||||
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',
|
||||
@ -313,184 +331,179 @@ describe('PgPacketStream', function () {
|
||||
where: 'where',
|
||||
file: 'file',
|
||||
line: 'line',
|
||||
routine: 'routine'
|
||||
})
|
||||
})
|
||||
routine: 'routine',
|
||||
});
|
||||
});
|
||||
|
||||
testForMessage(parseCompleteBuffer, {
|
||||
name: 'parseComplete'
|
||||
})
|
||||
name: 'parseComplete',
|
||||
});
|
||||
|
||||
testForMessage(bindCompleteBuffer, {
|
||||
name: 'bindComplete'
|
||||
})
|
||||
name: 'bindComplete',
|
||||
});
|
||||
|
||||
testForMessage(bindCompleteBuffer, {
|
||||
name: 'bindComplete'
|
||||
})
|
||||
name: 'bindComplete',
|
||||
});
|
||||
|
||||
testForMessage(buffers.closeComplete(), {
|
||||
name: 'closeComplete'
|
||||
})
|
||||
name: 'closeComplete',
|
||||
});
|
||||
|
||||
describe('parses portal suspended message', function () {
|
||||
testForMessage(portalSuspendedBuffer, {
|
||||
name: 'portalSuspended'
|
||||
})
|
||||
})
|
||||
name: 'portalSuspended',
|
||||
});
|
||||
});
|
||||
|
||||
describe('parses replication start message', function () {
|
||||
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
|
||||
name: 'replicationStart',
|
||||
length: 4
|
||||
})
|
||||
})
|
||||
length: 4,
|
||||
});
|
||||
});
|
||||
|
||||
describe('copy', () => {
|
||||
testForMessage(buffers.copyIn(0), {
|
||||
name: 'copyInResponse',
|
||||
length: 7,
|
||||
binary: false,
|
||||
columnTypes: []
|
||||
})
|
||||
columnTypes: [],
|
||||
});
|
||||
|
||||
testForMessage(buffers.copyIn(2), {
|
||||
name: 'copyInResponse',
|
||||
length: 11,
|
||||
binary: false,
|
||||
columnTypes: [0, 1]
|
||||
})
|
||||
columnTypes: [0, 1],
|
||||
});
|
||||
|
||||
testForMessage(buffers.copyOut(0), {
|
||||
name: 'copyOutResponse',
|
||||
length: 7,
|
||||
binary: false,
|
||||
columnTypes: []
|
||||
})
|
||||
columnTypes: [],
|
||||
});
|
||||
|
||||
testForMessage(buffers.copyOut(3), {
|
||||
name: 'copyOutResponse',
|
||||
length: 13,
|
||||
binary: false,
|
||||
columnTypes: [0, 1, 2]
|
||||
})
|
||||
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])
|
||||
})
|
||||
})
|
||||
|
||||
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, '!'])
|
||||
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], '!')
|
||||
})
|
||||
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 testMessageRecievedAfterSpiltAt = 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)
|
||||
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([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], '!')
|
||||
}
|
||||
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 () {
|
||||
testMessageRecievedAfterSpiltAt(6)
|
||||
})
|
||||
testMessageRecievedAfterSpiltAt(6);
|
||||
});
|
||||
|
||||
it('parses when split at end', function () {
|
||||
testMessageRecievedAfterSpiltAt(2)
|
||||
})
|
||||
testMessageRecievedAfterSpiltAt(2);
|
||||
});
|
||||
|
||||
it('parses when split at beginning', function () {
|
||||
testMessageRecievedAfterSpiltAt(fullBuffer.length - 2)
|
||||
testMessageRecievedAfterSpiltAt(fullBuffer.length - 1)
|
||||
testMessageRecievedAfterSpiltAt(fullBuffer.length - 5)
|
||||
})
|
||||
})
|
||||
testMessageRecievedAfterSpiltAt(fullBuffer.length - 2);
|
||||
testMessageRecievedAfterSpiltAt(fullBuffer.length - 1);
|
||||
testMessageRecievedAfterSpiltAt(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 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.strictEqual(messages.length, 2);
|
||||
assert.deepEqual(messages[0], {
|
||||
name: 'dataRow',
|
||||
fieldCount: 1,
|
||||
length: 11,
|
||||
fields: ['!']
|
||||
})
|
||||
assert.equal(messages[0].fields[0], '!')
|
||||
fields: ['!'],
|
||||
});
|
||||
assert.equal(messages[0].fields[0], '!');
|
||||
assert.deepEqual(messages[1], {
|
||||
name: 'readyForQuery',
|
||||
length: 5,
|
||||
status: 'I'
|
||||
})
|
||||
}
|
||||
status: 'I',
|
||||
});
|
||||
};
|
||||
// sanity check
|
||||
it('recieves both messages when packet is not split', async function () {
|
||||
const messages = await parseBuffers([fullBuffer])
|
||||
verifyMessages(messages)
|
||||
})
|
||||
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)
|
||||
}
|
||||
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('recieves both messages when packet is split', function () {
|
||||
it('in the middle', function () {
|
||||
return splitAndVerifyTwoMessages(11)
|
||||
})
|
||||
return splitAndVerifyTwoMessages(11);
|
||||
});
|
||||
it('at the front', function () {
|
||||
return Promise.all([
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 1),
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 4),
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 6)
|
||||
])
|
||||
})
|
||||
splitAndVerifyTwoMessages(fullBuffer.length - 6),
|
||||
]);
|
||||
});
|
||||
|
||||
it('at the end', function () {
|
||||
return Promise.all([
|
||||
splitAndVerifyTwoMessages(8),
|
||||
splitAndVerifyTwoMessages(1)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { BackendMessage } from './messages';
|
||||
import { serialize } from './serializer';
|
||||
import { Parser, MessageCallback } from './parser'
|
||||
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()))
|
||||
const parser = new Parser();
|
||||
stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback));
|
||||
return new Promise((resolve) => stream.on('end', () => resolve()));
|
||||
}
|
||||
|
||||
export { serialize };
|
||||
|
||||
@ -42,37 +42,37 @@ export const parseComplete: BackendMessage = {
|
||||
export const bindComplete: BackendMessage = {
|
||||
name: MessageName.bindComplete,
|
||||
length: 5,
|
||||
}
|
||||
};
|
||||
|
||||
export const closeComplete: BackendMessage = {
|
||||
name: MessageName.closeComplete,
|
||||
length: 5,
|
||||
}
|
||||
};
|
||||
|
||||
export const noData: BackendMessage = {
|
||||
name: MessageName.noData,
|
||||
length: 5
|
||||
}
|
||||
length: 5,
|
||||
};
|
||||
|
||||
export const portalSuspended: BackendMessage = {
|
||||
name: MessageName.portalSuspended,
|
||||
length: 5,
|
||||
}
|
||||
};
|
||||
|
||||
export const replicationStart: BackendMessage = {
|
||||
name: MessageName.replicationStart,
|
||||
length: 4,
|
||||
}
|
||||
};
|
||||
|
||||
export const emptyQuery: BackendMessage = {
|
||||
name: MessageName.emptyQuery,
|
||||
length: 4,
|
||||
}
|
||||
};
|
||||
|
||||
export const copyDone: BackendMessage = {
|
||||
name: MessageName.copyDone,
|
||||
length: 4,
|
||||
}
|
||||
};
|
||||
|
||||
interface NoticeOrError {
|
||||
message: string | undefined;
|
||||
@ -112,77 +112,89 @@ export class DatabaseError extends Error implements NoticeOrError {
|
||||
public line: string | undefined;
|
||||
public routine: string | undefined;
|
||||
constructor(message: string, public readonly length: number, public readonly name: MessageName) {
|
||||
super(message)
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyDataMessage {
|
||||
public readonly name = MessageName.copyData;
|
||||
constructor(public readonly length: number, public readonly chunk: Buffer) {
|
||||
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
}
|
||||
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 = MessageName.rowDescription;
|
||||
public readonly fields: Field[];
|
||||
constructor(public readonly length: number, public readonly fieldCount: number) {
|
||||
this.fields = new Array(this.fieldCount)
|
||||
this.fields = new Array(this.fieldCount);
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterStatusMessage {
|
||||
public readonly name: MessageName = MessageName.parameterStatus;
|
||||
constructor(public readonly length: number, public readonly parameterName: string, public readonly parameterValue: string) {
|
||||
|
||||
}
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly parameterName: string,
|
||||
public readonly parameterValue: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class AuthenticationMD5Password implements BackendMessage {
|
||||
public readonly name: MessageName = MessageName.authenticationMD5Password;
|
||||
constructor(public readonly length: number, public readonly salt: Buffer) {
|
||||
}
|
||||
constructor(public readonly length: number, public readonly salt: Buffer) {}
|
||||
}
|
||||
|
||||
export class BackendKeyDataMessage {
|
||||
public readonly name: MessageName = MessageName.backendKeyData;
|
||||
constructor(public readonly length: number, public readonly processID: number, public readonly secretKey: number) {
|
||||
}
|
||||
constructor(public readonly length: number, public readonly processID: number, public readonly secretKey: number) {}
|
||||
}
|
||||
|
||||
export class NotificationResponseMessage {
|
||||
public readonly name: MessageName = MessageName.notification;
|
||||
constructor(public readonly length: number, public readonly processId: number, public readonly channel: string, public readonly payload: string) {
|
||||
}
|
||||
constructor(
|
||||
public readonly length: number,
|
||||
public readonly processId: number,
|
||||
public readonly channel: string,
|
||||
public readonly payload: string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class ReadyForQueryMessage {
|
||||
public readonly name: MessageName = MessageName.readyForQuery;
|
||||
constructor(public readonly length: number, public readonly status: string) {
|
||||
}
|
||||
constructor(public readonly length: number, public readonly status: string) {}
|
||||
}
|
||||
|
||||
export class CommandCompleteMessage {
|
||||
public readonly name: MessageName = MessageName.commandComplete
|
||||
constructor(public readonly length: number, public readonly text: string) {
|
||||
}
|
||||
public readonly name: MessageName = MessageName.commandComplete;
|
||||
constructor(public readonly length: number, public readonly text: string) {}
|
||||
}
|
||||
|
||||
export class DataRowMessage {
|
||||
public readonly fieldCount: number;
|
||||
public readonly name: MessageName = MessageName.dataRow
|
||||
public readonly name: MessageName = MessageName.dataRow;
|
||||
constructor(public length: number, public fields: any[]) {
|
||||
this.fieldCount = fields.length;
|
||||
}
|
||||
|
||||
@ -1,85 +1,79 @@
|
||||
import assert from 'assert'
|
||||
import { serialize } from './serializer'
|
||||
import BufferList from './testing/buffer-list'
|
||||
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("'utf-8'")
|
||||
.addCString('').join(true))
|
||||
})
|
||||
database: 'bang',
|
||||
});
|
||||
assert.deepEqual(
|
||||
actual,
|
||||
new BufferList()
|
||||
.addInt16(3)
|
||||
.addInt16(0)
|
||||
.addCString('user')
|
||||
.addCString('brian')
|
||||
.addCString('database')
|
||||
.addCString('bang')
|
||||
.addCString('client_encoding')
|
||||
.addCString("'utf-8'")
|
||||
.addCString('')
|
||||
.join(true)
|
||||
);
|
||||
});
|
||||
|
||||
it('builds password message', function () {
|
||||
const actual = serialize.password('!')
|
||||
assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p'))
|
||||
})
|
||||
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)
|
||||
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'))
|
||||
})
|
||||
|
||||
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'))
|
||||
})
|
||||
|
||||
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'))
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
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]
|
||||
})
|
||||
types: [1, 2, 3, 4],
|
||||
});
|
||||
var expected = new BufferList()
|
||||
.addCString('force')
|
||||
.addCString('select * from bang where name = $1')
|
||||
@ -87,16 +81,15 @@ describe('serializer', () => {
|
||||
.addInt32(1)
|
||||
.addInt32(2)
|
||||
.addInt32(3)
|
||||
.addInt32(4).join(true, 'P')
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
.addInt32(4)
|
||||
.join(true, 'P');
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind messages', function () {
|
||||
it('with no values', function () {
|
||||
const actual = serialize.bind()
|
||||
const actual = serialize.bind();
|
||||
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('')
|
||||
@ -104,18 +97,18 @@ describe('serializer', () => {
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
.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']
|
||||
})
|
||||
values: ['1', 'hi', null, 'zing'],
|
||||
});
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('bang') // portal name
|
||||
.addCString('bang') // portal name
|
||||
.addCString('woo') // statement name
|
||||
.addInt16(0)
|
||||
.addInt16(4)
|
||||
@ -127,25 +120,25 @@ describe('serializer', () => {
|
||||
.addInt32(4)
|
||||
.add(Buffer.from('zing'))
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
})
|
||||
.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')]
|
||||
})
|
||||
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')],
|
||||
});
|
||||
var expectedBuffer = new BufferList()
|
||||
.addCString('bang') // portal name
|
||||
.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) // value count
|
||||
.addInt16(0) // string
|
||||
.addInt16(0) // string
|
||||
.addInt16(0) // string
|
||||
.addInt16(1) // binary
|
||||
.addInt16(4)
|
||||
.addInt32(1)
|
||||
.add(Buffer.from('1'))
|
||||
@ -155,102 +148,96 @@ describe('serializer', () => {
|
||||
.addInt32(4)
|
||||
.add(Buffer.from('zing', 'utf-8'))
|
||||
.addInt16(0)
|
||||
.join(true, 'B')
|
||||
assert.deepEqual(actual, expectedBuffer)
|
||||
})
|
||||
.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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
const actual = serialize.cancel(3, 4);
|
||||
const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true);
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,32 @@
|
||||
import { TransformOptions } from 'stream';
|
||||
import { Mode, bindComplete, parseComplete, closeComplete, noData, portalSuspended, copyDone, replicationStart, emptyQuery, ReadyForQueryMessage, CommandCompleteMessage, CopyDataMessage, CopyResponse, NotificationResponseMessage, RowDescriptionMessage, Field, DataRowMessage, ParameterStatusMessage, BackendKeyDataMessage, DatabaseError, BackendMessage, MessageName, AuthenticationMD5Password, NoticeMessage } from './messages';
|
||||
import {
|
||||
Mode,
|
||||
bindComplete,
|
||||
parseComplete,
|
||||
closeComplete,
|
||||
noData,
|
||||
portalSuspended,
|
||||
copyDone,
|
||||
replicationStart,
|
||||
emptyQuery,
|
||||
ReadyForQueryMessage,
|
||||
CommandCompleteMessage,
|
||||
CopyDataMessage,
|
||||
CopyResponse,
|
||||
NotificationResponseMessage,
|
||||
RowDescriptionMessage,
|
||||
Field,
|
||||
DataRowMessage,
|
||||
ParameterStatusMessage,
|
||||
BackendKeyDataMessage,
|
||||
DatabaseError,
|
||||
BackendMessage,
|
||||
MessageName,
|
||||
AuthenticationMD5Password,
|
||||
NoticeMessage,
|
||||
} from './messages';
|
||||
import { BufferReader } from './buffer-reader';
|
||||
import assert from 'assert'
|
||||
import assert from 'assert';
|
||||
|
||||
// every message is prefixed with a single bye
|
||||
const CODE_LENGTH = 1;
|
||||
@ -14,13 +39,13 @@ const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH;
|
||||
export type Packet = {
|
||||
code: number;
|
||||
packet: Buffer;
|
||||
}
|
||||
};
|
||||
|
||||
const emptyBuffer = Buffer.allocUnsafe(0);
|
||||
|
||||
type StreamOptions = TransformOptions & {
|
||||
mode: Mode
|
||||
}
|
||||
mode: Mode;
|
||||
};
|
||||
|
||||
const enum MessageCodes {
|
||||
DataRow = 0x44, // D
|
||||
@ -55,7 +80,7 @@ export class Parser {
|
||||
|
||||
constructor(opts?: StreamOptions) {
|
||||
if (opts?.mode === 'binary') {
|
||||
throw new Error('Binary mode not supported yet')
|
||||
throw new Error('Binary mode not supported yet');
|
||||
}
|
||||
this.mode = opts?.mode || 'text';
|
||||
}
|
||||
@ -64,11 +89,11 @@ export class Parser {
|
||||
let combinedBuffer = buffer;
|
||||
if (this.remainingBuffer.byteLength) {
|
||||
combinedBuffer = Buffer.allocUnsafe(this.remainingBuffer.byteLength + buffer.byteLength);
|
||||
this.remainingBuffer.copy(combinedBuffer)
|
||||
buffer.copy(combinedBuffer, this.remainingBuffer.byteLength)
|
||||
this.remainingBuffer.copy(combinedBuffer);
|
||||
buffer.copy(combinedBuffer, this.remainingBuffer.byteLength);
|
||||
}
|
||||
let offset = 0;
|
||||
while ((offset + HEADER_LENGTH) <= combinedBuffer.byteLength) {
|
||||
while (offset + HEADER_LENGTH <= combinedBuffer.byteLength) {
|
||||
// code is 1 byte long - it identifies the message type
|
||||
const code = combinedBuffer[offset];
|
||||
|
||||
@ -79,7 +104,7 @@ export class Parser {
|
||||
|
||||
if (fullMessageLength + offset <= combinedBuffer.byteLength) {
|
||||
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, combinedBuffer);
|
||||
callback(message)
|
||||
callback(message);
|
||||
offset += fullMessageLength;
|
||||
} else {
|
||||
break;
|
||||
@ -89,9 +114,8 @@ export class Parser {
|
||||
if (offset === combinedBuffer.byteLength) {
|
||||
this.remainingBuffer = emptyBuffer;
|
||||
} else {
|
||||
this.remainingBuffer = combinedBuffer.slice(offset)
|
||||
this.remainingBuffer = combinedBuffer.slice(offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage {
|
||||
@ -139,14 +163,14 @@ export class Parser {
|
||||
case MessageCodes.CopyData:
|
||||
return this.parseCopyData(offset, length, bytes);
|
||||
default:
|
||||
assert.fail(`unknown message code: ${code.toString(16)}`)
|
||||
assert.fail(`unknown message code: ${code.toString(16)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes);
|
||||
const status = this.reader.string(1);
|
||||
return new ReadyForQueryMessage(length, status)
|
||||
return new ReadyForQueryMessage(length, status);
|
||||
}
|
||||
|
||||
private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) {
|
||||
@ -161,17 +185,17 @@ export class Parser {
|
||||
}
|
||||
|
||||
private parseCopyInMessage(offset: number, length: number, bytes: Buffer) {
|
||||
return this.parseCopyMessage(offset, length, bytes, MessageName.copyInResponse)
|
||||
return this.parseCopyMessage(offset, length, bytes, MessageName.copyInResponse);
|
||||
}
|
||||
|
||||
private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) {
|
||||
return this.parseCopyMessage(offset, length, bytes, MessageName.copyOutResponse)
|
||||
return this.parseCopyMessage(offset, length, bytes, MessageName.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 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();
|
||||
@ -189,23 +213,23 @@ export class Parser {
|
||||
|
||||
private parseRowDescriptionMessage(offset: number, length: number, bytes: Buffer) {
|
||||
this.reader.setBuffer(offset, bytes);
|
||||
const fieldCount = this.reader.int16()
|
||||
const fieldCount = this.reader.int16();
|
||||
const message = new RowDescriptionMessage(length, fieldCount);
|
||||
for (let i = 0; i < fieldCount; i++) {
|
||||
message.fields[i] = this.parseField()
|
||||
message.fields[i] = this.parseField();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
private parseField(): Field {
|
||||
const name = this.reader.cstring()
|
||||
const tableID = this.reader.int32()
|
||||
const columnID = this.reader.int16()
|
||||
const dataTypeID = this.reader.int32()
|
||||
const dataTypeSize = this.reader.int16()
|
||||
const dataTypeModifier = this.reader.int32()
|
||||
const name = this.reader.cstring();
|
||||
const tableID = this.reader.int32();
|
||||
const columnID = this.reader.int16();
|
||||
const dataTypeID = this.reader.int32();
|
||||
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)
|
||||
return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode);
|
||||
}
|
||||
|
||||
private parseDataRowMessage(offset: number, length: number, bytes: Buffer) {
|
||||
@ -215,7 +239,7 @@ export class Parser {
|
||||
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)
|
||||
fields[i] = len === -1 ? null : this.reader.string(len);
|
||||
}
|
||||
return new DataRowMessage(length, fields);
|
||||
}
|
||||
@ -223,21 +247,20 @@ export class Parser {
|
||||
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)
|
||||
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)
|
||||
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()
|
||||
const code = this.reader.int32();
|
||||
// TODO(bmc): maybe better types here
|
||||
const message: BackendMessage & any = {
|
||||
name: MessageName.authenticationOk,
|
||||
@ -249,71 +272,74 @@ export class Parser {
|
||||
break;
|
||||
case 3: // AuthenticationCleartextPassword
|
||||
if (message.length === 8) {
|
||||
message.name = MessageName.authenticationCleartextPassword
|
||||
message.name = MessageName.authenticationCleartextPassword;
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 5: // AuthenticationMD5Password
|
||||
if (message.length === 12) {
|
||||
message.name = MessageName.authenticationMD5Password
|
||||
message.name = MessageName.authenticationMD5Password;
|
||||
const salt = this.reader.bytes(4);
|
||||
return new AuthenticationMD5Password(length, salt);
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 10: // AuthenticationSASL
|
||||
message.name = MessageName.authenticationSASL
|
||||
message.mechanisms = []
|
||||
message.name = MessageName.authenticationSASL;
|
||||
message.mechanisms = [];
|
||||
let mechanism: string;
|
||||
do {
|
||||
mechanism = this.reader.cstring()
|
||||
mechanism = this.reader.cstring();
|
||||
|
||||
if (mechanism) {
|
||||
message.mechanisms.push(mechanism)
|
||||
message.mechanisms.push(mechanism);
|
||||
}
|
||||
} while (mechanism)
|
||||
} while (mechanism);
|
||||
break;
|
||||
case 11: // AuthenticationSASLContinue
|
||||
message.name = MessageName.authenticationSASLContinue
|
||||
message.data = this.reader.string(length - 4)
|
||||
message.name = MessageName.authenticationSASLContinue;
|
||||
message.data = this.reader.string(length - 4);
|
||||
break;
|
||||
case 12: // AuthenticationSASLFinal
|
||||
message.name = MessageName.authenticationSASLFinal
|
||||
message.data = this.reader.string(length - 4)
|
||||
message.name = MessageName.authenticationSASLFinal;
|
||||
message.data = this.reader.string(length - 4);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown authenticationOk message type ' + code)
|
||||
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)
|
||||
const fields: Record<string, string> = {};
|
||||
let fieldType = this.reader.string(1);
|
||||
while (fieldType !== '\0') {
|
||||
fields[fieldType] = this.reader.cstring()
|
||||
fieldType = this.reader.string(1)
|
||||
fields[fieldType] = this.reader.cstring();
|
||||
fieldType = this.reader.string(1);
|
||||
}
|
||||
|
||||
const messageValue = fields.M
|
||||
const messageValue = fields.M;
|
||||
|
||||
const message = name === MessageName.notice ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name)
|
||||
const message =
|
||||
name === MessageName.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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Writer } from './buffer-writer'
|
||||
import { Writer } from './buffer-writer';
|
||||
|
||||
const enum code {
|
||||
startup = 0x70,
|
||||
@ -13,67 +13,61 @@ const enum code {
|
||||
describe = 0x44,
|
||||
copyFromChunk = 0x64,
|
||||
copyDone = 0x63,
|
||||
copyFail = 0x66
|
||||
copyFail = 0x66,
|
||||
}
|
||||
|
||||
const writer = new Writer()
|
||||
const writer = new Writer();
|
||||
|
||||
const startup = (opts: Record<string, string>): Buffer => {
|
||||
// protocol version
|
||||
writer.addInt16(3).addInt16(0)
|
||||
writer.addInt16(3).addInt16(0);
|
||||
for (const key of Object.keys(opts)) {
|
||||
writer.addCString(key).addCString(opts[key])
|
||||
writer.addCString(key).addCString(opts[key]);
|
||||
}
|
||||
|
||||
writer.addCString('client_encoding').addCString("'utf-8'")
|
||||
writer.addCString('client_encoding').addCString("'utf-8'");
|
||||
|
||||
var bodyBuffer = writer.addCString('').flush()
|
||||
var bodyBuffer = writer.addCString('').flush();
|
||||
// this message is sent without a code
|
||||
|
||||
var length = bodyBuffer.length + 4
|
||||
var length = bodyBuffer.length + 4;
|
||||
|
||||
return new Writer()
|
||||
.addInt32(length)
|
||||
.add(bodyBuffer)
|
||||
.flush()
|
||||
}
|
||||
return new Writer().addInt32(length).add(bodyBuffer).flush();
|
||||
};
|
||||
|
||||
const requestSsl = (): Buffer => {
|
||||
const response = Buffer.allocUnsafe(8)
|
||||
const response = Buffer.allocUnsafe(8);
|
||||
response.writeInt32BE(8, 0);
|
||||
response.writeInt32BE(80877103, 4)
|
||||
return response
|
||||
}
|
||||
response.writeInt32BE(80877103, 4);
|
||||
return response;
|
||||
};
|
||||
|
||||
const password = (password: string): Buffer => {
|
||||
return writer.addCString(password).flush(code.startup)
|
||||
}
|
||||
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)
|
||||
writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse);
|
||||
|
||||
return writer.flush(code.startup)
|
||||
}
|
||||
return writer.flush(code.startup);
|
||||
};
|
||||
|
||||
const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer {
|
||||
return writer.addString(additionalData).flush(code.startup)
|
||||
}
|
||||
return writer.addString(additionalData).flush(code.startup);
|
||||
};
|
||||
|
||||
const query = (text: string): Buffer => {
|
||||
return writer.addCString(text).flush(code.query)
|
||||
}
|
||||
return writer.addCString(text).flush(code.query);
|
||||
};
|
||||
|
||||
type ParseOpts = {
|
||||
name?: string;
|
||||
types?: number[];
|
||||
text: string;
|
||||
}
|
||||
};
|
||||
|
||||
const emptyArray: any[] = []
|
||||
const emptyArray: any[] = [];
|
||||
|
||||
const parse = (query: ParseOpts): Buffer => {
|
||||
// expect something like this:
|
||||
@ -82,171 +76,169 @@ const parse = (query: ParseOpts): Buffer => {
|
||||
// types: ['int8', 'bool'] }
|
||||
|
||||
// normalize missing query names to allow for null
|
||||
const name = query.name || ''
|
||||
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')
|
||||
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
|
||||
const types = query.types || emptyArray;
|
||||
|
||||
var len = types.length
|
||||
var len = types.length;
|
||||
|
||||
var buffer = writer
|
||||
.addCString(name) // name of query
|
||||
.addCString(query.text) // actual query text
|
||||
.addInt16(len)
|
||||
.addInt16(len);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
buffer.addInt32(types[i])
|
||||
buffer.addInt32(types[i]);
|
||||
}
|
||||
|
||||
return writer.flush(code.parse)
|
||||
}
|
||||
return writer.flush(code.parse);
|
||||
};
|
||||
|
||||
type BindOpts = {
|
||||
portal?: string;
|
||||
binary?: boolean;
|
||||
statement?: string;
|
||||
values?: any[];
|
||||
}
|
||||
};
|
||||
|
||||
const bind = (config: BindOpts = {}): Buffer => {
|
||||
// normalize config
|
||||
const portal = config.portal || ''
|
||||
const statement = config.statement || ''
|
||||
const binary = config.binary || false
|
||||
var values = config.values || emptyArray
|
||||
var len = values.length
|
||||
const portal = config.portal || '';
|
||||
const statement = config.statement || '';
|
||||
const binary = config.binary || false;
|
||||
var values = config.values || emptyArray;
|
||||
var len = values.length;
|
||||
|
||||
var useBinary = false
|
||||
var useBinary = false;
|
||||
// TODO(bmc): all the loops in here aren't nice, we can do better
|
||||
for (var j = 0; j < len; j++) {
|
||||
useBinary = useBinary || values[j] instanceof Buffer
|
||||
useBinary = useBinary || values[j] instanceof Buffer;
|
||||
}
|
||||
|
||||
var buffer = writer
|
||||
.addCString(portal)
|
||||
.addCString(statement)
|
||||
var buffer = writer.addCString(portal).addCString(statement);
|
||||
if (!useBinary) {
|
||||
buffer.addInt16(0)
|
||||
buffer.addInt16(0);
|
||||
} else {
|
||||
buffer.addInt16(len)
|
||||
buffer.addInt16(len);
|
||||
for (j = 0; j < len; j++) {
|
||||
buffer.addInt16(values[j] instanceof Buffer ? 1 : 0)
|
||||
buffer.addInt16(values[j] instanceof Buffer ? 1 : 0);
|
||||
}
|
||||
}
|
||||
buffer.addInt16(len)
|
||||
buffer.addInt16(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
var val = values[i]
|
||||
var val = values[i];
|
||||
if (val === null || typeof val === 'undefined') {
|
||||
buffer.addInt32(-1)
|
||||
buffer.addInt32(-1);
|
||||
} else if (val instanceof Buffer) {
|
||||
buffer.addInt32(val.length)
|
||||
buffer.add(val)
|
||||
buffer.addInt32(val.length);
|
||||
buffer.add(val);
|
||||
} else {
|
||||
buffer.addInt32(Buffer.byteLength(val))
|
||||
buffer.addString(val)
|
||||
buffer.addInt32(Buffer.byteLength(val));
|
||||
buffer.addString(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (binary) {
|
||||
buffer.addInt16(1) // format codes to use binary
|
||||
buffer.addInt16(1)
|
||||
buffer.addInt16(1); // format codes to use binary
|
||||
buffer.addInt16(1);
|
||||
} else {
|
||||
buffer.addInt16(0) // format codes to use text
|
||||
buffer.addInt16(0); // format codes to use text
|
||||
}
|
||||
return writer.flush(code.bind)
|
||||
}
|
||||
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 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) {
|
||||
if (!config || (!config.portal && !config.rows)) {
|
||||
return emptyExecute;
|
||||
}
|
||||
|
||||
const portal = config.portal || ''
|
||||
const rows = config.rows || 0
|
||||
const portal = config.portal || '';
|
||||
const rows = config.rows || 0;
|
||||
|
||||
const portalLength = Buffer.byteLength(portal)
|
||||
const len = 4 + portalLength + 1 + 4
|
||||
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')
|
||||
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)
|
||||
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)
|
||||
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',
|
||||
type: 'S' | 'P';
|
||||
name?: string;
|
||||
}
|
||||
};
|
||||
|
||||
const cstringMessage = (code: code, string: string): Buffer => {
|
||||
const stringLen = Buffer.byteLength(string)
|
||||
const len = 4 + stringLen + 1
|
||||
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 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 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;
|
||||
}
|
||||
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 text = `${msg.type}${msg.name || ''}`;
|
||||
return cstringMessage(code.close, text);
|
||||
};
|
||||
|
||||
const copyData = (chunk: Buffer): Buffer => {
|
||||
return writer.add(chunk).flush(code.copyFromChunk)
|
||||
}
|
||||
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 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 flushBuffer = codeOnlyBuffer(code.flush);
|
||||
const syncBuffer = codeOnlyBuffer(code.sync);
|
||||
const endBuffer = codeOnlyBuffer(code.end);
|
||||
const copyDoneBuffer = codeOnlyBuffer(code.copyDone);
|
||||
|
||||
const serialize = {
|
||||
startup,
|
||||
@ -266,7 +258,7 @@ const serialize = {
|
||||
copyData,
|
||||
copyDone: () => copyDoneBuffer,
|
||||
copyFail,
|
||||
cancel
|
||||
}
|
||||
cancel,
|
||||
};
|
||||
|
||||
export { serialize }
|
||||
export { serialize };
|
||||
|
||||
@ -1,79 +1,75 @@
|
||||
export default class BufferList {
|
||||
constructor(public buffers: Buffer[] = []) {
|
||||
|
||||
}
|
||||
constructor(public buffers: Buffer[] = []) {}
|
||||
|
||||
public add(buffer: Buffer, front?: boolean) {
|
||||
this.buffers[front ? 'unshift' : 'push'](buffer)
|
||||
return this
|
||||
this.buffers[front ? 'unshift' : 'push'](buffer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addInt16(val: number, front?: boolean) {
|
||||
return this.add(Buffer.from([(val >>> 8), (val >>> 0)]), front)
|
||||
return this.add(Buffer.from([val >>> 8, val >>> 0]), front);
|
||||
}
|
||||
|
||||
public getByteLength(initial?: number) {
|
||||
return this.buffers.reduce(function (previous, current) {
|
||||
return previous + current.length
|
||||
}, initial || 0)
|
||||
return previous + current.length;
|
||||
}, initial || 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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
return this.add(Buffer.from(char, 'utf8'), first);
|
||||
}
|
||||
|
||||
public addByte(byte: number) {
|
||||
return this.add(Buffer.from([byte]))
|
||||
return this.add(Buffer.from([byte]));
|
||||
}
|
||||
|
||||
public join(appendLength?: boolean, char?: string): Buffer {
|
||||
var length = this.getByteLength()
|
||||
var length = this.getByteLength();
|
||||
if (appendLength) {
|
||||
this.addInt32(length + 4, true)
|
||||
return this.join(false, char)
|
||||
this.addInt32(length + 4, true);
|
||||
return this.join(false, char);
|
||||
}
|
||||
if (char) {
|
||||
this.addChar(char, true)
|
||||
length++
|
||||
this.addChar(char, true);
|
||||
length++;
|
||||
}
|
||||
var result = Buffer.alloc(length)
|
||||
var index = 0
|
||||
var result = Buffer.alloc(length);
|
||||
var index = 0;
|
||||
this.buffers.forEach(function (buffer) {
|
||||
buffer.copy(result, index, 0)
|
||||
index += buffer.length
|
||||
})
|
||||
return result
|
||||
buffer.copy(result, index, 0);
|
||||
index += buffer.length;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static concat(): Buffer {
|
||||
var total = new BufferList()
|
||||
var total = new BufferList();
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
total.add(arguments[i])
|
||||
total.add(arguments[i]);
|
||||
}
|
||||
return total.join()
|
||||
return total.join();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,150 +1,123 @@
|
||||
// http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html
|
||||
import BufferList from './buffer-list'
|
||||
import BufferList from './buffer-list';
|
||||
|
||||
const buffers = {
|
||||
readyForQuery: function () {
|
||||
return new BufferList()
|
||||
.add(Buffer.from('I'))
|
||||
.join(true, 'Z')
|
||||
return new BufferList().add(Buffer.from('I')).join(true, 'Z');
|
||||
},
|
||||
|
||||
authenticationOk: function () {
|
||||
return new BufferList()
|
||||
.addInt32(0)
|
||||
.join(true, 'R')
|
||||
return new BufferList().addInt32(0).join(true, 'R');
|
||||
},
|
||||
|
||||
authenticationCleartextPassword: function () {
|
||||
return new BufferList()
|
||||
.addInt32(3)
|
||||
.join(true, 'R')
|
||||
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')
|
||||
.join(true, 'R');
|
||||
},
|
||||
|
||||
authenticationSASL: function () {
|
||||
return new BufferList()
|
||||
.addInt32(10)
|
||||
.addCString('SCRAM-SHA-256')
|
||||
.addCString('')
|
||||
.join(true, 'R')
|
||||
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')
|
||||
return new BufferList().addInt32(11).addString('data').join(true, 'R');
|
||||
},
|
||||
|
||||
authenticationSASLFinal: function () {
|
||||
return new BufferList()
|
||||
.addInt32(12)
|
||||
.addString('data')
|
||||
.join(true, 'R')
|
||||
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')
|
||||
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')
|
||||
return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K');
|
||||
},
|
||||
|
||||
commandComplete: function (string: string) {
|
||||
return new BufferList()
|
||||
.addCString(string)
|
||||
.join(true, 'C')
|
||||
return new BufferList().addCString(string).join(true, 'C');
|
||||
},
|
||||
|
||||
rowDescription: function (fields: any[]) {
|
||||
fields = fields || []
|
||||
var buf = new BufferList()
|
||||
buf.addInt16(fields.length)
|
||||
fields = fields || [];
|
||||
var buf = new BufferList();
|
||||
buf.addInt16(fields.length);
|
||||
fields.forEach(function (field) {
|
||||
buf.addCString(field.name)
|
||||
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')
|
||||
.addInt16(field.formatCode || 0);
|
||||
});
|
||||
return buf.join(true, 'T');
|
||||
},
|
||||
|
||||
dataRow: function (columns: any[]) {
|
||||
columns = columns || []
|
||||
var buf = new BufferList()
|
||||
buf.addInt16(columns.length)
|
||||
columns = columns || [];
|
||||
var buf = new BufferList();
|
||||
buf.addInt16(columns.length);
|
||||
columns.forEach(function (col) {
|
||||
if (col == null) {
|
||||
buf.addInt32(-1)
|
||||
buf.addInt32(-1);
|
||||
} else {
|
||||
var strBuf = Buffer.from(col, 'utf8')
|
||||
buf.addInt32(strBuf.length)
|
||||
buf.add(strBuf)
|
||||
var strBuf = Buffer.from(col, 'utf8');
|
||||
buf.addInt32(strBuf.length);
|
||||
buf.add(strBuf);
|
||||
}
|
||||
})
|
||||
return buf.join(true, 'D')
|
||||
});
|
||||
return buf.join(true, 'D');
|
||||
},
|
||||
|
||||
error: function (fields: any) {
|
||||
return buffers.errorOrNotice(fields).join(true, 'E')
|
||||
return buffers.errorOrNotice(fields).join(true, 'E');
|
||||
},
|
||||
|
||||
notice: function (fields: any) {
|
||||
return buffers.errorOrNotice(fields).join(true, 'N')
|
||||
return buffers.errorOrNotice(fields).join(true, 'N');
|
||||
},
|
||||
|
||||
errorOrNotice: function (fields: any) {
|
||||
fields = fields || []
|
||||
var buf = new BufferList()
|
||||
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
|
||||
buf.addChar(field.type);
|
||||
buf.addCString(field.value);
|
||||
});
|
||||
return buf.add(Buffer.from([0])); // terminator
|
||||
},
|
||||
|
||||
parseComplete: function () {
|
||||
return new BufferList().join(true, '1')
|
||||
return new BufferList().join(true, '1');
|
||||
},
|
||||
|
||||
bindComplete: function () {
|
||||
return new BufferList().join(true, '2')
|
||||
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')
|
||||
return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A');
|
||||
},
|
||||
|
||||
emptyQuery: function () {
|
||||
return new BufferList().join(true, 'I')
|
||||
return new BufferList().join(true, 'I');
|
||||
},
|
||||
|
||||
portalSuspended: function () {
|
||||
return new BufferList().join(true, 's')
|
||||
return new BufferList().join(true, 's');
|
||||
},
|
||||
|
||||
closeComplete: function () {
|
||||
return new BufferList().join(true, '3')
|
||||
return new BufferList().join(true, '3');
|
||||
},
|
||||
|
||||
copyIn: function (cols: number) {
|
||||
@ -156,7 +129,7 @@ const buffers = {
|
||||
for (let i = 0; i < cols; i++) {
|
||||
list.addInt16(i);
|
||||
}
|
||||
return list.join(true, 'G')
|
||||
return list.join(true, 'G');
|
||||
},
|
||||
|
||||
copyOut: function (cols: number) {
|
||||
@ -168,7 +141,7 @@ const buffers = {
|
||||
for (let i = 0; i < cols; i++) {
|
||||
list.addInt16(i);
|
||||
}
|
||||
return list.join(true, 'H')
|
||||
return list.join(true, 'H');
|
||||
},
|
||||
|
||||
copyData: function (bytes: Buffer) {
|
||||
@ -176,8 +149,8 @@ const buffers = {
|
||||
},
|
||||
|
||||
copyDone: function () {
|
||||
return new BufferList().join(true, 'c')
|
||||
}
|
||||
}
|
||||
return new BufferList().join(true, 'c');
|
||||
},
|
||||
};
|
||||
|
||||
export default buffers
|
||||
export default buffers;
|
||||
|
||||
2
packages/pg-protocol/src/types/chunky.d.ts
vendored
2
packages/pg-protocol/src/types/chunky.d.ts
vendored
@ -1 +1 @@
|
||||
declare module 'chunky'
|
||||
declare module 'chunky';
|
||||
|
||||
@ -7,7 +7,7 @@ params := $(connectionString)
|
||||
node-command := xargs -n 1 -I file node file $(params)
|
||||
|
||||
.PHONY : test test-connection test-integration bench test-native \
|
||||
lint publish test-missing-native update-npm
|
||||
publish test-missing-native update-npm
|
||||
|
||||
all:
|
||||
npm install
|
||||
@ -17,7 +17,7 @@ help:
|
||||
|
||||
test: test-unit
|
||||
|
||||
test-all: lint test-missing-native test-unit test-integration test-native
|
||||
test-all: test-missing-native test-unit test-integration test-native
|
||||
|
||||
|
||||
update-npm:
|
||||
@ -59,7 +59,3 @@ test-binary: test-connection
|
||||
|
||||
test-pool:
|
||||
@find test/integration/connection-pool -name "*.js" | $(node-command) binary
|
||||
|
||||
lint:
|
||||
@echo "***Starting lint***"
|
||||
node_modules/.bin/eslint lib
|
||||
|
||||
15
yarn.lock
15
yarn.lock
@ -836,6 +836,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.7.tgz#1c8c25cbf6e59ffa7d6b9652c78e547d9a41692d"
|
||||
integrity sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
@ -890,6 +895,16 @@
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^2.0.0"
|
||||
|
||||
"@typescript-eslint/parser@^2.27.0":
|
||||
version "2.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287"
|
||||
integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg==
|
||||
dependencies:
|
||||
"@types/eslint-visitor-keys" "^1.0.0"
|
||||
"@typescript-eslint/experimental-utils" "2.27.0"
|
||||
"@typescript-eslint/typescript-estree" "2.27.0"
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@2.27.0":
|
||||
version "2.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user