Permalink
Browse files

Test case for issue #12

  • Loading branch information...
1 parent 2f850c1 commit 8e65d58ddb3a7ad4aa591441a7a923132749d2b1 @cretz committed Mar 20, 2012
Showing with 231 additions and 28 deletions.
  1. +69 −15 lib/row.token.js
  2. +6 −3 lib/tds-constants.js
  3. +44 −10 src/row.token.coffee
  4. +3 −0 src/tds-constants.coffee
  5. +31 −0 test/node-tds.issue0012.test.coffee
  6. +78 −0 test/node-tds.vartext.test.coffee
View
84 lib/row.token.js
@@ -1,11 +1,13 @@
-var TdsUtils, Token,
+var TdsUtils, Token, _PLP_NULL,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
TdsUtils = require('./tds-utils').TdsUtils;
Token = require('./token').Token;
+_PLP_NULL = new Buffer([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
+
/**
Token for ROW (0xD1)
@@ -27,7 +29,7 @@ exports.RowToken = (function(_super) {
}
RowToken.prototype.fromBuffer = function(stream, context) {
- var column, index, val, _len, _ref, _results;
+ var chunk, chunkLength, chunks, column, index, len, pos, top, val, _i, _len, _len2, _ref, _results;
this._context = context;
this.metadata = context.colmetadata;
this.values = new Array(this.metadata.columns.length);
@@ -36,25 +38,74 @@ exports.RowToken = (function(_super) {
for (index = 0, _len = _ref.length; index < _len; index++) {
column = _ref[index];
val = {};
- switch (column.lengthType) {
- case 'int32LE':
- val.length = stream.readInt32LE();
- break;
- case 'uint16LE':
- val.length = stream.readUInt16LE();
- break;
- case 'uint8':
- val.length = stream.readByte();
- break;
- default:
- val.length = column.length;
+ context.debug('Checking column: ', column);
+ if (column.type.hasTextPointer) {
+ len = stream.readByte();
+ context.debug('Got len: ', len);
+ context.debug('Offset, length', stream.currentOffset(), stream.getBuffer().length);
+ if (len !== 0) {
+ stream.skip(len + 8);
+ } else {
+ val.length = -1;
+ }
+ context.debug('Val: ', val);
+ }
+ if (column.length !== 0xFFFF && val.length !== -1) {
+ switch (column.lengthType) {
+ case 'int32LE':
+ val.length = stream.readInt32LE();
+ break;
+ case 'uint16LE':
+ val.length = stream.readUInt16LE();
+ break;
+ case 'uint8':
+ val.length = stream.readByte();
+ break;
+ default:
+ val.length = column.length;
+ }
+ } else if (val.length !== -1) {
+ switch (column.type.sqlType) {
+ case 'Char':
+ case 'VarChar':
+ case 'NChar':
+ case 'NVarChar':
+ case 'Binary':
+ case 'VarBinary':
+ top = stream.readBuffer(8);
+ if (top.equals(_PLP_NULL)) {
+ val.length = -1;
+ } else {
+ chunkLength = stream.readUInt32LE();
+ val.length = 0;
+ chunks = [];
+ while (chunkLength !== 0) {
+ val.length += chunkLength;
+ chunks.push(stream.readBuffer(chunkLength));
+ chunkLength = stream.readUInt32LE();
+ }
+ val.buffer = new Buffer(val.length);
+ pos = 0;
+ for (_i = 0, _len2 = chunks.length; _i < _len2; _i++) {
+ chunk = chunks[_i];
+ chunk.copy(val.buffer, pos, 0);
+ pos += chunk.length;
+ }
+ if (column.type.sqlType === 'NChar' || column.type.sqlType === 'NVarChar') {
+ val.length /= 2;
+ }
+ }
+ break;
+ default:
+ val.length = column.length;
+ }
}
- if (val.length === 0xFFFF) val.length = -1;
@tdarsan
tdarsan added a line comment Nov 22, 2012

Deletion of this line is causing issues with nullable varchar columns and is throwing index out of bounds exception. Eg:- a varchar(9) nullable column with null values in it will thow an exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
if (val.length === 0 && column.type.emptyPossible) {
val.buffer = new Buffer(0);
} else if (val.length > 0) {
val.buffer = stream.readBuffer(val.length);
}
+ context.debug('Got value: ', val);
_results.push(this.values[index] = val);
}
return _results;
@@ -117,6 +168,7 @@ exports.RowToken = (function(_super) {
break;
case 'Char':
case 'VarChar':
+ case 'Text':
if (val.length === -1) {
return null;
} else {
@@ -125,6 +177,7 @@ exports.RowToken = (function(_super) {
break;
case 'NChar':
case 'NVarChar':
+ case 'NText':
if (val.length === -1) {
return null;
} else {
@@ -133,6 +186,7 @@ exports.RowToken = (function(_super) {
break;
case 'Binary':
case 'VarBinary':
+ case 'Image':
if (col.length === -1) {
return null;
} else {
View
9 lib/tds-constants.js
@@ -61,15 +61,17 @@ exports.TdsConstants = (function() {
sqlType: 'Image',
lengthType: 'int32LE',
hasTableName: true,
- emptyPossible: true
+ emptyPossible: true,
+ hasTextPointer: true
},
0x23: {
name: 'TEXTTYPE',
sqlType: 'Text',
lengthType: 'int32LE',
hasCollation: true,
hasTableName: true,
- emptyPossible: true
+ emptyPossible: true,
+ hasTextPointer: true
},
0x24: {
name: 'GUIDTYPE',
@@ -200,7 +202,8 @@ exports.TdsConstants = (function() {
lengthType: 'int32LE',
hasCollation: true,
hasTableName: true,
- emptyPossible: true
+ emptyPossible: true,
+ hasTextPointer: true
},
0x68: {
name: 'BITNTYPE',
View
54 src/row.token.coffee
@@ -1,6 +1,8 @@
{TdsUtils} = require './tds-utils'
{Token} = require './token'
+_PLP_NULL = new Buffer [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
+
###*
Token for ROW (0xD1)
@@ -22,12 +24,43 @@ class exports.RowToken extends Token
@values = new Array(@metadata.columns.length)
for column, index in @metadata.columns
val = {}
- switch column.lengthType
- when 'int32LE' then val.length = stream.readInt32LE()
- when 'uint16LE' then val.length = stream.readUInt16LE()
- when 'uint8' then val.length = stream.readByte()
- else val.length = column.length
- if val.length is 0xFFFF then val.length = -1
@tdarsan
tdarsan added a line comment Nov 22, 2012

Deletion of this line is causing issues with nullable varchar columns and is throwing index out of bounds exception. Eg:- a varchar(9) nullable column with null values in it will thow an exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ # ignore pointer
+ if column.type.hasTextPointer
+ len = stream.readByte()
+ if len isnt 0 then stream.skip len + 8
+ else val.length = -1
+ context.debug 'Val: ', val
+ if column.length isnt 0xFFFF and val.length isnt -1
+ switch column.lengthType
+ when 'int32LE' then val.length = stream.readInt32LE()
+ when 'uint16LE' then val.length = stream.readUInt16LE()
+ when 'uint8' then val.length = stream.readByte()
+ else val.length = column.length
+ else if val.length isnt -1
+ # max length stuff
+ switch column.type.sqlType
+ when 'Char', 'VarChar', 'NChar', 'NVarChar', 'Binary', 'VarBinary'
+ # grab the entire buffer
+ top = stream.readBuffer 8
+ if top.equals _PLP_NULL then val.length = -1
+ else
+ # skip expected length validation for now
+ chunkLength = stream.readUInt32LE()
+ val.length = 0
+ chunks = []
+ while chunkLength isnt 0
+ val.length += chunkLength
+ chunks.push stream.readBuffer(chunkLength)
+ chunkLength = stream.readUInt32LE()
+ val.buffer = new Buffer val.length
+ pos = 0
+ for chunk in chunks
+ chunk.copy val.buffer, pos, 0
+ pos += chunk.length
+ # the length is half if it's unicode
+ if column.type.sqlType is 'NChar' or column.type.sqlType is 'NVarChar'
+ val.length /= 2
+ else val.length = column.length
if val.length is 0 and column.type.emptyPossible
val.buffer = new Buffer 0
else if val.length > 0
@@ -65,13 +98,13 @@ class exports.RowToken extends Token
if val.length is 0 then null
else TdsUtils.bigIntBufferToString val.buffer
# TODO RowVersion/TimeStamp
- when 'Char', 'VarChar'
+ when 'Char', 'VarChar', 'Text'
if val.length is -1 then null
else val.buffer.toString 'ascii', 0, val.length
- when 'NChar', 'NVarChar'
+ when 'NChar', 'NVarChar', 'NText'
if val.length is -1 then null
else val.buffer.toString 'ucs2', 0, val.length * 2
- when 'Binary', 'VarBinary'
+ when 'Binary', 'VarBinary', 'Image'
if col.length is -1 then null
else val.buffer
# TODO when 'SmallMoney'
@@ -153,4 +186,5 @@ class exports.RowToken extends Token
date
_readDate: (buffer) ->
- throw new Error 'Not implemented'
+ throw new Error 'Not implemented'
+
View
3 src/tds-constants.coffee
@@ -62,13 +62,15 @@ class exports.TdsConstants
lengthType: 'int32LE'
hasTableName: true
emptyPossible: true
+ hasTextPointer: true
0x23:
name: 'TEXTTYPE'
sqlType: 'Text'
lengthType: 'int32LE'
hasCollation: true
hasTableName: true
emptyPossible: true
+ hasTextPointer: true
0x24:
name: 'GUIDTYPE'
sqlType: 'UniqueIdentifier'
@@ -176,6 +178,7 @@ class exports.TdsConstants
hasCollation: true
hasTableName: true
emptyPossible: true
+ hasTextPointer: true
0x68:
name: 'BITNTYPE'
lengthSubstitutes:
View
31 test/node-tds.issue0012.test.coffee
@@ -0,0 +1,31 @@
+{TestConstants} = require './constants.test'
+{TestUtils} = require './utils.test'
+
+describe 'Statement', ->
+
+ describe '#execute', ->
+ conn = null
+ beforeEach ->
+ conn = TestUtils.newConnection()
+
+ afterEach ->
+ conn?.end()
+
+ it 'should handle varchar and nvarchar max properly', (alldone) ->
+ foundRow = false
+ handler =
+ error: (error) ->
+ alldone error
+ row: (row) ->
+ TestUtils.assertRow row, 'VarCharCol', 'Foo'
+ TestUtils.assertRow row, 'NVarCharCol', 'Bar'
+ foundRow = true
+ done: (done) ->
+ if foundRow
+ alldone()
+ else
+ alldone new Error('Did not find row')
+ conn.connect =>
+ # select a varchar and nvarchar max
+ stmt = conn.createStatement "SELECT CAST('Foo' AS VARCHAR(MAX)) AS VarCharCol, CAST('Bar' AS NVARCHAR(MAX)) AS NVarCharCol", null, handler
+ stmt.execute()
View
78 test/node-tds.vartext.test.coffee
@@ -0,0 +1,78 @@
+{TestConstants} = require './constants.test'
+{TestUtils} = require './utils.test'
+
+describe 'Statement', ->
+
+ describe '#execute', ->
+ conn = null
+ beforeEach ->
+ conn = TestUtils.newConnection()
+
+ afterEach ->
+ conn?.end()
+
+ it 'should handle text properly', (alldone) ->
+ rowFound = false
+ handler =
+ row: (row) ->
+ rowFound = true
+ TestUtils.assertRow row, 0, 'Hello'
+ TestUtils.assertRow row, 1, ''
+ TestUtils.assertRow row, 2, null
+ done: (done) ->
+ if rowFound then alldone()
+ else alldone new Error('No row found')
+ conn.handler = handler
+ sql =
+ '''
+ SELECT CAST('Hello' AS Text) AS TextType1,
+ CAST('' AS Text) AS TextType2,
+ CAST(NULL AS Text) AS TextType3
+ '''
+ conn.connect =>
+ stmt = conn.createStatement sql, null, handler
+ stmt.execute()
+
+ it 'should handle ntext properly', (alldone) ->
+ rowFound = false
+ handler =
+ row: (row) ->
+ rowFound = true
+ TestUtils.assertRow row, 0, 'Hello'
+ TestUtils.assertRow row, 1, ''
+ TestUtils.assertRow row, 2, null
+ done: (done) ->
+ if rowFound then alldone()
+ else alldone new Error('No row found')
+ conn.handler = handler
+ sql =
+ '''
+ SELECT CAST('Hello' AS NText) AS NTextType1,
+ CAST('' AS NText) AS NTextType2,
+ CAST(NULL AS NText) AS NTextType3
+ '''
+ conn.connect =>
+ stmt = conn.createStatement sql, null, handler
+ stmt.execute()
+
+ it 'should handle image properly', (alldone) ->
+ rowFound = false
+ handler =
+ row: (row) ->
+ rowFound = true
+ TestUtils.assertRow row, 0, new Buffer [0x01, 0x02, 0x03]
+ TestUtils.assertRow row, 1, new Buffer 0
+ TestUtils.assertRow row, 2, null
+ done: (done) ->
+ if rowFound then alldone()
+ else alldone new Error('No row found')
+ conn.handler = handler
+ sql =
+ '''
+ SELECT CAST(0x010203 AS Image) AS ImageType1,
+ CAST('' AS Image) AS ImageType2,
+ CAST(NULL AS Image) AS ImageType3
+ '''
+ conn.connect =>
+ stmt = conn.createStatement sql, null, handler
+ stmt.execute()

0 comments on commit 8e65d58

Please sign in to comment.