Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove callback nesting from parsers #1477

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
400 changes: 146 additions & 254 deletions src/metadata-parser.ts

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions src/token/buffer-reader.ts
@@ -0,0 +1,84 @@
import Parser from './stream-parser';

class NotEnoughDataError extends Error { }


export default class BufferReader {
private offset: number;
parser: Parser;

constructor(parser: Parser) {
this.offset = parser.position;
this.parser = parser;
}

private checkDataLength(buffer: Buffer, numBytes: number): void {
if (buffer.length < this.parser.position + numBytes) {
this.parser.position = this.offset;
throw new NotEnoughDataError();
}
}

readUInt8(): number {
const numBytes = 1;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt8(this.parser.position);
this.parser.position += numBytes;
return data;
}

readUInt16LE(): number {
const numBytes = 2;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt16LE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readUInt32LE(): number {
const numBytes = 4;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt32LE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readUInt32BE(): number {
const numBytes = 4;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt32BE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readInt32LE(): number {
const numBytes = 4;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readInt32LE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readBVarChar(): string {
const numBytes = this.readUInt8() * 2;
const data = this.readFromBuffer(numBytes).toString('ucs2');
return data;
}


readUsVarChar(): string {
const numBytes = this.readUInt16LE() * 2;
const data = this.readFromBuffer(numBytes).toString('ucs2');
return data;
}

readFromBuffer(numBytes: number): Buffer {
this.checkDataLength(this.parser.buffer, numBytes);
const result = this.parser.buffer.slice(this.parser.position, this.parser.position + numBytes);
this.parser.position += numBytes;
return result;
}

}

module.exports = BufferReader;
42 changes: 26 additions & 16 deletions src/token/colmetadata-token-parser.ts
Expand Up @@ -12,6 +12,8 @@ export interface ColumnMetadata extends Metadata {
tableName?: string | string[] | undefined;
}

class NotEnoughDataError extends Error { }

function readTableName(parser: Parser, options: ParserOptions, metadata: Metadata, callback: (tableName?: string | string[]) => void) {
if (metadata.type.hasTableName) {
if (options.tdsVersion >= '7_2') {
Expand Down Expand Up @@ -60,22 +62,30 @@ function readColumnName(parser: Parser, options: ParserOptions, index: number, m
}

function readColumn(parser: Parser, options: ParserOptions, index: number, callback: (column: ColumnMetadata) => void) {
metadataParse(parser, options, (metadata) => {
readTableName(parser, options, metadata, (tableName) => {
readColumnName(parser, options, index, metadata, (colName) => {
callback({
userType: metadata.userType,
flags: metadata.flags,
type: metadata.type,
collation: metadata.collation,
precision: metadata.precision,
scale: metadata.scale,
udtInfo: metadata.udtInfo,
dataLength: metadata.dataLength,
schema: metadata.schema,
colName: colName,
tableName: tableName
});
let metadata!: Metadata;
try {
metadata = metadataParse(parser, options);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
readColumn(parser, options, index, callback);
});
}
}
readTableName(parser, options, metadata, (tableName) => {
readColumnName(parser, options, index, metadata, (colName) => {
callback({
userType: metadata.userType,
flags: metadata.flags,
type: metadata.type,
collation: metadata.collation,
precision: metadata.precision,
scale: metadata.scale,
udtInfo: metadata.udtInfo,
dataLength: metadata.dataLength,
schema: metadata.schema,
colName: colName,
tableName: tableName
});
});
});
Expand Down
79 changes: 46 additions & 33 deletions src/token/infoerror-token-parser.ts
@@ -1,7 +1,9 @@
import Parser, { ParserOptions } from './stream-parser';

import BufferReader from './buffer-reader';
import { InfoMessageToken, ErrorMessageToken } from './token';

class NotEnoughDataError extends Error { }

interface TokenData {
number: number;
state: number;
Expand All @@ -12,43 +14,54 @@ interface TokenData {
lineNumber: number;
}

function parseToken(parser: Parser, options: ParserOptions, callback: (data: TokenData) => void) {
function parseToken(parser: Parser, options: ParserOptions): TokenData {
// length
parser.readUInt16LE(() => {
parser.readUInt32LE((number) => {
parser.readUInt8((state) => {
parser.readUInt8((clazz) => {
parser.readUsVarChar((message) => {
parser.readBVarChar((serverName) => {
parser.readBVarChar((procName) => {
(options.tdsVersion < '7_2' ? parser.readUInt16LE : parser.readUInt32LE).call(parser, (lineNumber: number) => {
callback({
'number': number,
'state': state,
'class': clazz,
'message': message,
'serverName': serverName,
'procName': procName,
'lineNumber': lineNumber
});
});
});
});
});
});
});
});
});
const br = new BufferReader(parser);
br.readUInt16LE();
const number = br.readUInt32LE();
const state = br.readUInt8();
const clazz = br.readUInt8();
const message = br.readUsVarChar();
const serverName = br.readBVarChar();
const procName = br.readBVarChar();
const lineNumber = options.tdsVersion < '7_2' ? br.readUInt16LE() : br.readUInt32LE();
return {
'number': number,
'state': state,
'class': clazz,
'message': message,
'serverName': serverName,
'procName': procName,
'lineNumber': lineNumber
} as TokenData;
}

export function infoParser(parser: Parser, options: ParserOptions, callback: (token: InfoMessageToken) => void) {
parseToken(parser, options, (data) => {
callback(new InfoMessageToken(data));
});
let data!: TokenData;
try {
data = parseToken(parser, options);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
infoParser(parser, options, callback);
});
}
}

callback(new InfoMessageToken(data));
}

export function errorParser(parser: Parser, options: ParserOptions, callback: (token: ErrorMessageToken) => void) {
parseToken(parser, options, (data) => {
callback(new ErrorMessageToken(data));
});
let data!: TokenData;
try {
data = parseToken(parser, options);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
errorParser(parser, options, callback);
});
}
}

callback(new ErrorMessageToken(data));
}
69 changes: 40 additions & 29 deletions src/token/loginack-token-parser.ts
Expand Up @@ -3,43 +3,54 @@ import Parser, { ParserOptions } from './stream-parser';
import { LoginAckToken } from './token';

import { versionsByValue as versions } from '../tds-versions';
import BufferReader from './buffer-reader';

class NotEnoughDataError extends Error { }

const interfaceTypes: { [key: number]: string } = {
0: 'SQL_DFLT',
1: 'SQL_TSQL'
};

function parseToken(parser: Parser): LoginAckToken {
const br = new BufferReader(parser);
br.readUInt16LE();
const interfaceNumber = br.readUInt8();
const interfaceType = interfaceTypes[interfaceNumber];
const tdsVersionNumber = br.readUInt32BE();
const tdsVersion = versions[tdsVersionNumber];
const progName = br.readBVarChar();
const major = br.readUInt8();
const minor = br.readUInt8();
const buildNumHi = br.readUInt8();
const buildNumLow = br.readUInt8();

return new LoginAckToken({
interface: interfaceType,
tdsVersion: tdsVersion,
progName: progName,
progVersion: {
major: major,
minor: minor,
buildNumHi: buildNumHi,
buildNumLow: buildNumLow
}
});
}

function loginAckParser(parser: Parser, _options: ParserOptions, callback: (token: LoginAckToken) => void) {
// length
parser.readUInt16LE(() => {
parser.readUInt8((interfaceNumber) => {
const interfaceType = interfaceTypes[interfaceNumber];
parser.readUInt32BE((tdsVersionNumber) => {
const tdsVersion = versions[tdsVersionNumber];
parser.readBVarChar((progName) => {
parser.readUInt8((major) => {
parser.readUInt8((minor) => {
parser.readUInt8((buildNumHi) => {
parser.readUInt8((buildNumLow) => {
callback(new LoginAckToken({
interface: interfaceType,
tdsVersion: tdsVersion,
progName: progName,
progVersion: {
major: major,
minor: minor,
buildNumHi: buildNumHi,
buildNumLow: buildNumLow
}
}));
});
});
});
});
});
let data!: LoginAckToken;
try {
data = parseToken(parser);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
loginAckParser(parser, _options, callback);
});
});
});
}
}

callback(data);
}

export default loginAckParser;
Expand Down
42 changes: 24 additions & 18 deletions src/token/order-token-parser.ts
@@ -1,32 +1,38 @@
// s2.2.7.14
import BufferReader from './buffer-reader';
import Parser, { ParserOptions } from './stream-parser';

import { OrderToken } from './token';

function orderParser(parser: Parser, _options: ParserOptions, callback: (token: OrderToken) => void) {
parser.readUInt16LE((length) => {
const columnCount = length / 2;
const orderColumns: number[] = [];
class NotEnoughDataError extends Error { }

function parseToken(parser: Parser): OrderToken {
const br = new BufferReader(parser);

let i = 0;
function next(done: () => void) {
if (i === columnCount) {
return done();
}
const length = br.readUInt16LE();
const columnCount = length / 2;
const orderColumns: number[] = [];

parser.readUInt16LE((column) => {
orderColumns.push(column);
for (let i = 0; i < columnCount; i++) {
const column = br.readUInt16LE();
orderColumns.push(column);
}

i++;
return new OrderToken(orderColumns);
}

next(done);
function orderParser(parser: Parser, _options: ParserOptions, callback: (token: OrderToken) => void) {
let data!: OrderToken;
try {
data = parseToken(parser);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
orderParser(parser, _options, callback);
});
}

next(() => {
callback(new OrderToken(orderColumns));
});
});
}
callback(data);
}

export default orderParser;
Expand Down