diff --git a/src/bulk-load.ts b/src/bulk-load.ts index b383d416d..86f0de0b4 100644 --- a/src/bulk-load.ts +++ b/src/bulk-load.ts @@ -194,6 +194,7 @@ class RowTransform extends Transform { value: value }; + if (c.type.name === 'Text' || c.type.name === 'Image' || c.type.name === 'NText') { if (value == null) { this.push(textPointerNullBuffer); continue; diff --git a/src/data-types/ntext.ts b/src/data-types/ntext.ts index 6f6b441aa..aac180230 100644 --- a/src/data-types/ntext.ts +++ b/src/data-types/ntext.ts @@ -1,5 +1,7 @@ import { DataType } from '../data-type'; +const NULL_LENGTH = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]); + const NText: DataType = { id: 0x63, type: 'NTEXT', @@ -7,24 +9,59 @@ const NText: DataType = { hasTableName: true, - declaration() { - throw new Error('not implemented'); + declaration: function() { + return 'ntext'; + }, + + resolveLength: function(parameter) { + const value = parameter.value as any; // Temporary solution. Remove 'any' later. + + if (value != null) { + return value.length; + } else { + return -1; + } }, - generateTypeInfo() { - throw new Error('not implemented'); + generateTypeInfo(parameter, _options) { + const buffer = Buffer.alloc(10); + buffer.writeUInt8(this.id, 0); + buffer.writeInt32LE(parameter.length!, 1); + // TODO: Collation handling + return buffer; }, - generateParameterLength() { - throw new Error('not implemented'); + generateParameterLength(parameter, options) { + if (parameter.value == null) { + return NULL_LENGTH; + } + + const buffer = Buffer.alloc(4); + buffer.writeInt32LE(Buffer.byteLength(parameter.value, 'ucs2'), 0); + return buffer; }, - generateParameterData() { - throw new Error('not implemented'); + generateParameterData: function*(parameter, options) { + if (parameter.value == null) { + return; + } + + yield Buffer.from(parameter.value.toString(), 'ucs2'); }, - validate() { - throw new Error('not implemented'); + validate: function(value): string | null { + if (value == null) { + return null; + } + + if (typeof value !== 'string') { + if (typeof value.toString !== 'function') { + throw new TypeError('Invalid string.'); + } + value = value.toString(); + } + + return value; } }; diff --git a/test/integration/bulk-load-test.js b/test/integration/bulk-load-test.js index 6ac9429f6..7a4d54f81 100644 --- a/test/integration/bulk-load-test.js +++ b/test/integration/bulk-load-test.js @@ -1588,6 +1588,57 @@ describe('BulkLoad', function() { connection.execSqlBatch(request); }); + it('supports bulk loading into a `ntext` column', function(done) { + const expectedRows = [ + { value: 'some text 中文' }, + { value: null } + ]; + + const bulkLoad = connection.newBulkLoad('#tmpTestTable', (err, rowCount) => { + if (err) { + done(err); + } + + assert.strictEqual(rowCount, expectedRows.length); + + /** @type {unknown[]} */ + const results = []; + const request = new Request(` + SELECT value FROM #tmpTestTable + `, (err) => { + if (err) { + done(err); + } + + assert.deepEqual(results, expectedRows); + + done(); + }); + + request.on('row', (row) => { + results.push({ value: row[0].value }); + }); + + connection.execSql(request); + }); + + bulkLoad.addColumn('value', TYPES.NText, { nullable: true }); + + const request = new Request(` + CREATE TABLE "#tmpTestTable" ( + [value] ntext NULL + ) + `, (err) => { + if (err) { + return done(err); + } + + connection.execBulkLoad(bulkLoad, Readable.from(expectedRows)); + }); + + connection.execSqlBatch(request); + }); + it('supports bulk loading into a `image` column', function(done) { const expectedRows = [ { value: Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]) }, diff --git a/test/integration/parameterised-statements-test.js b/test/integration/parameterised-statements-test.js index bcff1fcf5..f1a478578 100644 --- a/test/integration/parameterised-statements-test.js +++ b/test/integration/parameterised-statements-test.js @@ -428,6 +428,28 @@ describe('Parameterised Statements Test', function() { execSql(done, TYPES.NChar, null); }); + describe('`ntext`', function() { + it('should handle `null` values', function(done) { + execSql(done, TYPES.NText, null); + }); + + it('should handle empty strings', function(done) { + execSql(done, TYPES.NText, ''); + }); + + it('should handle short strings', function(done) { + execSql(done, TYPES.NText, 'small'); + }); + + it('should handle large strings', function(done) { + execSql(done, TYPES.NText, new Array(500000).join('x')); + }); + + it('should handle multibyte characters', function(done) { + execSql(done, TYPES.NText, 'some text 中文'); + }); + }); + it('should test textNull', function(done) { execSql(done, TYPES.Text, null); });