diff --git a/src/tokens/colmetadata/index.js b/src/tokens/colmetadata/index.js index fc0b88d..0705bfb 100644 --- a/src/tokens/colmetadata/index.js +++ b/src/tokens/colmetadata/index.js @@ -9,18 +9,19 @@ class ColumnData { _tableNameParts: number tableName: Array - nullable: boolean - caseSensitive: boolean - updateable: ?boolean - identity: boolean - - computed: ?boolean - sparseColumnSet: ?boolean - encrypted: ?boolean - fixedLenCLRType: ?boolean - hidden: ?boolean - key: ?boolean - nullableUnknown: ?boolean + flags: { + nullable: boolean, + caseSensitive: boolean, + updateable: ?string, + identity: boolean, + computed: ?boolean, + fixedLenCLRType: ?boolean, + sparseColumnSet: ?boolean, + encrypted: ?boolean, + hidden: ?boolean, + key: ?boolean, + nullableUnknown: ?boolean + } typeInfo: ?TypeInfo @@ -30,19 +31,19 @@ class ColumnData { this.tableName = []; this._tableNameParts = 0; - this.nullable = false; - this.caseSensitive = false; - this.updateable = undefined; - this.identity = false; - - this.computed = undefined; - this.sparseColumnSet = undefined; - this.encrypted = undefined; - this.fixedLenCLRType = undefined; - this.hidden = undefined; - this.key = undefined; - this.nullableUnknown = undefined; - + this.flags = { + nullable: false, + caseSensitive: false, + updateable: undefined, + identity: false, + computed: undefined, + fixedLenCLRType: undefined, + sparseColumnSet: undefined, + encrypted: undefined, + hidden: undefined, + key: undefined, + nullableUnknown: undefined + }; this.typeInfo = undefined; } } diff --git a/src/tokens/colmetadata/read.js b/src/tokens/colmetadata/read.js index 0e4d707..1f5d992 100644 --- a/src/tokens/colmetadata/read.js +++ b/src/tokens/colmetadata/read.js @@ -72,47 +72,33 @@ function parseUserType_7_2(reader: Reader) { } function parseFlags(reader: Reader) { - if (reader.version < 0x72090002) { - return parseFlags_7_0; - } else if (reader.version < 0x74000004) { - return parseFlags_7_2; - } else { - return parseFlags_7_4; - } -} - -function parseFlags_7_0(reader: Reader) { if (!reader.bytesAvailable(2)) { return; } - // TODO: Implement flag parsing const flags = reader.readUInt16LE(0); // eslint-disable-line no-unused-vars reader.consumeBytes(2); - return parseTypeInfo; -} + const column: ColumnData = reader.stash[reader.stash.length - 1]; + const flagParser = new FlagParser(flags); + column.flags.nullable = flagParser.nullable(); + column.flags.caseSensitive = flagParser.caseSensitive(); + column.flags.updateable = flagParser.updateable(); + column.flags.identity = flagParser.identity(); -function parseFlags_7_2(reader: Reader) { - if (!reader.bytesAvailable(2)) { - return; + if (reader.version >= 0x74000004) { + column.flags.encrypted = flagParser.encrypted(); } - - // TODO: Implement flag parsing - const flags = reader.readUInt16LE(0); // eslint-disable-line no-unused-vars - reader.consumeBytes(2); - - return parseTypeInfo; -} - -function parseFlags_7_4(reader: Reader) { - if (!reader.bytesAvailable(2)) { - return; + if (reader.version >= 0x730B0003) { + column.flags.sparseColumnSet = flagParser.sparseColumnSet(); + } + if (reader.version >= 0x72090002) { + column.flags.computed = flagParser.computed(); + column.flags.fixedLenCLRType = flagParser.fixedLenCLRType(); + column.flags.hidden = flagParser.hidden(); + column.flags.key = flagParser.key(); + column.flags.nullableUnknown = flagParser.nullableUnknown(); } - - // TODO: Implement flag parsing - const flags = reader.readUInt16LE(0); // eslint-disable-line no-unused-vars - reader.consumeBytes(2); return parseTypeInfo; } @@ -214,3 +200,4 @@ module.exports = readColmetadataToken; const ColmetadataToken = require('.'); const ColumnData = ColmetadataToken.ColumnData; const { readTypeInfo } = require('../../types'); +const { FlagParser } = require('../parserUtil'); diff --git a/src/tokens/parserUtil.js b/src/tokens/parserUtil.js new file mode 100644 index 0000000..334dea9 --- /dev/null +++ b/src/tokens/parserUtil.js @@ -0,0 +1,64 @@ +/* @flow */ + +// FlagParser can be used for TVP_COLMETADATA, ALTMETADATA, COLMETADATA, RETURNVALUE parsing +class FlagParser { + flags: number + + constructor(flags: number) { + this.flags = flags; + } + + nullable() { + return 0x0001 == (this.flags & 0x0001); + } + + caseSensitive() { + return 0x0002 == (this.flags & 0x0002); + } + + updateable() { + const value = (this.flags >> 2) & 0x0003; + switch (value) { + case 0: + return 'READ-ONLY'; + case 1: + return 'READ-WRITE'; + case 2: + return 'UNKNOWN'; + } + } + + identity() { + return 0x0010 == (this.flags & 0x0010); + } + + computed() { + return 0x0020 == (this.flags & 0x0020); + } + + fixedLenCLRType() { + return 0x0100 == (this.flags & 0x0100); + } + + sparseColumnSet() { + return 0x0400 == (this.flags & 0x0400); + } + + encrypted() { + return 0x0800 == (this.flags & 0x0800); + } + + hidden() { + return 0x2000 == (this.flags & 0x2000); + } + + key() { + return 0x4000 == (this.flags & 0x4000); + } + + nullableUnknown() { + return 0x8000 == (this.flags & 0x8000); + } +} + +module.exports.FlagParser = FlagParser; diff --git a/test/colmetadata-test.js b/test/colmetadata-test.js index 1ed1fbf..0241522 100644 --- a/test/colmetadata-test.js +++ b/test/colmetadata-test.js @@ -2,6 +2,7 @@ const assert = require('chai').assert; const Reader = require('../src').Reader; +const ColmetadataToken = require('../src/tokens/colmetadata'); describe('Parsing a COLMETADATA token', function() { describe('in TDS 7.0 mode', function() { @@ -20,7 +21,7 @@ describe('Parsing a COLMETADATA token', function() { data.writeUInt16LE(2, 3); // Flags - data.writeUInt16LE(3, 5); + data.writeUInt16LE(7, 5); // Type data.writeUInt8(0x30, 7); @@ -57,6 +58,29 @@ describe('Parsing a COLMETADATA token', function() { reader.end(data); }); + + + it('should parse flags correctly', function(done) { + let token = {}; + + reader.on('data', function(colToken) { + assert.instanceOf(colToken, ColmetadataToken); + token = colToken; + }); + + reader.on('end', function() { + const column = token.columns[0]; + const flags = column.flags; + assert.isTrue(flags.nullable); + assert.isTrue(flags.caseSensitive); + assert.strictEqual('READ-WRITE', flags.updateable); + assert.isNotTrue(flags.identity); + + done(); + }); + + reader.end(data); + }); }); describe('in TDS 7.1 mode', function() { @@ -130,7 +154,7 @@ describe('Parsing a COLMETADATA token', function() { data.writeUInt32LE(2, 3); // Flags - data.writeUInt16LE(3, 7); + data.writeUInt16LE(0xC139, 7); // Type data.writeUInt8(0x30, 9); @@ -167,5 +191,91 @@ describe('Parsing a COLMETADATA token', function() { reader.end(data); }); + + it('should parse flags correctly', function(done) { + let token = {}; + + reader.on('data', function(colToken) { + assert.instanceOf(colToken, ColmetadataToken); + token = colToken; + }); + + reader.on('end', function() { + const column = token.columns[0]; + const flags = column.flags; + assert.isTrue(flags.nullable); + assert.isNotTrue(flags.caseSensitive); + assert.strictEqual('UNKNOWN', flags.updateable); + assert.isTrue(flags.identity); + assert.isTrue(flags.computed); + assert.isTrue(flags.fixedLenCLRType); + assert.isNotTrue(flags.hidden); + assert.isTrue(flags.key); + assert.isTrue(flags.nullableUnknown); + + done(); + }); + + reader.end(data); + }); + }); + + describe('in TDS 7.4 mode', function() { + let reader, data; + + beforeEach(function() { + reader = new Reader(0x74000004); + + data = Buffer.alloc(19); + data.writeUInt8(0x81, 0); + + // Number of columns + data.writeUInt16LE(1, 1); + + // UserType + data.writeUInt32LE(2, 3); + + // Flags + data.writeUInt16LE(0xAC02, 7); + + // Type + data.writeUInt8(0x30, 9); + + // ColName + data.writeUInt8(4, 10); + data.write('test', 11, 8, 'ucs2'); + }); + + it('should parse flags correctly', function(done) { + let token = {}; + + reader.on('data', function(colToken) { + assert.instanceOf(colToken, ColmetadataToken); + token = colToken; + }); + + reader.on('end', function() { + const column = token.columns[0]; + const flags = column.flags; + assert.isNotTrue(flags.nullable); + assert.isTrue(flags.caseSensitive); + assert.strictEqual('READ-ONLY', flags.updateable); + assert.isNotTrue(flags.identity); + assert.isNotTrue(flags.computed); + assert.isNotTrue(flags.fixedLenCLRType); + assert.isTrue(flags.sparseColumnSet); + assert.isTrue(flags.encrypted); + assert.isTrue(flags.hidden); + assert.isNotTrue(flags.key); + assert.isTrue(flags.nullableUnknown); + + done(); + }); + + reader.end(data); + }); + + }); + });