Skip to content

Commit fd9f7d6

Browse files
committed
Enhanced lob.getData() method to accept offset and amount arguments (Issue #1643)
1 parent 4baf7e1 commit fd9f7d6

File tree

8 files changed

+229
-15
lines changed

8 files changed

+229
-15
lines changed

doc/src/release_notes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ node-oracledb `v6.4.0 <https://github.com/oracle/node-oracledb/compare/v6.3.0...
1313
Common Changes
1414
++++++++++++++
1515

16+
#) Enhanced :meth:`~lob.getData` method to accept offset and amount arguments.
17+
See `Issue #1643 <https://github.com/oracle/node-oracledb/issues/1643>`__.
18+
1619
#) Add support for fetching BLOB columns which have "IS JSON FORMAT OSON"
1720
constraint enabled in the same way as columns of type JSON.
1821
In node-oracledb :ref:`Thick mode <enablingthick>` this requires

lib/lob.js

+20-4
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,28 @@ class Lob extends Duplex {
185185
//---------------------------------------------------------------------------
186186
// getData()
187187
//
188-
// Returns all of the data in the LOB as a single string or buffer.
188+
// Return a portion (or all) of the data in the LOB. Note that the amount
189+
// and offset are in bytes for BLOB and BFILE type LOBs and in UCS - 2 code
190+
// points for CLOB and NCLOB type LOBs.UCS-2 code points are equivalent
191+
// to characters for all but supplemental characters.If supplemental
192+
// characters are in the LOB, the offset and amount will have to be chosen
193+
// carefully to avoid splitting a character.
194+
// Returns data in the LOB as a single string or buffer.
189195
//---------------------------------------------------------------------------
190-
async getData() {
191-
errors.assertArgCount(arguments, 0, 0);
196+
async getData(offset, amount) {
197+
errors.assertArgCount(arguments, 0, 2);
198+
if (offset === undefined) {
199+
offset = 1;
200+
} else {
201+
errors.assertParamValue(Number.isInteger(offset) && offset > 0, 1);
202+
}
203+
if (amount === undefined) {
204+
amount = 0;
205+
} else {
206+
errors.assertParamValue(Number.isInteger(amount) && amount > 0, 2);
207+
}
192208
errors.assert(this._impl, errors.ERR_INVALID_LOB);
193-
return await this._impl.getData();
209+
return await this._impl.getData(offset, amount);
194210
}
195211

196212
//---------------------------------------------------------------------------

lib/thin/lob.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ class ThinLobImpl extends LobImpl {
8989
return this.dbType;
9090
}
9191

92-
async getData() {
93-
if (this._length < 0) {
94-
errors.throwErr(errors.ERR_INVALID_LOB);
92+
async getData(offset = 1, len = this._length) {
93+
if (!len) {
94+
len = this._length;
9595
}
96-
return await this.read(1, this._length);
96+
return await this.read(offset, len);
9797
}
9898

9999
async read(offset, length) {

src/njsLob.c

+27-7
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,20 @@ NJS_NAPI_METHOD_IMPL_SYNC(njsLob_getType, 0, NULL)
188188

189189
//-----------------------------------------------------------------------------
190190
// njsLob_getData()
191-
// Read all of the data from the LOB and return it as a single string or
191+
// Read data from the LOB and return it as a single string or
192192
// buffer.
193193
//
194-
// PARAMETERS - NONE
194+
// PARAMETERS
195+
// - lobOffset
196+
// - lobAmount
197+
//
195198
//-----------------------------------------------------------------------------
196-
NJS_NAPI_METHOD_IMPL_ASYNC(njsLob_getData, 0, NULL)
199+
NJS_NAPI_METHOD_IMPL_ASYNC(njsLob_getData, 2, NULL)
197200
{
201+
NJS_CHECK_NAPI(env, napi_get_value_uint32(env, args[0],
202+
&baton->lobOffset))
203+
NJS_CHECK_NAPI(env, napi_get_value_uint32(env, args[1],
204+
&baton->lobAmount))
198205
return njsBaton_queueWork(baton, env, "GetData", njsLob_getDataAsync,
199206
njsLob_getDataPostAsync, returnValue);
200207
}
@@ -208,18 +215,31 @@ static bool njsLob_getDataAsync(njsBaton *baton)
208215
{
209216
njsLob *lob = (njsLob*) baton->callingInstance;
210217
bool ok = true;
218+
uint32_t len;
211219

212220
// if the length is marked dirty, acquire it at this time
213221
if (lob->dirtyLength) {
214222
if (dpiLob_getSize(lob->handle, &lob->length) < 0)
215223
return njsBaton_setErrorDPI(baton);
216224
lob->dirtyLength = false;
217225
}
226+
len = lob->length;
227+
if ((baton->lobAmount == 0) || (baton->lobAmount >= lob->length)) {
228+
// If user has not given lobAmount or user gave greater than
229+
// lob length, adjust the len value.
230+
if (lob->length >= baton->lobOffset) {
231+
len = lob->length - (baton->lobOffset - 1);
232+
} else {
233+
len = 1;
234+
}
235+
} else {
236+
len = baton->lobAmount;
237+
}
218238

219239
// determine size of buffer that is required
220240
if (lob->dataType == NJS_DATATYPE_BLOB) {
221-
baton->bufferSize = lob->length;
222-
} else if (dpiLob_getBufferSize(lob->handle, lob->length,
241+
baton->bufferSize = len;
242+
} else if (dpiLob_getBufferSize(lob->handle, len,
223243
&baton->bufferSize) < 0) {
224244
ok = njsBaton_setErrorDPI(baton);
225245
}
@@ -232,8 +252,8 @@ static bool njsLob_getDataAsync(njsBaton *baton)
232252
}
233253

234254
// read from the LOB into the provided buffer
235-
if (ok && baton->bufferSize > 0 && dpiLob_readBytes(lob->handle, 1,
236-
lob->length, baton->bufferPtr, &baton->bufferSize) < 0)
255+
if (ok && baton->bufferSize > 0 && dpiLob_readBytes(lob->handle,
256+
baton->lobOffset, len, baton->bufferPtr, &baton->bufferSize) < 0)
237257
ok = njsBaton_setErrorDPI(baton);
238258

239259
return ok;

test/dataTypeBlob.js

+55
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const assert = require('assert');
4242
const dbConfig = require('./dbconfig.js');
4343
const assist = require('./dataTypeAssist.js');
4444
const testsUtil = require(`./testsUtil.js`);
45+
const random = require('./random.js');
4546

4647
const inFileName = 'test/fuzzydinosaur.jpg'; // contains the image to be inserted
4748
const outFileName = 'test/blobstreamout.jpg';
@@ -146,6 +147,60 @@ describe('41. dataTypeBlob.js', function() {
146147
const blob = await lob.getData();
147148
assert.deepStrictEqual(data, blob);
148149
}); // 41.1.2
150+
151+
it('41.1.3 BLOB getData(offset, len)', async function() {
152+
const size = 32768;
153+
const specialStr = "41.1.3";
154+
const bigStr = random.getRandomString(size, specialStr);
155+
const bufferStr = Buffer.from(bigStr, "utf-8");
156+
157+
let result = await connection.execute(
158+
`INSERT INTO nodb_myblobs (num, content) VALUES (:1, :2) `,
159+
[4, bufferStr]);
160+
161+
assert.strictEqual(result.rowsAffected, 1);
162+
result = await connection.execute(
163+
"SELECT content FROM nodb_myblobs WHERE num = :n",
164+
{ n: 4 });
165+
166+
const lob = result.rows[0][0];
167+
168+
// both offset and end.
169+
let offset = 5;
170+
let len = 10;
171+
172+
// Returns data from offset -1 (lob[offset - 1])
173+
let blob = await lob.getData(offset, len);
174+
assert.deepStrictEqual(bufferStr.subarray(offset - 1,
175+
offset + len - 1), blob);
176+
177+
// end not specified gives entire data starting from offset
178+
offset = 5;
179+
blob = await lob.getData(offset);
180+
assert.deepStrictEqual(bufferStr.subarray(offset - 1), blob);
181+
182+
// large number of bytes starting from offset 5.
183+
offset = 5;
184+
len = 9999;
185+
blob = await lob.getData(offset, len);
186+
assert.deepStrictEqual(bufferStr.subarray(offset - 1, offset + len - 1),
187+
blob);
188+
189+
// large length which is ignored and entire lob data starting
190+
// from offset is returned.
191+
offset = 5;
192+
len = 99999;
193+
blob = await lob.getData(offset, len);
194+
assert.deepStrictEqual(bufferStr.subarray(offset - 1),
195+
blob);
196+
197+
// Invalid offset, we return null.
198+
offset = 99999;
199+
len = 10;
200+
blob = await lob.getData(offset, len);
201+
assert.equal(blob, null);
202+
203+
}); // 41.1.3
149204
}); //41.1
150205

151206
describe('41.2 stores null value correctly', function() {

test/dataTypeClob.js

+84
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const assert = require('assert');
4343
const dbConfig = require('./dbconfig.js');
4444
const assist = require('./dataTypeAssist.js');
4545
const testsUtil = require('./testsUtil.js');
46+
const random = require('./random.js');
4647

4748
const inFileName = 'test/clobexample.txt'; // the file with text to be inserted into the database
4849
const outFileName = 'test/clobstreamout.txt'; // output file with the stream out data
@@ -164,6 +165,89 @@ describe('40. dataTypeClob.js', function() {
164165

165166
}); // 40.1.2
166167

168+
it('40.1.3 CLOB getData(offset, len)', async function() {
169+
const lenStr = 32768;
170+
const specialStr = "40.1.3";
171+
const largeStr = random.getRandomString(lenStr, specialStr);
172+
const multiByteStr = 'aüÅæÖÆåøübcd';
173+
174+
const binds = [
175+
[3, largeStr],
176+
[4, multiByteStr]
177+
];
178+
let result = await connection.executeMany(
179+
`INSERT INTO nodb_myclobs (num, content) ` +
180+
`VALUES (:1, :2)`,
181+
binds,
182+
[
183+
{ type: oracledb.NUMBER },
184+
{ type: oracledb.CLOB }
185+
]);
186+
assert.strictEqual(result.rowsAffected, 2);
187+
188+
result = await connection.execute(
189+
"SELECT content FROM nodb_myclobs WHERE num = :n",
190+
{ n: 3 });
191+
192+
let lob = result.rows[0][0];
193+
let clob = await lob.getData();
194+
assert.strictEqual(largeStr, clob);
195+
196+
let offset = 5;
197+
let len = 10;
198+
// starting from position 4 (largeStr[4]) to 10 characters.
199+
clob = await lob.getData(offset, len);
200+
assert.strictEqual(largeStr.slice(offset - 1, offset + len - 1), clob);
201+
202+
// len not specified gives entire data starting from offset
203+
offset = 5;
204+
clob = await lob.getData(offset);
205+
assert.strictEqual(largeStr.slice(offset - 1), clob);
206+
207+
// len specified as 0 gives error.
208+
offset = 5;
209+
len = 0;
210+
await assert.rejects(
211+
async () => await lob.getData(offset, len),
212+
/NJS-005:/
213+
);
214+
215+
// large number of characters starting from offset 5.
216+
offset = 5;
217+
len = 9999;
218+
clob = await lob.getData(offset, len);
219+
assert.strictEqual(largeStr.slice(offset - 1, offset + len - 1), clob);
220+
221+
// len exceeding lob length is simply ignored and
222+
// characters till end starting from offset is returned.
223+
offset = 5;
224+
len = 99999;
225+
clob = await lob.getData(offset, len);
226+
assert.strictEqual(largeStr.slice(offset - 1), clob);
227+
228+
// Invalid ofset, we get null.
229+
offset = 99999;
230+
len = 10;
231+
clob = await lob.getData(offset, len);
232+
assert.equal(clob, null);
233+
234+
result = await connection.execute(
235+
"SELECT content FROM nodb_myclobs WHERE num = :n",
236+
{ n: 4 });
237+
238+
lob = result.rows[0][0];
239+
240+
clob = await lob.getData();
241+
assert.strictEqual(multiByteStr, clob);
242+
243+
offset = 2;
244+
len = 10;
245+
// starting from position 1 (multiByteStr[1]) to 10 characters. "üÅæÖÆåøübc"
246+
clob = await lob.getData(offset, len);
247+
assert.strictEqual(multiByteStr.slice(offset - 1, offset + len - 1), clob);
248+
249+
}); // 40.1.3
250+
167251
}); // 40.1
168252

169253
describe('40.2 stores null value correctly', function() {

test/dataTypeNclob.js

+33
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ describe('123. dataTypeNclob.js', function() {
8383

8484
});
8585

86+
it('123.1.3 works with lob getData(offset, len)', async function() {
87+
const insertLength = 100;
88+
89+
const insertStr = random.getRandomLengthString(insertLength);
90+
91+
await insertData(tableName, insertStr);
92+
93+
await getLobData(tableName, insertStr);
94+
95+
});
96+
8697
}); // 123.1
8798

8899
describe('123.2 insert and fetch as string with fetchInfo', function() {
@@ -236,6 +247,28 @@ describe('123. dataTypeNclob.js', function() {
236247
});
237248
};
238249

250+
const getLobData = async function(tableName, originalStr) {
251+
let result = null;
252+
result = await connection.execute("SELECT TO_CLOB(content) FROM " + tableName + " where num = " + insertID);
253+
const lob = result.rows[0][0];
254+
255+
assert(lob);
256+
lob.setEncoding('utf8'); // set the encoding so we get a 'string' not a 'buffer'
257+
let data = await lob.getData();
258+
assert.strictEqual(originalStr, data);
259+
260+
let offset = 5;
261+
data = await lob.getData(offset);
262+
assert.strictEqual(originalStr.slice(offset - 1), data);
263+
264+
// len exceeding lob length is simply ignored and
265+
// characters till end starting from offset is returned.
266+
offset = 5;
267+
const len = 99999;
268+
data = await lob.getData(offset, len);
269+
assert.strictEqual(originalStr.slice(offset - 1), data);
270+
};
271+
239272
const fetchLob_fetchInfo = async function(tableName, originalStr) {
240273
let result = null;
241274
result = await connection.execute(

test/list.txt

+3
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,15 @@ Overview of node-oracledb functional tests
645645
40.1 testing CLOB data type
646646
40.1.1 stores CLOB value correctly
647647
40.1.2 CLOB getData()
648+
40.1.3 CLOB getData(offset, len)
648649
40.2 stores null value correctly
649650
40.2.1 testing Null, Empty string and Undefined
650651

651652
41. dataTypeBlob.js
652653
41.1 testing BLOB data type
653654
41.1.1 stores BLOB value correctly
654655
41.1.2 BLOB getData()
656+
41.1.3 BLOB getData(offset, len)
655657
41.2 stores null value correctly
656658
41.2.1 testing Null, Empty string and Undefined
657659
41.3 OSON column metadata
@@ -3409,6 +3411,7 @@ oracledb.OUT_FORMAT_OBJECT and resultSet = true
34093411
123.1 insert and stream out
34103412
123.1.1 works with data size 100
34113413
123.1.2 works with data size 3000
3414+
123.1.3 works with lob getData (offset, len)
34123415
123.2 insert and fetch as string with fetchInfo
34133416
123.2.1 works with data size 100
34143417
123.2.2 works with data size 3000

0 commit comments

Comments
 (0)