Forráskód Böngészése

fcl-db: odbc: implements RefreshLastInsertID for autoincrementing columns.
Bug #16076

git-svn-id: trunk@29751 -

lacak 10 éve
szülő
commit
2a2078e7f7

+ 240 - 201
packages/fcl-db/src/sqldb/odbc/odbcconn.pas

@@ -56,10 +56,16 @@ type
 
   TODBCConnection = class(TSQLConnection)
   private
-    FDriver: string;
-    FEnvironment:TODBCEnvironment;
-    FDBCHandle:SQLHDBC; // ODBC Connection Handle
-    FFileDSN: string;
+    type
+      TDBMSInfo = record
+        GetLastInsertIDSQL: string; // SQL statement for get last insert value for autoincrement column
+      end;
+    var
+      FDriver: string;
+      FEnvironment:TODBCEnvironment;
+      FDBCHandle:SQLHDBC; // ODBC Connection Handle
+      FFileDSN: string;
+      FDBMSInfo: TDBMSInfo;
 
     procedure SetParameters(ODBCCursor:TODBCCursor; AParams:TParams);
     procedure FreeParamBuffers(ODBCCursor:TODBCCursor);
@@ -87,6 +93,7 @@ type
     // - Statement execution
     procedure Execute(cursor:TSQLCursor; ATransaction:TSQLTransaction; AParams:TParams); override;
     function RowsAffected(cursor: TSQLCursor): TRowsCount; override;
+    function RefreshLastInsertID(Query : TCustomSQLQuery; Field : TField): boolean; override;
     // - Result retrieving
     procedure AddFieldDefs(cursor:TSQLCursor; FieldDefs:TFieldDefs); override;
     function Fetch(cursor:TSQLCursor):boolean; override;
@@ -545,6 +552,7 @@ var
   ConnectionString:string;
   OutConnectionString:string;
   ActualLength:SQLSMALLINT;
+  DBMS_NAME: array[0..20] of AnsiChar;
 begin
   // Do not call the inherited method as it checks for a non-empty DatabaseName, and we don't even use DatabaseName!
   // inherited DoInternalConnect;
@@ -589,9 +597,24 @@ begin
     end;
   end;
 
-// commented out as the OutConnectionString is not used further at the moment
-//  if ActualLength<BufferLength-1 then
-//    SetLength(OutConnectionString,ActualLength); // fix completed connection string length
+  // set DBMS specific options
+  if SQLGetInfo(FDBCHandle, SQL_DBMS_NAME, @DBMS_NAME, sizeof(DBMS_NAME), @ActualLength) = SQL_SUCCESS then
+    case AnsiString(DBMS_NAME) of
+      'Microsoft SQL Server':
+        begin
+        FDBMSInfo.GetLastInsertIDSQL := 'SELECT @@IDENTITY';
+        Include(FConnOptions, sqLastInsertID);
+        end;
+      'MySQL':
+        begin
+        FDBMSInfo.GetLastInsertIDSQL := 'SELECT last_insert_id()';
+        Include(FConnOptions, sqLastInsertID);
+        end;
+      else
+        begin
+        FDBMSInfo.GetLastInsertIDSQL := '';
+        end;
+    end;
 
   // set connection attributes (none yet)
 end;
@@ -779,204 +802,20 @@ begin
     Result:=-1;
 end;
 
-function TODBCConnection.Fetch(cursor: TSQLCursor): boolean;
+function TODBCConnection.RefreshLastInsertID(Query : TCustomSQLQuery; Field : TField): boolean;
 var
-  ODBCCursor:TODBCCursor;
-  Res:SQLRETURN;
+  STMTHandle: SQLHSTMT;
+  StrLenOrInd: SQLLEN;
+  LastInsertID: LargeInt;
 begin
-  ODBCCursor:=cursor as TODBCCursor;
-
-  // fetch new row
-  Res:=SQLFetch(ODBCCursor.FSTMTHandle);
-  if Res<>SQL_NO_DATA then
-    ODBCCheckResult(Res,SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not fetch new row from result set.');
-
-  // result is true iff a new row was available
-  Result:=Res<>SQL_NO_DATA;
-end;
-
-const
-  DEFAULT_BLOB_BUFFER_SIZE = 1024;
-
-function TODBCConnection.LoadField(cursor: TSQLCursor; FieldDef: TFieldDef; buffer: pointer; out CreateBlob : boolean): boolean;
-var
-  ODBCCursor:TODBCCursor;
-  StrLenOrInd:SQLLEN;
-  ODBCDateStruct:SQL_DATE_STRUCT;
-  ODBCTimeStruct:SQL_TIME_STRUCT;
-  ODBCTimeStampStruct:SQL_TIMESTAMP_STRUCT;
-  DateTime:TDateTime;
-  Res:SQLRETURN;
-begin
-  CreateBlob := False;
-  ODBCCursor:=cursor as TODBCCursor;
-
-  // load the field using SQLGetData
-  // Note: optionally we can implement the use of SQLBindCol later for even more speed
-  // TODO: finish this
-  case FieldDef.DataType of
-    ftWideString,ftFixedWideChar: // mapped to TWideStringField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_WCHAR, buffer, FieldDef.Size+sizeof(WideChar), @StrLenOrInd); //buffer must contain space for the null-termination character
-    ftGuid, ftFixedChar,ftString: // are mapped to a TStringField (including TGuidField)
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_CHAR, buffer, FieldDef.Size+1, @StrLenOrInd);
-    ftSmallint:           // mapped to TSmallintField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_SSHORT, buffer, SizeOf(Smallint), @StrLenOrInd);
-    ftInteger,ftAutoInc:  // mapped to TLongintField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_SLONG, buffer, SizeOf(Longint), @StrLenOrInd);
-    ftWord:               // mapped to TWordField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_USHORT, buffer, SizeOf(Word), @StrLenOrInd);
-    ftLargeint:           // mapped to TLargeintField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_SBIGINT, buffer, SizeOf(Largeint), @StrLenOrInd);
-    ftFloat,ftCurrency:   // mapped to TFloatField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_DOUBLE, buffer, SizeOf(Double), @StrLenOrInd);
-    ftTime:               // mapped to TTimeField
-    begin
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_TIME, @ODBCTimeStruct, SizeOf(SQL_TIME_STRUCT), @StrLenOrInd);
-      if StrLenOrInd<>SQL_NULL_DATA then
-      begin
-        DateTime:=TimeStructToDateTime(@ODBCTimeStruct);
-        Move(DateTime, buffer^, SizeOf(TDateTime));
-      end;
-    end;
-    ftDate:               // mapped to TDateField
-    begin
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_DATE, @ODBCDateStruct, SizeOf(SQL_DATE_STRUCT), @StrLenOrInd);
-      if StrLenOrInd<>SQL_NULL_DATA then
-      begin
-        DateTime:=DateStructToDateTime(@ODBCDateStruct);
-        Move(DateTime, buffer^, SizeOf(TDateTime));
-      end;
-    end;
-    ftDateTime:           // mapped to TDateTimeField
-    begin
-      // Seems like not all ODBC-drivers (mysql on Linux) set the fractional part. Initialize
-      // it's value to avoid 'random' data.
-      ODBCTimeStampStruct.Fraction:=0;
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_TIMESTAMP, @ODBCTimeStampStruct, SizeOf(SQL_TIMESTAMP_STRUCT), @StrLenOrInd);
-      if StrLenOrInd<>SQL_NULL_DATA then
-      begin
-        DateTime:=TimeStampStructToDateTime(@ODBCTimeStampStruct);
-        Move(DateTime, buffer^, SizeOf(TDateTime));
-      end;
-    end;
-    ftBoolean:            // mapped to TBooleanField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BIT, buffer, SizeOf(Wordbool), @StrLenOrInd);
-    ftBytes:              // mapped to TBytesField
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd);
-    ftVarBytes:           // mapped to TVarBytesField
-    begin
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer+SizeOf(Word), FieldDef.Size, @StrLenOrInd);
-      if StrLenOrInd < 0 then
-        PWord(buffer)^ := 0
-      else
-        PWord(buffer)^ := StrLenOrInd;
-    end;
-    ftWideMemo,
-    ftBlob, ftMemo:       // BLOBs
+  if SQLAllocHandle(SQL_HANDLE_STMT, FDBCHandle, STMTHandle) = SQL_SUCCESS then
     begin
-      //Writeln('BLOB');
-      // Try to discover BLOB data length
-      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, 0, @StrLenOrInd);
-      ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not get field data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
-      // Read the data if not NULL
-      if StrLenOrInd<>SQL_NULL_DATA then
-      begin
-        CreateBlob:=true; // defer actual loading of blob data to LoadBlobIntoBuffer method
-        //WriteLn('Deferring loading of blob of length ',StrLenOrInd);
-      end;
+    if SQLExecDirect(STMTHandle, PChar(FDBMSInfo.GetLastInsertIDSQL), Length(FDBMSInfo.GetLastInsertIDSQL)) = SQL_SUCCESS then
+      if SQLFetch(STMTHandle) = SQL_SUCCESS then
+        if SQLGetData(STMTHandle, 1, SQL_C_SBIGINT, @LastInsertID, SizeOf(LargeInt), @StrLenOrInd) = SQL_SUCCESS then
+          Field.AsLargeInt := LastInsertID;
+    SQLFreeHandle(SQL_HANDLE_STMT, STMTHandle);
     end;
-    // TODO: Loading of other field types
-  else
-    raise EODBCException.CreateFmt('Tried to load field of unsupported field type %s',[Fieldtypenames[FieldDef.DataType]]);
-  end;
-  ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not get field data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
-  Result:=StrLenOrInd<>SQL_NULL_DATA; // Result indicates whether the value is non-null
-
-  //writeln(Format('Field.Size: %d; StrLenOrInd: %d',[FieldDef.Size, StrLenOrInd]));
-end;
-
-procedure TODBCConnection.LoadBlobIntoBuffer(FieldDef: TFieldDef; ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction: TSQLTransaction);
-var
-  ODBCCursor: TODBCCursor;
-  Res: SQLRETURN;
-  StrLenOrInd:SQLLEN;
-  BlobBuffer:pointer;
-  BlobBufferSize,BytesRead:SQLINTEGER;
-  BlobMemoryStream:TMemoryStream;
-begin
-  ODBCCursor:=cursor as TODBCCursor;
-  // Try to discover BLOB data length
-  //   NB MS ODBC requires that TargetValuePtr is not nil, so we supply it with a valid pointer, even though BufferLength is 0
-  StrLenOrInd:=0;
-  Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, @BlobBuffer, 0, @StrLenOrInd);
-  if Res<>SQL_NO_DATA then
-    ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not get field data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
-  // Read the data if not NULL
-  if StrLenOrInd<>SQL_NULL_DATA then
-  begin
-    // Determine size of buffer to use
-    if StrLenOrInd<>SQL_NO_TOTAL then begin
-      // Size is known on beforehand
-      // set size & alloc buffer
-      //WriteLn('Loading blob of length ',StrLenOrInd);
-      BlobBufferSize:=StrLenOrInd;
-      ABlobBuf^.BlobBuffer^.Size:=BlobBufferSize;
-      ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer, BlobBufferSize);
-      // get blob data
-      if BlobBufferSize>0 then begin
-        Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, ABlobBuf^.BlobBuffer^.Buffer, BlobBufferSize, @StrLenOrInd);
-        ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not load blob data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
-      end;
-    end else begin
-      // Size is not known on beforehand; read data in chuncks; write to a TMemoryStream (which implements O(n) writing)
-      BlobBufferSize:=DEFAULT_BLOB_BUFFER_SIZE;
-      // init BlobBuffer and BlobMemoryStream to nil pointers
-      BlobBuffer:=nil; // the buffer that will hold the chuncks of data; not to be confused with ABlobBuf^.BlobBuffer
-      BlobMemoryStream:=nil;
-      try
-        // Allocate the buffer and memorystream
-        BlobBuffer:=GetMem(BlobBufferSize);
-        BlobMemoryStream:=TMemoryStream.Create;
-        // Retrieve data in parts
-        repeat
-          Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, BlobBuffer, BlobBufferSize, @StrLenOrInd);
-          ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not load (partial) blob data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
-          // Append data in buffer to memorystream
-          if (StrLenOrInd=SQL_NO_TOTAL) or (StrLenOrInd>BlobBufferSize) then
-            BytesRead:=BlobBufferSize
-          else
-            BytesRead:=StrLenOrInd;
-          BlobMemoryStream.Write(BlobBuffer^, BytesRead);
-        until Res=SQL_SUCCESS;
-        // Copy memory stream data to ABlobBuf^.BlobBuffer
-        BlobBufferSize:=BlobMemoryStream.Size; // actual blob size
-        //   alloc ABlobBuf^.BlobBuffer
-        ABlobBuf^.BlobBuffer^.Size:=BlobBufferSize;
-        ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer, BlobBufferSize);
-        //   read memory stream data into ABlobBuf^.BlobBuffer
-        BlobMemoryStream.Position:=0;
-        BlobMemoryStream.Read(ABlobBuf^.BlobBuffer^.Buffer^, BlobBufferSize);
-      finally
-        // free buffer and memory stream
-        BlobMemoryStream.Free;
-        if BlobBuffer<>nil then
-          Freemem(BlobBuffer,BlobBufferSize);
-      end;
-    end;
-  end;
-end;
-
-procedure TODBCConnection.FreeFldBuffers(cursor: TSQLCursor);
-var
-  ODBCCursor:TODBCCursor;
-begin
-  ODBCCursor:=cursor as TODBCCursor;
-
-  if ODBCCursor.FSTMTHandle <> SQL_NULL_HSTMT then
-    ODBCCheckResult(
-      SQLFreeStmt(ODBCCursor.FSTMTHandle, SQL_CLOSE),
-      SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not close ODBC statement cursor.'
-    );
 end;
 
 procedure TODBCConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);
@@ -1202,6 +1041,206 @@ begin
   end;
 end;
 
+function TODBCConnection.Fetch(cursor: TSQLCursor): boolean;
+var
+  ODBCCursor:TODBCCursor;
+  Res:SQLRETURN;
+begin
+  ODBCCursor:=cursor as TODBCCursor;
+
+  // fetch new row
+  Res:=SQLFetch(ODBCCursor.FSTMTHandle);
+  if Res<>SQL_NO_DATA then
+    ODBCCheckResult(Res,SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not fetch new row from result set.');
+
+  // result is true iff a new row was available
+  Result:=Res<>SQL_NO_DATA;
+end;
+
+const
+  DEFAULT_BLOB_BUFFER_SIZE = 1024;
+
+function TODBCConnection.LoadField(cursor: TSQLCursor; FieldDef: TFieldDef; buffer: pointer; out CreateBlob : boolean): boolean;
+var
+  ODBCCursor:TODBCCursor;
+  StrLenOrInd:SQLLEN;
+  ODBCDateStruct:SQL_DATE_STRUCT;
+  ODBCTimeStruct:SQL_TIME_STRUCT;
+  ODBCTimeStampStruct:SQL_TIMESTAMP_STRUCT;
+  DateTime:TDateTime;
+  Res:SQLRETURN;
+begin
+  CreateBlob := False;
+  ODBCCursor:=cursor as TODBCCursor;
+
+  // load the field using SQLGetData
+  // Note: optionally we can implement the use of SQLBindCol later for even more speed
+  // TODO: finish this
+  case FieldDef.DataType of
+    ftWideString,ftFixedWideChar: // mapped to TWideStringField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_WCHAR, buffer, FieldDef.Size+sizeof(WideChar), @StrLenOrInd); //buffer must contain space for the null-termination character
+    ftGuid, ftFixedChar,ftString: // are mapped to a TStringField (including TGuidField)
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_CHAR, buffer, FieldDef.Size+1, @StrLenOrInd);
+    ftSmallint:           // mapped to TSmallintField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_SSHORT, buffer, SizeOf(Smallint), @StrLenOrInd);
+    ftInteger,ftAutoInc:  // mapped to TLongintField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_SLONG, buffer, SizeOf(Longint), @StrLenOrInd);
+    ftWord:               // mapped to TWordField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_USHORT, buffer, SizeOf(Word), @StrLenOrInd);
+    ftLargeint:           // mapped to TLargeintField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_SBIGINT, buffer, SizeOf(Largeint), @StrLenOrInd);
+    ftFloat,ftCurrency:   // mapped to TFloatField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_DOUBLE, buffer, SizeOf(Double), @StrLenOrInd);
+    ftTime:               // mapped to TTimeField
+    begin
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_TIME, @ODBCTimeStruct, SizeOf(SQL_TIME_STRUCT), @StrLenOrInd);
+      if StrLenOrInd<>SQL_NULL_DATA then
+      begin
+        DateTime:=TimeStructToDateTime(@ODBCTimeStruct);
+        Move(DateTime, buffer^, SizeOf(TDateTime));
+      end;
+    end;
+    ftDate:               // mapped to TDateField
+    begin
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_DATE, @ODBCDateStruct, SizeOf(SQL_DATE_STRUCT), @StrLenOrInd);
+      if StrLenOrInd<>SQL_NULL_DATA then
+      begin
+        DateTime:=DateStructToDateTime(@ODBCDateStruct);
+        Move(DateTime, buffer^, SizeOf(TDateTime));
+      end;
+    end;
+    ftDateTime:           // mapped to TDateTimeField
+    begin
+      // Seems like not all ODBC-drivers (mysql on Linux) set the fractional part. Initialize
+      // it's value to avoid 'random' data.
+      ODBCTimeStampStruct.Fraction:=0;
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_TIMESTAMP, @ODBCTimeStampStruct, SizeOf(SQL_TIMESTAMP_STRUCT), @StrLenOrInd);
+      if StrLenOrInd<>SQL_NULL_DATA then
+      begin
+        DateTime:=TimeStampStructToDateTime(@ODBCTimeStampStruct);
+        Move(DateTime, buffer^, SizeOf(TDateTime));
+      end;
+    end;
+    ftBoolean:            // mapped to TBooleanField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BIT, buffer, SizeOf(Wordbool), @StrLenOrInd);
+    ftBytes:              // mapped to TBytesField
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd);
+    ftVarBytes:           // mapped to TVarBytesField
+    begin
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer+SizeOf(Word), FieldDef.Size, @StrLenOrInd);
+      if StrLenOrInd < 0 then
+        PWord(buffer)^ := 0
+      else
+        PWord(buffer)^ := StrLenOrInd;
+    end;
+    ftWideMemo,
+    ftBlob, ftMemo:       // BLOBs
+    begin
+      //Writeln('BLOB');
+      // Try to discover BLOB data length
+      Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, 0, @StrLenOrInd);
+      ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not get field data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
+      // Read the data if not NULL
+      if StrLenOrInd<>SQL_NULL_DATA then
+      begin
+        CreateBlob:=true; // defer actual loading of blob data to LoadBlobIntoBuffer method
+        //WriteLn('Deferring loading of blob of length ',StrLenOrInd);
+      end;
+    end;
+    // TODO: Loading of other field types
+  else
+    raise EODBCException.CreateFmt('Tried to load field of unsupported field type %s',[Fieldtypenames[FieldDef.DataType]]);
+  end;
+  ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not get field data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
+  Result:=StrLenOrInd<>SQL_NULL_DATA; // Result indicates whether the value is non-null
+
+  //writeln(Format('Field.Size: %d; StrLenOrInd: %d',[FieldDef.Size, StrLenOrInd]));
+end;
+
+procedure TODBCConnection.LoadBlobIntoBuffer(FieldDef: TFieldDef; ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction: TSQLTransaction);
+var
+  ODBCCursor: TODBCCursor;
+  Res: SQLRETURN;
+  StrLenOrInd:SQLLEN;
+  BlobBuffer:pointer;
+  BlobBufferSize,BytesRead:SQLINTEGER;
+  BlobMemoryStream:TMemoryStream;
+begin
+  ODBCCursor:=cursor as TODBCCursor;
+  // Try to discover BLOB data length
+  //   NB MS ODBC requires that TargetValuePtr is not nil, so we supply it with a valid pointer, even though BufferLength is 0
+  StrLenOrInd:=0;
+  Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, @BlobBuffer, 0, @StrLenOrInd);
+  if Res<>SQL_NO_DATA then
+    ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not get field data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
+  // Read the data if not NULL
+  if StrLenOrInd<>SQL_NULL_DATA then
+  begin
+    // Determine size of buffer to use
+    if StrLenOrInd<>SQL_NO_TOTAL then begin
+      // Size is known on beforehand
+      // set size & alloc buffer
+      //WriteLn('Loading blob of length ',StrLenOrInd);
+      BlobBufferSize:=StrLenOrInd;
+      ABlobBuf^.BlobBuffer^.Size:=BlobBufferSize;
+      ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer, BlobBufferSize);
+      // get blob data
+      if BlobBufferSize>0 then begin
+        Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, ABlobBuf^.BlobBuffer^.Buffer, BlobBufferSize, @StrLenOrInd);
+        ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not load blob data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
+      end;
+    end else begin
+      // Size is not known on beforehand; read data in chuncks; write to a TMemoryStream (which implements O(n) writing)
+      BlobBufferSize:=DEFAULT_BLOB_BUFFER_SIZE;
+      // init BlobBuffer and BlobMemoryStream to nil pointers
+      BlobBuffer:=nil; // the buffer that will hold the chuncks of data; not to be confused with ABlobBuf^.BlobBuffer
+      BlobMemoryStream:=nil;
+      try
+        // Allocate the buffer and memorystream
+        BlobBuffer:=GetMem(BlobBufferSize);
+        BlobMemoryStream:=TMemoryStream.Create;
+        // Retrieve data in parts
+        repeat
+          Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, BlobBuffer, BlobBufferSize, @StrLenOrInd);
+          ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not load (partial) blob data for field "%s" (index %d).',[FieldDef.Name, FieldDef.Index+1]);
+          // Append data in buffer to memorystream
+          if (StrLenOrInd=SQL_NO_TOTAL) or (StrLenOrInd>BlobBufferSize) then
+            BytesRead:=BlobBufferSize
+          else
+            BytesRead:=StrLenOrInd;
+          BlobMemoryStream.Write(BlobBuffer^, BytesRead);
+        until Res=SQL_SUCCESS;
+        // Copy memory stream data to ABlobBuf^.BlobBuffer
+        BlobBufferSize:=BlobMemoryStream.Size; // actual blob size
+        //   alloc ABlobBuf^.BlobBuffer
+        ABlobBuf^.BlobBuffer^.Size:=BlobBufferSize;
+        ReAllocMem(ABlobBuf^.BlobBuffer^.Buffer, BlobBufferSize);
+        //   read memory stream data into ABlobBuf^.BlobBuffer
+        BlobMemoryStream.Position:=0;
+        BlobMemoryStream.Read(ABlobBuf^.BlobBuffer^.Buffer^, BlobBufferSize);
+      finally
+        // free buffer and memory stream
+        BlobMemoryStream.Free;
+        if BlobBuffer<>nil then
+          Freemem(BlobBuffer,BlobBufferSize);
+      end;
+    end;
+  end;
+end;
+
+procedure TODBCConnection.FreeFldBuffers(cursor: TSQLCursor);
+var
+  ODBCCursor:TODBCCursor;
+begin
+  ODBCCursor:=cursor as TODBCCursor;
+
+  if ODBCCursor.FSTMTHandle <> SQL_NULL_HSTMT then
+    ODBCCheckResult(
+      SQLFreeStmt(ODBCCursor.FSTMTHandle, SQL_CLOSE),
+      SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not close ODBC statement cursor.'
+    );
+end;
+
 procedure TODBCConnection.UpdateIndexDefs(IndexDefs: TIndexDefs; TableName: string);
 var
   Len: integer;

+ 0 - 2
packages/fcl-db/tests/testsqldb.pas

@@ -566,8 +566,6 @@ var datatype: string;
 begin
   with SQLDBConnector do
     begin
-    if not (sqLastInsertID in Connection.ConnOptions) then
-      Ignore(STestNotApplicable);
     case SQLServerType of
       ssMySQL:
         datatype := 'integer auto_increment';