From e0b5816cc656c6b6cd3ef9b12a4b7b1cf00fc99d Mon Sep 17 00:00:00 2001 From: he-is-harry Date: Thu, 6 Mar 2025 13:26:16 -0500 Subject: [PATCH] Fixed blob stream deadlock Fixed the issue in SAP/node-hdb#233 where streaming blobs out of the database into another table can cause a deadlock - Added a "blocked" mode to the Queue which prevents tasks from running except for the blocking task and READ_LOB tasks - Modified ExecuteTask's run to free the queue while it waits for the Writer's getParameters - The callback of getParameters will enqueue the task again to send the packet - Before the freeing of the queue to the next task, ExecuteTask will block the queue to only allow itself and READ_LOB tasks to run - This prevents issues where exec's can run at the same time which will lead to HANA disconnecting and sending invalid LOB locator id errors --- lib/protocol/Connection.js | 5 + lib/protocol/ExecuteTask.js | 139 ++++++++++++++++------------ lib/util/Queue.js | 100 ++++++++++++++++++-- test/acceptance/db.Lob.js | 177 ++++++++++++++++++++++++++++++++++++ test/lib.ExecuteTask.js | 134 ++++++++++++++++++++++----- 5 files changed, 468 insertions(+), 87 deletions(-) diff --git a/lib/protocol/Connection.js b/lib/protocol/Connection.js index 1b998e1..6ca294b 100644 --- a/lib/protocol/Connection.js +++ b/lib/protocol/Connection.js @@ -441,6 +441,7 @@ Connection.prototype.enqueue = function enqueue(task, cb) { if (task instanceof request.Segment) { queueable = this._queue.createTask(this.send.bind(this, task), cb); queueable.name = MessageTypeName[task.type]; + queueable.msgType = task.type; } else if (util.isFunction(task.run)) { queueable = task; } @@ -732,6 +733,10 @@ Connection.prototype.isIdle = function isIdle() { return this._queue.empty && !this._queue.busy; }; +Connection.prototype.blockQueue = function blockQueue(blockingTask) { + this._queue.block(blockingTask); +} + Connection.prototype.setAutoCommit = function setAutoCommit(autoCommit) { this._transaction.autoCommit = autoCommit; }; diff --git a/lib/protocol/ExecuteTask.js b/lib/protocol/ExecuteTask.js index 4df380e..3f55550 100644 --- a/lib/protocol/ExecuteTask.js +++ b/lib/protocol/ExecuteTask.js @@ -39,6 +39,9 @@ function ExecuteTask(connection, options, callback) { } this.callback = callback; this.reply = undefined; + this.finishedError = null; + this.finishedParameters = undefined; + this.isExecuteParams = true; } ExecuteTask.create = function createExecuteTask(connection, options, cb) { @@ -59,60 +62,79 @@ ExecuteTask.prototype.run = function run(next) { } if (err) { return self.sendRollback(function () { - // ignore roolback error + // ignore rollback error done(err); }); } self.sendCommit(done); } - function execute() { + function getExecuteRequest() { if (!self.parameterValues.length && !self.writer.hasParameters) { return finalize(); } - self.sendExecute(function receive(err, reply) { - if (err) { - return finalize(err); - } - if (!self.writer.finished && reply.rowsAffected == -1) { - reply.rowsAffected = undefined; - } - self.pushReply(reply); - if (!self.writer.finished && reply.writeLobReply) { - self.writer.update(reply.writeLobReply); - } - writeLob(); + self.finishedParameters = undefined; + var availableSize = self.connection.getAvailableSize(false) - STATEMENT_ID_PART_LENGTH; + var availableSizeForLOBs = self.connection.getAvailableSize(true) - STATEMENT_ID_PART_LENGTH; + + // Block the queue to this task and read lob requests + self.connection.blockQueue(self); + + self.getParameters(availableSize, availableSizeForLOBs, function send(err, parameters) { + // Enqueue itself to wait for when the task becomes the one actively running in the queue + // and the connection is avaliable to send the packet + self.finishedError = err; + self.finishedParameters = parameters; + self.isExecuteParams = true; + self.connection.enqueue(self); }); + + // Yield to only read lob tasks in the queue, the callback will enqueue this task + // again once the parameters are ready + next(); } - function writeLob() { + function getWriteLobRequest() { if (self.writer.finished || self.writer.hasParameters) { - return execute(); + return getExecuteRequest(); } - self.sendWriteLobRequest(function receive(err, reply) { - /* jshint unused:false */ - if (err) { - return finalize(err); - } - self.pushReply(reply); - writeLob(); + self.finishedParameters = undefined; + var availableSize = self.connection.getAvailableSize(true); + self.connection.blockQueue(self); + self.writer.getWriteLobRequest(availableSize, function (err, buffer) { + self.finishedError = err; + self.finishedParameters = buffer; + self.isExecuteParams = false; + self.connection.enqueue(self); }); + + next(); } - // validate function code - if (self.parameterValues.length > 1) { - switch (self.functionCode) { - case FunctionCode.DDL: - case FunctionCode.INSERT: - case FunctionCode.UPDATE: - case FunctionCode.DELETE: - break; - default: - return done(createInvalidFunctionCodeError()); + if (this.finishedError) { + finalize(this.finishedError); + } else if (this.finishedParameters) { + if (this.isExecuteParams) { + self.sendExecute(this.finishedParameters, finalize, getWriteLobRequest); + } else { + self.sendWriteLobRequest(this.finishedParameters, finalize, getWriteLobRequest); + } + } else { // No stored error or parameters, so get initial execute data + // validate function code + if (self.parameterValues.length > 1) { + switch (self.functionCode) { + case FunctionCode.DDL: + case FunctionCode.INSERT: + case FunctionCode.UPDATE: + case FunctionCode.DELETE: + break; + default: + return done(createInvalidFunctionCodeError()); + } } - } - execute(); + getExecuteRequest(); + } }; ExecuteTask.prototype.end = function end(err) { @@ -195,35 +217,40 @@ ExecuteTask.prototype.getParameters = function getParameters(availableSize, avai next(); }; -ExecuteTask.prototype.sendExecute = function sendExecute(cb) { +ExecuteTask.prototype.sendExecute = function sendExecute(parameters, finalize, cb) { var self = this; - var availableSize = self.connection.getAvailableSize(false) - STATEMENT_ID_PART_LENGTH; - var availableSizeForLOBs = self.connection.getAvailableSize(true) - STATEMENT_ID_PART_LENGTH; - self.getParameters(availableSize, availableSizeForLOBs, function send(err, parameters) { + self.connection.send(request.execute({ + autoCommit: self.autoCommit, + holdCursorsOverCommit: self.holdCursorsOverCommit, + scrollableCursor: self.scrollableCursor, + statementId: self.statementId, + parameters: parameters, + useCesu8: self.connection.useCesu8 + }), function (err, reply) { if (err) { - return cb(err); + return finalize(err); } - self.connection.send(request.execute({ - autoCommit: self.autoCommit, - holdCursorsOverCommit: self.holdCursorsOverCommit, - scrollableCursor: self.scrollableCursor, - statementId: self.statementId, - parameters: parameters, - useCesu8: self.connection.useCesu8 - }), cb); + if (!self.writer.finished && reply.rowsAffected == -1) { + reply.rowsAffected = undefined; + } + self.pushReply(reply); + if (!self.writer.finished && reply.writeLobReply) { + self.writer.update(reply.writeLobReply); + } + cb(); }); -}; +} -ExecuteTask.prototype.sendWriteLobRequest = function sendWriteLobRequest(cb) { +ExecuteTask.prototype.sendWriteLobRequest = function sendWriteLobRequest(buffer, finalize, cb) { var self = this; - var availableSize = self.connection.getAvailableSize(true); - self.writer.getWriteLobRequest(availableSize, function send(err, buffer) { + self.connection.send(request.writeLob({ + writeLobRequest: buffer + }), function (err, reply) { if (err) { - return cb(err); + return finalize(err); } - self.connection.send(request.writeLob({ - writeLobRequest: buffer - }), cb); + self.pushReply(reply); + cb(); }); }; diff --git a/lib/util/Queue.js b/lib/util/Queue.js index 85cb7dc..4fe71f0 100644 --- a/lib/util/Queue.js +++ b/lib/util/Queue.js @@ -15,6 +15,7 @@ var util = require('util'); var EventEmitter = require('events').EventEmitter; +var MessageType = require('../protocol/common/MessageType'); module.exports = Queue; @@ -26,6 +27,12 @@ function Queue(immediate) { this.queue = []; this.busy = false; this.running = !!immediate; + // Records read lob tasks which can be called out of position when + // the queue is blocked. If other tasks need to be called out of position + // this can be changed to a Map with the message type as keys. + this.readLobQueue = []; + this.blocked = false; + this.blockingTask = undefined; } Object.defineProperty(Queue.prototype, 'empty', { @@ -36,14 +43,25 @@ Object.defineProperty(Queue.prototype, 'empty', { Queue.prototype.unshift = function unshift(task) { this.queue.unshift(task); - if (this.running) { + if (task.msgType === MessageType.READ_LOB) { + this.readLobQueue.unshift(task); + } + if (this.blocked && this._isBlockingTask(task)) { + this.emit('unblock', task); + } else if (this.running) { this.dequeue(); } return this; }; Queue.prototype.push = function push(task) { + if (this.blocked && this._isBlockingTask(task)) { + return this.unshift(task); + } this.queue.push(task); + if (task.msgType === MessageType.READ_LOB) { + this.readLobQueue.push(task); + } if (this.running) { this.dequeue(); } @@ -72,14 +90,28 @@ Queue.prototype.abort = function abort(err) { return this; }; -Queue.prototype.createTask = function createTask(send, receive, name) { - return new Task(send, receive, name); +Queue.prototype.createTask = function createTask(send, receive, name, msgType) { + return new Task(send, receive, name, msgType); }; +Queue.prototype.block = function block(blockingTask) { + this.blocked = true; + this.blockingTask = blockingTask; +} + +Queue.prototype.unblock = function unblock() { + this.blocked = false; + this.blockingTask = undefined; +} + +Queue.prototype._isBlockingTask = function _isBlockingTask(task) { + return task === this.blockingTask || task.msgType === MessageType.READ_LOB; +} + Queue.prototype.dequeue = function dequeue() { var self = this; - function next(err, name) { + function runNext() { /* jshint unused:false */ self.busy = false; if (self.queue.length) { @@ -89,21 +121,77 @@ Queue.prototype.dequeue = function dequeue() { } } + function runReadLob() { + if (self.readLobQueue.length) { + self.busy = false; + if (self.running && !self.busy) { + self.busy = true; + var task = self.readLobQueue.shift(); + // Mark the task as ran so it will be skipped in the queue + task.ran = true; + // Optimization: When blocked, often read lobs are the most recently + // added at the beginning or end of the queue so they can be removed from there + // Note that the queue is not empty since it always has at least as many elements + // as the readLobQueue + if (self.queue[0] === task) { + self.queue.shift(); + } else if (self.queue[self.queue.length - 1] === task) { + self.queue.pop(); + } + task.run(next); + } + } else { + runNext(); + } + } + + function next(err, name) { + if (self.blocked) { + // Check if there exists a task that can be run + if (self.queue.length && self.blockingTask === self.queue[0]) { + self.unblock(); + runNext(); + } else if (self.readLobQueue.length) { + runReadLob(); + } else { + self.once('unblock', function runTask (task) { + if (task === self.blockingTask) { + self.unblock(); + runNext(); + } else { + runReadLob(); + } + }); + } + } else { + runNext(); + } + } + function run() { if (self.running && !self.busy) { // Queue is running and not busy self.busy = true; var task = self.queue.shift(); - task.run(next); + if (task.ran) { + next(null, task.name); + } else { + if (task.msgType === MessageType.READ_LOB) { + self.readLobQueue.shift(); + } + task.run(next); + } } } run(); }; -function Task(send, receive, name) { +function Task(send, receive, name, msgType) { this.send = send; this.receive = receive; this.name = name; + this.msgType = msgType; + this.ran = false; } Task.prototype.run = function run(next) { diff --git a/test/acceptance/db.Lob.js b/test/acceptance/db.Lob.js index d371c2e..48abb20 100644 --- a/test/acceptance/db.Lob.js +++ b/test/acceptance/db.Lob.js @@ -310,6 +310,164 @@ describe('db', function () { }); }); + + describeRemoteDB('stream write lob from read lob (issue 233)', function () { + this.timeout(10000); + beforeEach(function (done) { + if (isRemoteDB) { + db.createTable.bind(db)('TEST_STREAM_BLOB_DEST', ['A BLOB'], null, function (err) { + if (err) done(err); + db.createTable.bind(db)('TEST_STREAM_BLOB_SRC', ['A BLOB'], null, done); + }); + } else { + this.skip(); + done(); + } + }); + afterEach(function (done) { + if (isRemoteDB) { + db.dropTable.bind(db)('TEST_STREAM_BLOB_DEST', function (err) { + db.dropTable.bind(db)('TEST_STREAM_BLOB_SRC', done); + }); + } else { + done(); + } + }); + + var dirname = path.join(__dirname, '..', 'fixtures', 'img'); + + it('should write a blob streamed from the database', function (done) { + var statement; + var resultAdapter = new ResultAdapter(); + var buffer = Buffer.alloc(2 * client._connection.packetSize); + for (var i = 0; i < buffer.length; i++) { + buffer[i] = i % 25; + } + function prepareSourceInsert(cb) { + client.prepare('INSERT INTO TEST_STREAM_BLOB_SRC VALUES (?)', function (err, ps) { + if (err) done(err); + statement = ps; + cb(err); + }); + } + + function sourceInsert(cb) { + statement.exec([buffer], function (err, rowsAffected) { + if (err) done(err); + statement.drop(); + cb(err); + }); + } + + function sourceSelect(cb) { + client.execute('SELECT * FROM TEST_STREAM_BLOB_SRC', function (err, rs) { + if (err) done(err); + var objectStream = rs.createObjectStream(); + objectStream.pipe(resultAdapter); + cb(); + }); + } + + function prepareDestInsert(cb) { + client.prepare('INSERT INTO TEST_STREAM_BLOB_DEST VALUES (?)', function (err, ps) { + if (err) done(err); + statement = ps; + cb(err); + }); + } + + function destInsert(cb) { + statement.exec([resultAdapter], function (err, rowsAffected) { + if (err) done(err); + statement.drop(); + cb(err); + }) + } + + function destSelect(cb) { + client.exec('SELECT * FROM TEST_STREAM_BLOB_DEST', function (err, rows) { + if (err) done(err); + rows.should.have.length(1); + rows.should.eql([{A: buffer}]); + cb(err); + }) + } + + async.waterfall([prepareSourceInsert, sourceInsert, sourceSelect, prepareDestInsert, + destInsert, destSelect], done); + }); + + it('should write a blob streamed from the database while another exec is enqueued', function (done) { + var statement; + var resultAdapter = new ResultAdapter(); + var buffer = Buffer.alloc(2 * client._connection.packetSize); + for (var i = 0; i < buffer.length; i++) { + buffer[i] = i % 25; + } + function prepareSourceInsert(cb) { + client.prepare('INSERT INTO TEST_STREAM_BLOB_SRC VALUES (?)', function (err, ps) { + if (err) done(err); + statement = ps; + cb(err); + }); + } + + function sourceInsert(cb) { + statement.exec([buffer], function (err, rowsAffected) { + if (err) done(err); + statement.drop(); + cb(err); + }); + } + + function sourceSelect(cb) { + client.execute('SELECT * FROM TEST_STREAM_BLOB_SRC', function (err, rs) { + if (err) done(err); + var objectStream = rs.createObjectStream(); + objectStream.pipe(resultAdapter); + cb(); + }); + } + + function insertTwiceAndSelect(cb) { + var callbackNum = 1; + client.prepare('INSERT INTO TEST_STREAM_BLOB_DEST VALUES (?)', function (err, stmt1) { // First + if (err) done(err); + callbackNum.should.equal(1); + callbackNum++; + stmt1.exec([resultAdapter], function (err, rowsAffected) { // Third + if (err) done(err); + rowsAffected.should.equal(1); + callbackNum.should.equal(3); + callbackNum++; + stmt1.drop(); + client.exec('SELECT * FROM TEST_STREAM_BLOB_DEST', function (err, rows) { // Fifth + if (err) done(err); + callbackNum.should.equal(5); + callbackNum++; + rows.should.have.length(1); + rows.should.eql([{A: buffer}]); + cb(err); + }); + }); + }); + client.prepare('INSERT INTO TEST_STREAM_BLOB_SRC VALUES (?)', function (err, stmt2) { // Second + if (err) done(err); + callbackNum.should.equal(2); + callbackNum++; + stmt2.exec([fs.createReadStream(path.join(dirname, 'lobby.jpg'))], function (err, rowsAffected) { // Fourth + if (err) done(err); + rowsAffected.should.equal(1); + callbackNum.should.equal(4); + callbackNum++; + stmt2.drop(); + }); + }); + } + + async.waterfall([prepareSourceInsert, sourceInsert, sourceSelect, insertTwiceAndSelect], done); + }); + }); }); function MD5(data) { @@ -339,3 +497,22 @@ StrictMemoryTransform.prototype._transform = function _transform(chunk, encoding } tryPush(); } + +util.inherits(ResultAdapter, stream.Transform); + +function ResultAdapter() { + stream.Transform.call(this, { objectMode: true }); +} + +// Transform will not call back until the data is completely consumed +ResultAdapter.prototype._transform = function _transform(data, encoding, cb) { + var stream = data.A.createReadStream(); + stream.on('readable', function(){ + var content; + while((content = stream.read()) != null){ + this.push(content); + } + }.bind(this)); + stream.on('end', cb); + stream.on('error', cb); +} \ No newline at end of file diff --git a/test/lib.ExecuteTask.js b/test/lib.ExecuteTask.js index c8074ad..5921990 100644 --- a/test/lib.ExecuteTask.js +++ b/test/lib.ExecuteTask.js @@ -76,7 +76,8 @@ describe('Lib', function () { } }, function done(err) { err.should.be.an.instanceOf(Error); - }).run(next); + next(); + }).runTest(); }); it('should raise an error correctly', function (next) { @@ -97,10 +98,11 @@ describe('Lib', function () { }] }, function done(err) { err.should.be.an.instanceOf(Error); + next(); }, false); task.writer._types = undefined; - task.run(next); + task.runTest(); }); it('should run a batch task with INT type', function (next) { @@ -122,7 +124,8 @@ describe('Lib', function () { }, function done(err, reply) { (!err).should.be.ok; reply.rowsAffected.should.eql([1, 1, 1]); - }).run(next); + next(); + }).runTest(); }); it('should run a large batch task', function (next) { @@ -147,7 +150,8 @@ describe('Lib', function () { }, function done(err, reply) { (!err).should.be.ok; reply.rowsAffected.should.eql(rowsAffected); - }).run(next); + next(); + }).runTest(); }); it( @@ -233,7 +237,8 @@ describe('Lib', function () { }, function done(err, reply) { (!err).should.be.ok; reply.rowsAffected.should.eql([1, 1, 1, 1]); - }).run(next); + next(); + }).runTest(); }); it('should run a single failing task with INT type', function (next) { @@ -244,7 +249,8 @@ describe('Lib', function () { }] }, function done(err) { err.should.be.an.instanceOf(Error); - }).run(next); + next(); + }).runTest(); }); it('should run a single task with BLOB type', function (next) { @@ -280,7 +286,8 @@ describe('Lib', function () { (!err).should.be.ok; reply.rowsAffected.should.eql([1]); reply.writeLobReply[0].should.eql(locatorId); - }).run(next); + next(); + }).runTest(); }); it('should run a single failing task with BLOB type ', function (next) { @@ -305,7 +312,8 @@ describe('Lib', function () { }] }, function done(err) { err.should.be.an.instanceOf(Error); - }).run(next); + next(); + }).runTest(); }); it('should fail to bind parameters if insufficient space left in packet', function(next) { @@ -321,7 +329,8 @@ describe('Lib', function () { }, function done(err) { err.should.be.an.instanceOf(Error); err.message.should.equal('Failed to set parameters, maximum packet size exceeded.'); - }).run(cb); + cb(); + }).runTest(); } function succeedWithBinding(cb) { @@ -337,7 +346,8 @@ describe('Lib', function () { }] }, function done(err) { (!err).should.be.ok; - }).run(cb); + cb(); + }).runTest(); } failToBind(() => { @@ -399,7 +409,8 @@ describe('Lib', function () { }] }, function done(err) { (!err).should.be.ok; - }).run(next); + next(); + }).runTest(); }); it('should run a task with one stream parameter with read stream error between writeLOB chunks', function (next) { @@ -431,7 +442,8 @@ describe('Lib', function () { }] }, function done(err) { err.should.be.an.instanceOf(Error); - }).run(next); + next(); + }).runTest(); }); it('should run a task with multiple stream parameters with read stream error between writeLOB chunks', function (next) { @@ -482,7 +494,8 @@ describe('Lib', function () { }] }, function done(err) { err.should.be.an.instanceOf(Error); - }).run(next); + next(); + }).runTest(); }); it('should run a task with read stream error before parameters bound', function (next) { @@ -502,7 +515,8 @@ describe('Lib', function () { replies: [] }, function done(err) { err.should.be.an.instanceOf(Error); - }).run(next); + next(); + }).runTest(); }); it('should accumulate rows affected', function () { @@ -570,31 +584,87 @@ describe('Lib', function () { }); }); - it('should sendExecute with error', function (done) { - var task = createExecuteTask(); + it('should run getExecuteRequest with error', function (next) { var error = new Error('error'); + var task = createExecuteTask({ + parameters: { + types: [TypeCode.INT], + values: [1] + }, + replies: [] + }, function done(err) { + err.should.equal(error); + next(); + }); task.getParameters = function (availableSize, availableSizeForLOBs, cb) { availableSize.should.equal(40); availableSizeForLOBs.should.equal(40); cb(error); }; - task.sendExecute(function (err) { + task.runTest(); + }); + + it('should run getWriteLobRequest with error', function (next) { + var buffer = Buffer.alloc(64); + var locatorId = Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]); + var error = new Error('error'); + var task = createExecuteTask({ + parameters: { + types: [TypeCode.BLOB], + values: [buffer] + }, + replies: [{ + type: MessageType.EXECUTE, + args: [null, { + writeLobReply: [locatorId] + }] + }, { + type: MessageType.ROLLBACK, + args: [null, {}] + }] + }, function done(err) { err.should.equal(error); - done(); + next(); }); + task.writer.getWriteLobRequest = function (availableSize, cb) { + availableSize.should.equal(64); + cb(error); + }; + task.runTest(); + }); + + it('should sendExecute with error', function (done) { + var task = createExecuteTask(); + var error = new Error('error'); + task.connection.send = function (msg, cb) { + cb(error); + }; + task.sendExecute( + Buffer.alloc(0), + function (err) { // finalize + err.should.equal(error); + done(); + }, + function () { + done(new Error("Should call finalize immediately, not callback")); + }); }); it('should sendWriteLobRequest with error', function (done) { var task = createExecuteTask(); var error = new Error('error'); - task.writer.getWriteLobRequest = function (availableSize, cb) { - availableSize.should.equal(64); + task.connection.send = function (msg, cb) { cb(error); }; - task.sendWriteLobRequest(function (err) { - err.should.equal(error); - done(); - }); + task.sendWriteLobRequest( + Buffer.alloc(0), + function (err) { // finalize + err.should.equal(error); + done(); + }, + function () { + done(new Error("Should call finalize immediately, not callback")); + }); }); it('should provide cesu-8 configuration in execute', function(done) { @@ -632,12 +702,16 @@ function createExecuteTask(options, cb, checkReplies) { options.availableSizeForLobs = undefined; options.replies = undefined; if (checkReplies === undefined) checkReplies = true; - return ExecuteTask.create(connection, options, function () { + var task = ExecuteTask.create(connection, options, function () { if (checkReplies) { connection.replies.should.have.length(0); } cb.apply(null, arguments); }); + task.runTest = function () { + this.connection.enqueue(this); + } + return task; } function Connection(size, sizeForLobs, replies) { @@ -645,6 +719,7 @@ function Connection(size, sizeForLobs, replies) { this.sizeForLobs = sizeForLobs; this.replies = replies || []; this.useCesu8 = true; + this.queue = new util.Queue(true); } Connection.prototype.send = function (msg, cb) { @@ -658,6 +733,15 @@ Connection.prototype.send = function (msg, cb) { }); }; +Connection.prototype.enqueue = function enqueue(task, cb) { + // The task will always be the same ExecuteTask enqueuing itself + this.queue.push(task); +}; + +Connection.prototype.blockQueue = function blockQueue(blockingTask) { + this.queue.block(blockingTask); +} + Connection.prototype.getAvailableSize = function (forLobs = false) { if(forLobs) { return this.sizeForLobs;