dolphinsmalltalk / Dolphin

Dolphin Smalltalk Core Image
MIT License
301 stars 58 forks source link

Dolphin 7.1.24 ODBC doesn't support varchar(max) or varbinary(max) #1286

Open oldhamir opened 4 months ago

oldhamir commented 4 months ago

Describe the bug If you use Dolphin ODBC to access a DB table that uses varchar(max) or varbinary(max) you'll get a walkback as Dolphin is trying to copy the entry's column data from a 0 length ByteArray.

To Reproduce Steps to reproduce the behaviour:

  1. Open a Dolphin ODBC connection to a database that has a table that has varbinary(max) and/or varchar(max) columns.
  2. Try and retrieve data from the column(s) in that table and get a walkback like the following:

2024-05-26T21:27:08.7755494+01:00: Unhandled exception - a BoundsError('Index 2274981 is out of bounds')

ByteArray(Object)>>errorSubscriptBounds: ByteArray(Object)>>_primitiveError:at: ByteArray(Object)>>basicAt: ByteArray>>replaceBytesOf:from:to:startingAt: ByteArray>>replaceFrom:to:with:startingAt: ByteArray(ArrayedCollection)>>copyFrom:to: DBField>>getByteArray DBField>>value [] in DBRowBuffer>>objects [] in SequenceableCollection>>collect: Array(SequenceableCollection)>>uncheckedFrom:to:keysAndValuesDo: Array(SequenceableCollection)>>keysAndValuesDo: Array(SequenceableCollection)>>collect: DBBoundBuffer(DBRowBuffer)>>objects DBRow>>initializeFromBuffer: DBRow class>>fromBuffer: DBBoundBuffer(DBRowBuffer)>>asObject DBForwardOnlyResultSet(DBResultSet)>>from:keysAndValuesDo: DBForwardOnlyResultSet(DBResultSet)>>keysAndValuesDo: DBForwardOnlyResultSet(SequenceableCollection)>>do: DBForwardOnlyResultSet(DBResultSet)>>collect: OdbcDbDataService>>collectResultsFrom:for:using: [] in OdbcDbDataService(DbDataService)>>getEntitiesFromDb:for:creator:

Expected behaviour I would expect the data to be successfully copied as it is for varchar(n) and/or varbinary(n) columns.

Screenshots The problem (IMHO) is that in DBField>>initializeForColumn: the column attributes for the varbinary(max) column are as follows: image

So a 0 length ByteArray is allocated to hold the data when it is fetched from the table. As the DB reports the length of that particular entry as being 2274981 bytes you get the walkback when DBField>>getByteArray is called .

I don't know an awful lot about the inner details of SQL/ODBC but my assumption is that the 0 length for the SQL_VARBINARY attribute means max i.e. up to 2GB.

Please complete the following information):

Additional context Given that none of my uses need more than around 4MB per varbinary/varchar entries I made it so that a 16MB buffer is allocated for varbinary(max) and varchar(max) columns, but all that has done is to "kick the can down the road" for a more general solution, given that these columns can in theory hold 2GB per entry.

I made the following changes to my Dolphin core image to hack me past the problem:

DBCollAttr>>lengthC

    (length = 0 and: [self isBinaryType or: [self isCharType]]) ifTrue: [^##(2 raisedTo: 24)].
    ^length + (CTypesExtraBytes at: sqlType + TypeOffset)
DBField>>initializeForColumn: aDBColAttr

lengthBuf := SDWORD fromInteger: SQL_NULL_DATA.
    column := aDBColAttr.
    buffer := ByteArray newFixed: (##(2 raisedTo: 24) min: aDBColAttr lengthC).
    ^self