mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-14 06:49:23 +02:00
fcl-db: odbc: implements RefreshLastInsertID for autoincrementing columns.
Bug #16076 git-svn-id: trunk@29751 -
This commit is contained in:
parent
a861bbc5ae
commit
2a2078e7f7
@ -56,10 +56,16 @@ type
|
|||||||
|
|
||||||
TODBCConnection = class(TSQLConnection)
|
TODBCConnection = class(TSQLConnection)
|
||||||
private
|
private
|
||||||
|
type
|
||||||
|
TDBMSInfo = record
|
||||||
|
GetLastInsertIDSQL: string; // SQL statement for get last insert value for autoincrement column
|
||||||
|
end;
|
||||||
|
var
|
||||||
FDriver: string;
|
FDriver: string;
|
||||||
FEnvironment:TODBCEnvironment;
|
FEnvironment:TODBCEnvironment;
|
||||||
FDBCHandle:SQLHDBC; // ODBC Connection Handle
|
FDBCHandle:SQLHDBC; // ODBC Connection Handle
|
||||||
FFileDSN: string;
|
FFileDSN: string;
|
||||||
|
FDBMSInfo: TDBMSInfo;
|
||||||
|
|
||||||
procedure SetParameters(ODBCCursor:TODBCCursor; AParams:TParams);
|
procedure SetParameters(ODBCCursor:TODBCCursor; AParams:TParams);
|
||||||
procedure FreeParamBuffers(ODBCCursor:TODBCCursor);
|
procedure FreeParamBuffers(ODBCCursor:TODBCCursor);
|
||||||
@ -87,6 +93,7 @@ type
|
|||||||
// - Statement execution
|
// - Statement execution
|
||||||
procedure Execute(cursor:TSQLCursor; ATransaction:TSQLTransaction; AParams:TParams); override;
|
procedure Execute(cursor:TSQLCursor; ATransaction:TSQLTransaction; AParams:TParams); override;
|
||||||
function RowsAffected(cursor: TSQLCursor): TRowsCount; override;
|
function RowsAffected(cursor: TSQLCursor): TRowsCount; override;
|
||||||
|
function RefreshLastInsertID(Query : TCustomSQLQuery; Field : TField): boolean; override;
|
||||||
// - Result retrieving
|
// - Result retrieving
|
||||||
procedure AddFieldDefs(cursor:TSQLCursor; FieldDefs:TFieldDefs); override;
|
procedure AddFieldDefs(cursor:TSQLCursor; FieldDefs:TFieldDefs); override;
|
||||||
function Fetch(cursor:TSQLCursor):boolean; override;
|
function Fetch(cursor:TSQLCursor):boolean; override;
|
||||||
@ -545,6 +552,7 @@ var
|
|||||||
ConnectionString:string;
|
ConnectionString:string;
|
||||||
OutConnectionString:string;
|
OutConnectionString:string;
|
||||||
ActualLength:SQLSMALLINT;
|
ActualLength:SQLSMALLINT;
|
||||||
|
DBMS_NAME: array[0..20] of AnsiChar;
|
||||||
begin
|
begin
|
||||||
// Do not call the inherited method as it checks for a non-empty DatabaseName, and we don't even use DatabaseName!
|
// Do not call the inherited method as it checks for a non-empty DatabaseName, and we don't even use DatabaseName!
|
||||||
// inherited DoInternalConnect;
|
// inherited DoInternalConnect;
|
||||||
@ -589,9 +597,24 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// commented out as the OutConnectionString is not used further at the moment
|
// set DBMS specific options
|
||||||
// if ActualLength<BufferLength-1 then
|
if SQLGetInfo(FDBCHandle, SQL_DBMS_NAME, @DBMS_NAME, sizeof(DBMS_NAME), @ActualLength) = SQL_SUCCESS then
|
||||||
// SetLength(OutConnectionString,ActualLength); // fix completed connection string length
|
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)
|
// set connection attributes (none yet)
|
||||||
end;
|
end;
|
||||||
@ -779,204 +802,20 @@ begin
|
|||||||
Result:=-1;
|
Result:=-1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TODBCConnection.Fetch(cursor: TSQLCursor): boolean;
|
function TODBCConnection.RefreshLastInsertID(Query : TCustomSQLQuery; Field : TField): boolean;
|
||||||
var
|
var
|
||||||
ODBCCursor:TODBCCursor;
|
STMTHandle: SQLHSTMT;
|
||||||
Res:SQLRETURN;
|
StrLenOrInd: SQLLEN;
|
||||||
|
LastInsertID: LargeInt;
|
||||||
begin
|
begin
|
||||||
ODBCCursor:=cursor as TODBCCursor;
|
if SQLAllocHandle(SQL_HANDLE_STMT, FDBCHandle, STMTHandle) = SQL_SUCCESS then
|
||||||
|
|
||||||
// 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
|
begin
|
||||||
Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_TYPE_TIME, @ODBCTimeStruct, SizeOf(SQL_TIME_STRUCT), @StrLenOrInd);
|
if SQLExecDirect(STMTHandle, PChar(FDBMSInfo.GetLastInsertIDSQL), Length(FDBMSInfo.GetLastInsertIDSQL)) = SQL_SUCCESS then
|
||||||
if StrLenOrInd<>SQL_NULL_DATA then
|
if SQLFetch(STMTHandle) = SQL_SUCCESS then
|
||||||
begin
|
if SQLGetData(STMTHandle, 1, SQL_C_SBIGINT, @LastInsertID, SizeOf(LargeInt), @StrLenOrInd) = SQL_SUCCESS then
|
||||||
DateTime:=TimeStructToDateTime(@ODBCTimeStruct);
|
Field.AsLargeInt := LastInsertID;
|
||||||
Move(DateTime, buffer^, SizeOf(TDateTime));
|
SQLFreeHandle(SQL_HANDLE_STMT, STMTHandle);
|
||||||
end;
|
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;
|
end;
|
||||||
|
|
||||||
procedure TODBCConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);
|
procedure TODBCConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);
|
||||||
@ -1202,6 +1041,206 @@ begin
|
|||||||
end;
|
end;
|
||||||
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);
|
procedure TODBCConnection.UpdateIndexDefs(IndexDefs: TIndexDefs; TableName: string);
|
||||||
var
|
var
|
||||||
Len: integer;
|
Len: integer;
|
||||||
|
@ -566,8 +566,6 @@ var datatype: string;
|
|||||||
begin
|
begin
|
||||||
with SQLDBConnector do
|
with SQLDBConnector do
|
||||||
begin
|
begin
|
||||||
if not (sqLastInsertID in Connection.ConnOptions) then
|
|
||||||
Ignore(STestNotApplicable);
|
|
||||||
case SQLServerType of
|
case SQLServerType of
|
||||||
ssMySQL:
|
ssMySQL:
|
||||||
datatype := 'integer auto_increment';
|
datatype := 'integer auto_increment';
|
||||||
|
Loading…
Reference in New Issue
Block a user