+ Patch from Bram Kuijvenhoven to implement blob field and more verbose errors

git-svn-id: trunk@3458 -
This commit is contained in:
michael 2006-05-08 20:22:20 +00:00
parent ab1660c9c2
commit c8fb11776d
2 changed files with 153 additions and 17 deletions

View File

@ -31,6 +31,7 @@ type
FQuery:string; // last prepared query, with :ParamName converted to ? FQuery:string; // last prepared query, with :ParamName converted to ?
FParamIndex:TParamBinding; // maps the i-th parameter in the query to the TParams passed to PrepareStatement FParamIndex:TParamBinding; // maps the i-th parameter in the query to the TParams passed to PrepareStatement
FParamBuf:array of pointer; // buffers that can be used to bind the i-th parameter in the query FParamBuf:array of pointer; // buffers that can be used to bind the i-th parameter in the query
FBlobStreams:TList; // list of Blob TMemoryStreams stored in field buffers (we need this currently as we can't hook into the freeing of TBufDataset buffers)
public public
constructor Create(Connection:TODBCConnection); constructor Create(Connection:TODBCConnection);
destructor Destroy; override; destructor Destroy; override;
@ -126,7 +127,7 @@ type
implementation implementation
uses uses
Math; // for the Min proc Math, DBConst;
const const
DefaultEnvironment:TODBCEnvironment = nil; DefaultEnvironment:TODBCEnvironment = nil;
@ -351,7 +352,8 @@ var
OutConnectionString:string; OutConnectionString:string;
ActualLength:SQLSMALLINT; ActualLength:SQLSMALLINT;
begin begin
inherited DoInternalConnect; // Do not call the inherited method as it checks for a non-empty DatabaseName, and we don't even use DatabaseName!
// inherited DoInternalConnect;
// make sure we have an environment // make sure we have an environment
if not Assigned(FEnvironment) then if not Assigned(FEnvironment) then
@ -382,7 +384,7 @@ begin
SQL_HANDLE_DBC,FDBCHandle,Format('Could not connect with connection string "%s".',[ConnectionString]) SQL_HANDLE_DBC,FDBCHandle,Format('Could not connect with connection string "%s".',[ConnectionString])
); );
// commented out as the OutConenctionString is not used further at the moment // commented out as the OutConnectionString is not used further at the moment
// if ActualLength<BufferLength-1 then // if ActualLength<BufferLength-1 then
// SetLength(OutConnectionString,ActualLength); // fix completed connection string length // SetLength(OutConnectionString,ActualLength); // fix completed connection string length
@ -520,6 +522,8 @@ begin
end; end;
function TODBCConnection.LoadField(cursor: TSQLCursor; FieldDef: TFieldDef; buffer: pointer): boolean; function TODBCConnection.LoadField(cursor: TSQLCursor; FieldDef: TFieldDef; buffer: pointer): boolean;
const
DEFAULT_BLOB_BUFFER_SIZE = 1024;
var var
ODBCCursor:TODBCCursor; ODBCCursor:TODBCCursor;
StrLenOrInd:SQLINTEGER; StrLenOrInd:SQLINTEGER;
@ -527,6 +531,9 @@ var
ODBCTimeStruct:SQL_TIME_STRUCT; ODBCTimeStruct:SQL_TIME_STRUCT;
ODBCTimeStampStruct:SQL_TIMESTAMP_STRUCT; ODBCTimeStampStruct:SQL_TIMESTAMP_STRUCT;
DateTime:TDateTime; DateTime:TDateTime;
BlobBuffer:pointer;
BlobBufferSize,BytesRead:SQLINTEGER;
BlobMemoryStream:TMemoryStream;
Res:SQLRETURN; Res:SQLRETURN;
begin begin
ODBCCursor:=cursor as TODBCCursor; ODBCCursor:=cursor as TODBCCursor;
@ -578,27 +585,86 @@ begin
Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd); Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd);
ftVarBytes: // mapped to TVarBytesField ftVarBytes: // mapped to TVarBytesField
Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd); Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, buffer, FieldDef.Size, @StrLenOrInd);
ftBlob, ftMemo: // BLOBs
begin
// 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, Format('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
BlobBufferSize:=StrLenOrInd
else
BlobBufferSize:=DEFAULT_BLOB_BUFFER_SIZE;
try
// Allocate the buffer and memorystream
BlobBuffer:=GetMem(BlobBufferSize);
BlobMemoryStream:=TMemoryStream.Create;
if BlobBufferSize>0 then
begin
// Retrieve data in parts (or effectively in one part if StrLenOrInd<>SQL_NO_TOTAL above)
repeat
Res:=SQLGetData(ODBCCursor.FSTMTHandle, FieldDef.Index+1, SQL_C_BINARY, BlobBuffer, BlobBufferSize, @StrLenOrInd);
ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get field 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;
end;
// Store memorystream pointer in Field buffer and in the cursor's FBlobStreams list
TObject(buffer^):=BlobMemoryStream;
ODBCCursor.FBlobStreams.Add(BlobMemoryStream);
// Set BlobMemoryStream to nil, so it won't get freed in the finally block below
BlobMemoryStream:=nil;
finally
BlobMemoryStream.Free;
Freemem(BlobBuffer,BlobBufferSize);
end;
end;
end;
// TODO: Loading of other field types // TODO: Loading of other field types
else else
raise EODBCException.CreateFmt('Tried to load field of unsupported field type %s',[Fieldtypenames[FieldDef.DataType]]); raise EODBCException.CreateFmt('Tried to load field of unsupported field type %s',[Fieldtypenames[FieldDef.DataType]]);
end; end;
ODBCCheckResult(Res,SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get field data for field ''%s'' (index %d).',[FieldDef.Name, FieldDef.Index+1])); ODBCCheckResult(Res, SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('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 Result:=StrLenOrInd<>SQL_NULL_DATA; // Result indicates whether the value is non-null
// writeln(Format('Field.Size: %d; StrLenOrInd: %d',[FieldDef.Size, StrLenOrInd])); // writeln(Format('Field.Size: %d; StrLenOrInd: %d',[FieldDef.Size, StrLenOrInd]));
end; end;
function TODBCConnection.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; function TODBCConnection.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
var
ODBCCursor: TODBCCursor;
BlobMemoryStream, BlobMemoryStreamCopy: TMemoryStream;
begin begin
// TODO: implement TODBCConnection.CreateBlobStream // TODO: implement TODBCConnection.CreateBlobStream
Result:=nil; if Mode=bmRead then
begin
Field.GetData(@BlobMemoryStream);
BlobMemoryStreamCopy:=TMemoryStream.Create;
BlobMemoryStreamCopy.LoadFromStream(BlobMemoryStream);
Result:=BlobMemoryStreamCopy;
end
else
Result:=nil;
end; end;
procedure TODBCConnection.FreeFldBuffers(cursor: TSQLCursor); procedure TODBCConnection.FreeFldBuffers(cursor: TSQLCursor);
var var
ODBCCursor:TODBCCursor; ODBCCursor:TODBCCursor;
i: integer;
begin begin
ODBCCursor:=cursor as TODBCCursor; ODBCCursor:=cursor as TODBCCursor;
// Free TMemoryStreams in cursor.FBlobStreams and clear it
for i:=0 to ODBCCursor.FBlobStreams.Count-1 do
TObject(ODBCCursor.FBlobStreams[i]).Free;
ODBCCursor.FBlobStreams.Clear;
ODBCCheckResult( ODBCCheckResult(
SQLFreeStmt(ODBCCursor.FSTMTHandle, SQL_CLOSE), SQLFreeStmt(ODBCCursor.FSTMTHandle, SQL_CLOSE),
@ -608,14 +674,15 @@ end;
procedure TODBCConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs); procedure TODBCConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);
const const
ColNameDefaultLength = 40; // should be > 0, because an ansistring of length 0 is a nil pointer instead of a pointer to a #0 ColNameDefaultLength = 40; // should be > 0, because an ansistring of length 0 is a nil pointer instead of a pointer to a #0
TypeNameDefaultLength = 80; // idem
var var
ODBCCursor:TODBCCursor; ODBCCursor:TODBCCursor;
ColumnCount:SQLSMALLINT; ColumnCount:SQLSMALLINT;
i:integer; i:integer;
ColNameLength,DataType,DecimalDigits,Nullable:SQLSMALLINT; ColNameLength,TypeNameLength,DataType,DecimalDigits,Nullable:SQLSMALLINT;
ColumnSize:SQLUINTEGER; ColumnSize:SQLUINTEGER;
ColName:string; ColName,TypeName:string;
FieldType:TFieldType; FieldType:TFieldType;
FieldSize:word; FieldSize:word;
begin begin
@ -668,10 +735,10 @@ begin
case DataType of case DataType of
SQL_CHAR: begin FieldType:=ftFixedChar; FieldSize:=ColumnSize+1; end; SQL_CHAR: begin FieldType:=ftFixedChar; FieldSize:=ColumnSize+1; end;
SQL_VARCHAR: begin FieldType:=ftString; FieldSize:=ColumnSize+1; end; SQL_VARCHAR: begin FieldType:=ftString; FieldSize:=ColumnSize+1; end;
SQL_LONGVARCHAR: begin FieldType:=ftString; FieldSize:=ColumnSize+1; end; // no fixed maximum length; make ftMemo when blobs are supported SQL_LONGVARCHAR: begin FieldType:=ftMemo; FieldSize:=sizeof(pointer); end; // is a blob
SQL_WCHAR: begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end; SQL_WCHAR: begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end;
SQL_WVARCHAR: begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end; SQL_WVARCHAR: begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end;
SQL_WLONGVARCHAR: begin FieldType:=ftWideString; FieldSize:=ColumnSize+1; end; // no fixed maximum length; make ftMemo when blobs are supported SQL_WLONGVARCHAR: begin FieldType:=ftMemo; FieldSize:=sizeof(pointer); end; // is a blob
SQL_DECIMAL: begin FieldType:=ftFloat; FieldSize:=0; end; SQL_DECIMAL: begin FieldType:=ftFloat; FieldSize:=0; end;
SQL_NUMERIC: begin FieldType:=ftFloat; FieldSize:=0; end; SQL_NUMERIC: begin FieldType:=ftFloat; FieldSize:=0; end;
SQL_SMALLINT: begin FieldType:=ftSmallint; FieldSize:=0; end; SQL_SMALLINT: begin FieldType:=ftSmallint; FieldSize:=0; end;
@ -684,12 +751,12 @@ begin
SQL_BIGINT: begin FieldType:=ftLargeint; FieldSize:=0; end; SQL_BIGINT: begin FieldType:=ftLargeint; FieldSize:=0; end;
SQL_BINARY: begin FieldType:=ftBytes; FieldSize:=ColumnSize; end; SQL_BINARY: begin FieldType:=ftBytes; FieldSize:=ColumnSize; end;
SQL_VARBINARY: begin FieldType:=ftVarBytes; FieldSize:=ColumnSize; end; SQL_VARBINARY: begin FieldType:=ftVarBytes; FieldSize:=ColumnSize; end;
SQL_LONGVARBINARY: begin FieldType:=ftBlob; FieldSize:=ColumnSize; end; SQL_LONGVARBINARY: begin FieldType:=ftBlob; FieldSize:=sizeof(pointer); end; // is a blob
SQL_TYPE_DATE: begin FieldType:=ftDate; FieldSize:=0; end; SQL_TYPE_DATE: begin FieldType:=ftDate; FieldSize:=0; end;
SQL_TYPE_TIME: begin FieldType:=ftTime; FieldSize:=0; end; SQL_TYPE_TIME: begin FieldType:=ftTime; FieldSize:=0; end;
SQL_TYPE_TIMESTAMP:begin FieldType:=ftDateTime; FieldSize:=0; end; SQL_TYPE_TIMESTAMP:begin FieldType:=ftDateTime; FieldSize:=0; end;
{ SQL_TYPE_UTCDATETIME:FieldType:=ftUnknown;} { SQL_TYPE_UTCDATETIME:FieldType:=ftUnknown;}
{ SQL_TYPE_UTCTIME: FieldType:=ftUnknown; } { SQL_TYPE_UTCTIME: FieldType:=ftUnknown;}
{ SQL_INTERVAL_MONTH: FieldType:=ftUnknown;} { SQL_INTERVAL_MONTH: FieldType:=ftUnknown;}
{ SQL_INTERVAL_YEAR: FieldType:=ftUnknown;} { SQL_INTERVAL_YEAR: FieldType:=ftUnknown;}
{ SQL_INTERVAL_YEAR_TO_MONTH: FieldType:=ftUnknown;} { SQL_INTERVAL_YEAR_TO_MONTH: FieldType:=ftUnknown;}
@ -707,12 +774,48 @@ begin
else else
begin FieldType:=ftUnknown; FieldSize:=ColumnSize; end begin FieldType:=ftUnknown; FieldSize:=ColumnSize; end
end; end;
if (FieldType in [ftString,ftFixedChar]) and // field types mapped to TStringField if (FieldType in [ftString,ftFixedChar]) and // field types mapped to TStringField
(FieldSize >= dsMaxStringSize) then (FieldSize >= dsMaxStringSize) then
begin begin
FieldSize:=dsMaxStringSize-1; FieldSize:=dsMaxStringSize-1;
end; end;
if FieldType=ftUnknown then // if unknown field type encountered, try finding more specific information about the ODBC SQL DataType
begin
SetLength(TypeName,TypeNameDefaultLength); // also garantuees uniqueness
ODBCCheckResult(
SQLColAttribute(ODBCCursor.FSTMTHandle, // statement handle
i, // column number
SQL_DESC_TYPE_NAME, // FieldIdentifier indicating the datasource dependent data type name (useful for diagnostics)
@(TypeName[1]), // default buffer
TypeNameDefaultLength+1, // and its length; we include the #0 terminating any ansistring of Length > 0 in the buffer
@TypeNameLength, // actual type name length
nil // no need for a pointer to return a numeric attribute at
),
SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get datasource dependent type name for column %s.',[ColName])
);
// truncate buffer or make buffer long enough for entire column name (note: the call is the same for both cases!)
SetLength(TypeName,TypeNameLength);
// check whether entire column name was returned
if TypeNameLength>TypeNameDefaultLength then
begin
// request column name with buffer that is long enough
ODBCCheckResult(
SQLColAttribute(ODBCCursor.FSTMTHandle, // statement handle
i, // column number
SQL_DESC_TYPE_NAME, // FieldIdentifier indicating the datasource dependent data type name (useful for diagnostics)
@(TypeName[1]), // buffer
TypeNameLength+1, // buffer size
@TypeNameLength, // actual length
nil), // no need for a pointer to return a numeric attribute at
SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, Format('Could not get datasource dependent type name for column %s.',[ColName])
);
end;
DatabaseErrorFmt('Column %s has an unknown or unsupported column type. Datasource dependent type name: %s. ODBC SQL data type code: %d.', [ColName, TypeName, DataType]);
end;
// add FieldDef // add FieldDef
TFieldDef.Create(FieldDefs, ColName, FieldType, FieldSize, False, i); TFieldDef.Create(FieldDefs, ColName, FieldType, FieldSize, False, i);
@ -773,6 +876,9 @@ begin
SQLAllocHandle(SQL_HANDLE_STMT, Connection.FDBCHandle, FSTMTHandle), SQLAllocHandle(SQL_HANDLE_STMT, Connection.FDBCHandle, FSTMTHandle),
SQL_HANDLE_DBC, Connection.FDBCHandle, 'Could not allocate ODBC Statement handle.' SQL_HANDLE_DBC, Connection.FDBCHandle, 'Could not allocate ODBC Statement handle.'
); );
// allocate FBlobStreams
FBlobStreams:=TList.Create;
end; end;
destructor TODBCCursor.Destroy; destructor TODBCCursor.Destroy;
@ -780,6 +886,8 @@ var
Res:SQLRETURN; Res:SQLRETURN;
begin begin
inherited Destroy; inherited Destroy;
FBlobStreams.Free;
if FSTMTHandle<>SQL_INVALID_HANDLE then if FSTMTHandle<>SQL_INVALID_HANDLE then
begin begin

View File

@ -964,10 +964,38 @@ const
{$ifdef ODBCVER3} {$ifdef ODBCVER3}
SQL_COLUMN_DRIVER_START = 1000; SQL_COLUMN_DRIVER_START = 1000;
{$endif} { ODBCVER >= 0x0300 } {$endif} { ODBCVER >= 0x0300 }
SQL_DESC_AUTO_UNIQUE_VALUE = SQL_COLUMN_AUTO_INCREMENT;
SQL_DESC_BASE_COLUMN_NAME = 22; { SQLColAttribute defines }
SQL_DESC_BASE_TABLE_NAME = 23; {$ifdef ODBCVER3}
SQL_DESC_TABLE_NAME = SQL_COLUMN_TABLE_NAME; SQL_DESC_ARRAY_SIZE = 20;
SQL_DESC_ARRAY_STATUS_PTR = 21;
SQL_DESC_AUTO_UNIQUE_VALUE = SQL_COLUMN_AUTO_INCREMENT;
SQL_DESC_BASE_COLUMN_NAME = 22;
SQL_DESC_BASE_TABLE_NAME = 23;
SQL_DESC_BIND_OFFSET_PTR = 24;
SQL_DESC_BIND_TYPE = 25;
SQL_DESC_CASE_SENSITIVE = SQL_COLUMN_CASE_SENSITIVE;
SQL_DESC_CATALOG_NAME = SQL_COLUMN_QUALIFIER_NAME;
SQL_DESC_CONCISE_TYPE = SQL_COLUMN_TYPE;
SQL_DESC_DATETIME_INTERVAL_PRECISION = 26;
SQL_DESC_DISPLAY_SIZE = SQL_COLUMN_DISPLAY_SIZE;
SQL_DESC_FIXED_PREC_SCALE = SQL_COLUMN_MONEY;
SQL_DESC_LABEL = SQL_COLUMN_LABEL;
SQL_DESC_LITERAL_PREFIX = 27;
SQL_DESC_LITERAL_SUFFIX = 28;
SQL_DESC_LOCAL_TYPE_NAME = 29;
SQL_DESC_MAXIMUM_SCALE = 30;
SQL_DESC_MINIMUM_SCALE = 31;
SQL_DESC_NUM_PREC_RADIX = 32;
SQL_DESC_PARAMETER_TYPE = 33;
SQL_DESC_ROWS_PROCESSED_PTR = 34;
SQL_DESC_SCHEMA_NAME = SQL_COLUMN_OWNER_NAME;
SQL_DESC_SEARCHABLE = SQL_COLUMN_SEARCHABLE;
SQL_DESC_TYPE_NAME = SQL_COLUMN_TYPE_NAME;
SQL_DESC_TABLE_NAME = SQL_COLUMN_TABLE_NAME;
SQL_DESC_UNSIGNED = SQL_COLUMN_UNSIGNED;
SQL_DESC_UPDATABLE = SQL_COLUMN_UPDATABLE;
{$endif}
//* SQLEndTran() options */ //* SQLEndTran() options */
SQL_COMMIT = 0; SQL_COMMIT = 0;