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

feat: improve performance of response data parsing #1580

Merged
merged 30 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bacd841
WIP: rewrite parser
arthurschreiber Sep 28, 2023
5413802
chore: fix CI failures
MichaelSun90 Oct 10, 2023
3eb8c53
Merge branch 'master' into arthur/rewrite-parser
MichaelSun90 Oct 10, 2023
460f1b4
change fedauth length read
mShan0 Oct 10, 2023
006116d
chore: fix test failures for connection-retry
MichaelSun90 Oct 11, 2023
abe78ac
chore: fix lint
MichaelSun90 Oct 11, 2023
df2a6fa
chore: fix the test case for connection retry
MichaelSun90 Oct 11, 2023
e180c39
lint fixes
mShan0 Oct 11, 2023
ce5bfb6
convert nbc row token parser
mShan0 Oct 11, 2023
a88407d
more lint fixes
mShan0 Oct 11, 2023
918bd90
result to class
mShan0 Oct 11, 2023
da61854
chore: remove bufferlist as accepted type for buf
MichaelSun90 Oct 11, 2023
e62b80d
convert value-parser result to class
mShan0 Oct 11, 2023
fb440aa
convert result type to class
mShan0 Oct 11, 2023
192fb8f
Update datatypes-in-results-test.ts
mShan0 Oct 11, 2023
de40b13
chore: clean up value parser
MichaelSun90 Oct 11, 2023
1032f07
chore: remove a console.log
MichaelSun90 Oct 12, 2023
68627c8
chore: fix ci failures
MichaelSun90 Oct 12, 2023
1cb091f
removed wrong length row token parser test
mShan0 Oct 12, 2023
bd9b11a
Merge branch 'arthur/rewrite-parser' of https://github.com/tediousjs/…
MichaelSun90 Oct 12, 2023
b3363d9
chore: fix CI failure for money types
MichaelSun90 Oct 12, 2023
031abc6
chore: fix lint
MichaelSun90 Oct 12, 2023
03de398
tds version fix colmetadata
mShan0 Oct 13, 2023
25a387b
chore: revision based on comments
MichaelSun90 Oct 16, 2023
91f1401
chore: add read PLP with known length
MichaelSun90 Oct 17, 2023
14c6c49
chore: modified based on comment
MichaelSun90 Oct 17, 2023
f057795
chore: add the expectedLength back
MichaelSun90 Oct 17, 2023
4dd82bd
chore: fix based on comment
MichaelSun90 Oct 18, 2023
0435a78
chore: fix based on comment
MichaelSun90 Oct 18, 2023
fd4d03a
fix lint failure.
arthurschreiber Oct 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
565 changes: 312 additions & 253 deletions src/metadata-parser.ts

Large diffs are not rendered by default.

164 changes: 88 additions & 76 deletions src/token/colmetadata-token-parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import metadataParse, { type Metadata } from '../metadata-parser';
import { readMetadata, type Metadata } from '../metadata-parser';

import Parser, { type ParserOptions } from './stream-parser';
import { ColMetadataToken } from './token';
import { NotEnoughDataError, Result, readBVarChar, readUInt16LE, readUInt8, readUsVarChar } from './helpers';

export interface ColumnMetadata extends Metadata {
/**
Expand All @@ -12,101 +13,112 @@
tableName?: string | string[] | undefined;
}

function readTableName(parser: Parser, options: ParserOptions, metadata: Metadata, callback: (tableName?: string | string[]) => void) {
if (metadata.type.hasTableName) {
if (options.tdsVersion >= '7_2') {
parser.readUInt8((numberOfTableNameParts) => {
const tableName: string[] = [];
function readTableName(buf: Buffer, offset: number, metadata: Metadata, options: ParserOptions): Result<string | string[] | undefined> {
if (!metadata.type.hasTableName) {
return new Result(undefined, offset);
}

let i = 0;
function next(done: () => void) {
if (numberOfTableNameParts === i) {
return done();
}
if (options.tdsVersion < '7_2') {
return readUsVarChar(buf, offset);
}

parser.readUsVarChar((part) => {
tableName.push(part);
let numberOfTableNameParts;
({ offset, value: numberOfTableNameParts } = readUInt8(buf, offset));

i++;
const tableName: string[] = [];
for (let i = 0; i < numberOfTableNameParts; i++) {
let tableNamePart;
({ offset, value: tableNamePart } = readUsVarChar(buf, offset));

next(done);
});
}

next(() => {
callback(tableName);
});
});
} else {
parser.readUsVarChar(callback);
}
} else {
callback(undefined);
tableName.push(tableNamePart);
}

return new Result(tableName, offset);
}

function readColumnName(parser: Parser, options: ParserOptions, index: number, metadata: Metadata, callback: (colName: string) => void) {
parser.readBVarChar((colName) => {
if (options.columnNameReplacer) {
callback(options.columnNameReplacer(colName, index, metadata));
} else if (options.camelCaseColumns) {
callback(colName.replace(/^[A-Z]/, function(s) {
return s.toLowerCase();
}));
} else {
callback(colName);
}
});
function readColumnName(buf: Buffer, offset: number, index: number, metadata: Metadata, options: ParserOptions): Result<string> {
let colName;
({ offset, value: colName } = readBVarChar(buf, offset));

if (options.columnNameReplacer) {
return new Result(options.columnNameReplacer(colName, index, metadata), offset);

Check warning on line 44 in src/token/colmetadata-token-parser.ts

View check run for this annotation

Codecov / codecov/patch

src/token/colmetadata-token-parser.ts#L44

Added line #L44 was not covered by tests
} else if (options.camelCaseColumns) {
return new Result(colName.replace(/^[A-Z]/, function(s) {
return s.toLowerCase();

Check warning on line 47 in src/token/colmetadata-token-parser.ts

View check run for this annotation

Codecov / codecov/patch

src/token/colmetadata-token-parser.ts#L46-L47

Added lines #L46 - L47 were not covered by tests
}), offset);
} else {
return new Result(colName, offset);
}
}

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
});
});
});
});
function readColumn(buf: Buffer, offset: number, options: ParserOptions, index: number) {
let metadata;
({ offset, value: metadata } = readMetadata(buf, offset, options));

let tableName;
({ offset, value: tableName } = readTableName(buf, offset, metadata, options));

let colName;
({ offset, value: colName } = readColumnName(buf, offset, index, metadata, options));

return new Result({
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
}, offset);
}

async function colMetadataParser(parser: Parser): Promise<ColMetadataToken> {
while (parser.buffer.length - parser.position < 2) {
await parser.streamBuffer.waitForChunk();
}
let columnCount;

while (true) {
let offset;

try {
({ offset, value: columnCount } = readUInt16LE(parser.buffer, parser.position));
} catch (err) {
if (err instanceof NotEnoughDataError) {
await parser.waitForChunk();
continue;

Check warning on line 90 in src/token/colmetadata-token-parser.ts

View check run for this annotation

Codecov / codecov/patch

src/token/colmetadata-token-parser.ts#L89-L90

Added lines #L89 - L90 were not covered by tests
}

throw err;

Check warning on line 93 in src/token/colmetadata-token-parser.ts

View check run for this annotation

Codecov / codecov/patch

src/token/colmetadata-token-parser.ts#L93

Added line #L93 was not covered by tests
}

const columnCount = parser.buffer.readUInt16LE(parser.position);
parser.position += 2;
parser.position = offset;
break;
}

const columns: ColumnMetadata[] = [];
for (let i = 0; i < columnCount; i++) {
let column: ColumnMetadata;

readColumn(parser, parser.options, i, (c) => {
column = c;
});
while (true) {
let column: ColumnMetadata;
let offset;

try {
({ offset, value: column } = readColumn(parser.buffer, parser.position, parser.options, i));
} catch (err: any) {
if (err instanceof NotEnoughDataError) {
await parser.waitForChunk();
continue;

Check warning on line 111 in src/token/colmetadata-token-parser.ts

View check run for this annotation

Codecov / codecov/patch

src/token/colmetadata-token-parser.ts#L110-L111

Added lines #L110 - L111 were not covered by tests
}

while (parser.suspended) {
await parser.streamBuffer.waitForChunk();
throw err;

Check warning on line 114 in src/token/colmetadata-token-parser.ts

View check run for this annotation

Codecov / codecov/patch

src/token/colmetadata-token-parser.ts#L114

Added line #L114 was not covered by tests
}

parser.suspended = false;
const next = parser.next!;
parser.position = offset;
columns.push(column);

next();
break;
}

columns.push(column!);
}

return new ColMetadataToken(columns);
Expand Down
76 changes: 36 additions & 40 deletions src/token/done-token-parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Parser, { type ParserOptions } from './stream-parser';
import { type ParserOptions } from './stream-parser';
import { DoneToken, DoneInProcToken, DoneProcToken } from './token';
import { Result, readBigUInt64LE, readUInt16LE, readUInt32LE } from './helpers';

// s2.2.7.5/6/7

Expand All @@ -22,51 +23,46 @@ interface TokenData {
curCmd: number;
}

function parseToken(parser: Parser, options: ParserOptions, callback: (data: TokenData) => void) {
parser.readUInt16LE((status) => {
const more = !!(status & STATUS.MORE);
const sqlError = !!(status & STATUS.ERROR);
const rowCountValid = !!(status & STATUS.COUNT);
const attention = !!(status & STATUS.ATTN);
const serverError = !!(status & STATUS.SRVERROR);
function readToken(buf: Buffer, offset: number, options: ParserOptions): Result<TokenData> {
let status;
({ offset, value: status } = readUInt16LE(buf, offset));

parser.readUInt16LE((curCmd) => {
const next = (rowCount: number) => {
callback({
more: more,
sqlError: sqlError,
attention: attention,
serverError: serverError,
rowCount: rowCountValid ? rowCount : undefined,
curCmd: curCmd
});
};
const more = !!(status & STATUS.MORE);
const sqlError = !!(status & STATUS.ERROR);
const rowCountValid = !!(status & STATUS.COUNT);
const attention = !!(status & STATUS.ATTN);
const serverError = !!(status & STATUS.SRVERROR);

if (options.tdsVersion < '7_2') {
parser.readUInt32LE(next);
} else {
parser.readBigUInt64LE((rowCount) => {
next(Number(rowCount));
});
}
});
});
let curCmd;
({ offset, value: curCmd } = readUInt16LE(buf, offset));

let rowCount;
({ offset, value: rowCount } = (options.tdsVersion < '7_2' ? readUInt32LE : readBigUInt64LE)(buf, offset));

return new Result({
more: more,
sqlError: sqlError,
attention: attention,
serverError: serverError,
rowCount: rowCountValid ? Number(rowCount) : undefined,
curCmd: curCmd
}, offset);
}

export function doneParser(parser: Parser, options: ParserOptions, callback: (token: DoneToken) => void) {
parseToken(parser, options, (data) => {
callback(new DoneToken(data));
});
export function doneParser(buf: Buffer, offset: number, options: ParserOptions): Result<DoneToken> {
let value;
({ offset, value } = readToken(buf, offset, options));
return new Result(new DoneToken(value), offset);
}

export function doneInProcParser(parser: Parser, options: ParserOptions, callback: (token: DoneInProcToken) => void) {
parseToken(parser, options, (data) => {
callback(new DoneInProcToken(data));
});
export function doneInProcParser(buf: Buffer, offset: number, options: ParserOptions): Result<DoneInProcToken> {
let value;
({ offset, value } = readToken(buf, offset, options));
return new Result(new DoneInProcToken(value), offset);
}

export function doneProcParser(parser: Parser, options: ParserOptions, callback: (token: DoneProcToken) => void) {
parseToken(parser, options, (data) => {
callback(new DoneProcToken(data));
});
export function doneProcParser(buf: Buffer, offset: number, options: ParserOptions): Result<DoneProcToken> {
let value;
({ offset, value } = readToken(buf, offset, options));
return new Result(new DoneProcToken(value), offset);
}
Loading
Loading