* Patch from Luiz Americo

- fixes bug #7925
  - optimization for ApplyUpdates
  - clean InitFieldDefs implementation

git-svn-id: trunk@5683 -
This commit is contained in:
michael 2006-12-22 18:22:08 +00:00
parent f3f8a76559
commit a166a31b61
3 changed files with 172 additions and 56 deletions

View File

@ -57,6 +57,8 @@ type
end;
TSqliteCallback = function (UserData:Pointer; Columns:longint; Values:PPchar; ColumnNames:PPchar):longint;cdecl;
TGetSqlStrFunction = function (APChar: PChar): String;
{ TCustomSqliteDataset }
@ -104,6 +106,7 @@ type
FBeginItem: PDataRecord;
FEndItem: PDataRecord;
FCacheItem: PDataRecord;
FGetSqlStr: array of TGetSqlStrFunction;
function SqliteExec(AHandle: Pointer; Sql:PChar):Integer;virtual; abstract;
procedure InternalCloseHandle;virtual;abstract;
function InternalGetHandle: Pointer; virtual; abstract;
@ -229,6 +232,10 @@ type
property OnEditError;
end;
function Num2SqlStr(APChar: PChar): String;
function Char2SqlStr(APChar: PChar): String;
implementation
uses
@ -237,6 +244,30 @@ uses
const
SQLITE_OK = 0;//sqlite2.x.x and sqlite3.x.x defines this equal
function Num2SqlStr(APChar: PChar): String;
begin
if APChar = nil then
begin
Result:='NULL';
Exit;
end;
Result:=StrPas(APChar);
end;
function Char2SqlStr(APChar: PChar): String;
begin
if APChar = nil then
begin
Result:='NULL';
Exit;
end;
//todo: create custom routine to directly transform PChar -> SQL str
Result:=StrPas(APChar);
if Pos('''',Result) > 0 then
Result:=AnsiReplaceStr(Result,'''','''''');
Result:=''''+Result+'''';
end;
// TDSStream
constructor TDSStream.Create(const ActiveItem: PDataRecord; FieldIndex:Integer);
@ -919,12 +950,17 @@ end;
procedure TCustomSqliteDataset.SetDetailFilter;
function FieldToSqlStr(AField:TField):String;
begin
case AField.DataType of
ftString,ftMemo: Result:='"'+AField.AsString+'"';//todo: handle " caracter properly
ftDateTime,ftDate,ftTime:Str(AField.AsDateTime,Result);
if not AField.IsNull then
begin
case AField.DataType of
ftString,ftMemo: Result:='"'+AField.AsString+'"';//todo: handle " caracter properly
ftDateTime,ftDate,ftTime:Str(AField.AsDateTime,Result);
else
Result:=AField.AsString;
end;//case
end
else
Result:=AField.AsString;
end;//case
Result:='NULL';
end;//function
var
@ -1034,25 +1070,9 @@ begin
ExecSQL(FSql);
end;
function GetSqlStr(IsString: boolean; APChar: PChar): String;
begin
if APChar = nil then
begin
Result:='NULL';
Exit;
end;
Result:=StrPas(APChar);
if IsString then
begin
if Pos('''',Result) > 0 then
Result:=AnsiReplaceStr(Result,'''','''''');
Result:=''''+Result+'''';
end;
end;
function TCustomSqliteDataset.ApplyUpdates:Boolean;
var
CounterFields,CounterItems,StatementsCounter:Integer;
iFields,iItems,StatementsCounter:Integer;
SqlTemp,WhereKeyNameEqual,ASqlLine,TemplateStr:String;
begin
if not UpdatesPending then
@ -1076,10 +1096,10 @@ begin
// Delete Records
if FDeletedItems.Count > 0 then
TemplateStr:='DELETE FROM '+FTableName+WhereKeyNameEqual;
for CounterItems:= 0 to FDeletedItems.Count - 1 do
for iItems:= 0 to FDeletedItems.Count - 1 do
begin
SqlTemp:=SqlTemp+(TemplateStr+
StrPas(PDataRecord(FDeletedItems[CounterItems])^.Row[FPrimaryKeyNo])+';');
StrPas(PDataRecord(FDeletedItems[iItems])^.Row[FPrimaryKeyNo])+';');
inc(StatementsCounter);
//ApplyUpdates each 400 statements
if StatementsCounter = 400 then
@ -1093,18 +1113,18 @@ begin
// Update changed records
if FUpdatedItems.Count > 0 then
TemplateStr:='UPDATE '+FTableName+' SET ';
for CounterItems:= 0 to FUpdatedItems.Count - 1 do
for iItems:= 0 to FUpdatedItems.Count - 1 do
begin
ASqlLine:=TemplateStr;
for CounterFields:= 0 to Fields.Count - 2 do
for iFields:= 0 to Fields.Count - 2 do
begin
ASqlLine:=ASqlLine + (Fields[CounterFields].FieldName +' = '+
GetSqlStr((Fields[CounterFields].DataType in [ftString,ftMemo]),
PDataRecord(FUpdatedItems[CounterItems])^.Row[CounterFields])+',');
ASqlLine:=ASqlLine + (Fields[iFields].FieldName +' = '+
FGetSqlStr[iFields](PDataRecord(FUpdatedItems[iItems])^.Row[iFields])+',');
end;
ASqlLine:=ASqlLine + (Fields[Fields.Count - 1].FieldName +' = '+
GetSqlStr((Fields[Fields.Count - 1].DataType in [ftString,ftMemo]),PDataRecord(FUpdatedItems[CounterItems])^.Row[Fields.Count - 1])+
WhereKeyNameEqual+StrPas(PDataRecord(FUpdatedItems[CounterItems])^.Row[FPrimaryKeyNo])+';');
iFields:=Fields.Count - 1;
ASqlLine:=ASqlLine + (Fields[iFields].FieldName +' = '+
FGetSqlStr[iFields](PDataRecord(FUpdatedItems[iItems])^.Row[iFields])+
WhereKeyNameEqual+StrPas(PDataRecord(FUpdatedItems[iItems])^.Row[FPrimaryKeyNo])+';');
SqlTemp:=SqlTemp + ASqlLine;
inc(StatementsCounter);
//ApplyUpdates each 400 statements
@ -1121,24 +1141,22 @@ begin
if FAddedItems.Count > 0 then
begin
TemplateStr:='INSERT INTO '+FTableName+ ' (';
for CounterFields:= 0 to Fields.Count - 1 do
for iFields:= 0 to Fields.Count - 2 do
begin
TemplateStr:=TemplateStr + Fields[CounterFields].FieldName;
if CounterFields <> Fields.Count - 1 then
TemplateStr:=TemplateStr+',';
TemplateStr:=TemplateStr + Fields[iFields].FieldName+',';
end;
TemplateStr:=TemplateStr+') VALUES (';
TemplateStr:= TemplateStr+Fields[Fields.Count - 1].FieldName+') VALUES (';
end;
for CounterItems:= 0 to FAddedItems.Count - 1 do
for iItems:= 0 to FAddedItems.Count - 1 do
begin
ASqlLine:=TemplateStr;
for CounterFields:= 0 to Fields.Count - 2 do
for iFields:= 0 to Fields.Count - 2 do
begin
ASqlLine:=ASqlLine + (GetSqlStr((Fields[CounterFields].DataType in [ftString,ftMemo]),
PDataRecord(FAddedItems[CounterItems])^.Row[CounterFields])+',');
ASqlLine:=ASqlLine + (FGetSqlStr[iFields](PDataRecord(FAddedItems[iItems])^.Row[iFields])+',');
end;
ASqlLine:=ASqlLine + (GetSqlStr((Fields[Fields.Count -1].DataType in [ftString,ftMemo]),
PDataRecord(FAddedItems[CounterItems])^.Row[Fields.Count - 1])+');');
//todo: see if i can assume iFields = Fields.Count-2 safely
iFields:=Fields.Count - 1;
ASqlLine:=ASqlLine + (FGetSqlStr[iFields](PDataRecord(FAddedItems[iItems])^.Row[iFields])+');');
SqlTemp:=SqlTemp + ASqlLine;
inc(StatementsCounter);
//ApplyUpdates each 400 statements
@ -1254,6 +1272,7 @@ begin
for i := 0 to BufferCount - 1 do
PPDataRecord(Buffers[i])^:=FBeginItem;
Resync([]);
DoAfterScroll;
end;
function TCustomSqliteDataset.TableExists: Boolean;

View File

@ -95,7 +95,7 @@ procedure TSqlite3Dataset.InternalInitFieldDefs;
var
vm:Pointer;
ColumnStr:String;
i,FieldSize:Integer;
i,ColumnCount,FieldSize:Integer;
AType:TFieldType;
begin
{$ifdef DEBUG}
@ -105,7 +105,12 @@ begin
FieldDefs.Clear;
sqlite3_prepare(FSqliteHandle,PChar(FSql),-1,@vm,nil);
sqlite3_step(vm);
for i:= 0 to sqlite3_column_count(vm) - 1 do
ColumnCount:=sqlite3_column_count(vm);
//Set BufferSize
FRowBufferSize:=(SizeOf(PPChar)*ColumnCount);
//Prepare the array of pchar2sql functions
SetLength(FGetSqlStr,ColumnCount);
for i:= 0 to ColumnCount - 1 do
begin
ColumnStr:= UpperCase(StrPas(sqlite3_column_decltype(vm,i)));
if (ColumnStr = 'INTEGER') or (ColumnStr = 'INT') then
@ -170,13 +175,17 @@ begin
FieldSize:=0;
end;
FieldDefs.Add(StrPas(sqlite3_column_name(vm,i)), AType, FieldSize, False);
//Set the pchar2sql function
if AType in [ftString,ftMemo] then
FGetSqlStr[i]:=@Char2SqlStr
else
FGetSqlStr[i]:=@Num2SqlStr;
{$ifdef DEBUG}
writeln(' Field[',i,'] Name: ',sqlite3_column_name(vm,i));
writeln(' Field[',i,'] Type: ',sqlite3_column_decltype(vm,i));
{$endif}
end;
sqlite3_finalize(vm);
FRowBufferSize:=(SizeOf(PPChar)*FieldDefs.Count);
{$ifdef DEBUG}
writeln(' FieldDefs.Count: ',FieldDefs.Count);
{$endif}

View File

@ -57,9 +57,6 @@ implementation
uses
sqlite,db;
var
DummyAutoIncFieldNo:Integer;
//function sqlite_last_statement_changes(dbhandle:Pointer):longint;cdecl;external 'sqlite' name 'sqlite_last_statement_changes';
function GetAutoIncValue(NextValue: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
@ -77,6 +74,7 @@ begin
Result:=1;
end;
{
function GetFieldDefs(TheDataset: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
var
FieldSize:Word;
@ -84,6 +82,8 @@ var
AType:TFieldType;
ColumnStr:String;
begin
//Prepare the array of pchar2sql functions
SetLength(TCustomSqliteDataset(TheDataset).FGetSqlStr,Columns);
// Sqlite is typeless (allows any type in any field)
// regardless of what is in Create Table, but returns
// exactly what is in Create Table statement
@ -155,10 +155,11 @@ begin
FieldSize:=0;
end;
TDataset(TheDataset).FieldDefs.Add(StrPas(ColumnNames[i]), AType, FieldSize, False);
//Set
end;
Result:=-1;
end;
}
{ TSqliteDataset }
@ -179,17 +180,104 @@ begin
end;
procedure TSqliteDataset.InternalInitFieldDefs;
var
ColumnCount,i:Integer;
FieldSize:Word;
AType:TFieldType;
vm:Pointer;
ColumnNames,ColumnValues:PPChar;
ColumnStr:String;
begin
FieldDefs.Clear;
sqlite_exec(FSqliteHandle,PChar('PRAGMA empty_result_callbacks = ON;PRAGMA show_datatypes = ON;'),nil,nil,nil);
DummyAutoIncFieldNo:=-1;
FSqliteReturnId:=sqlite_exec(FSqliteHandle,PChar(FSql),@GetFieldDefs,Self,nil);
FAutoIncFieldNo:=DummyAutoIncFieldNo;
FAutoIncFieldNo:=-1;
sqlite_compile(FSqliteHandle,PChar(FSql),nil,@vm,nil);
sqlite_step(vm,@ColumnCount,@ColumnValues,@ColumnNames);
//Prepare the array of pchar2sql functions
SetLength(FGetSqlStr,ColumnCount);
//Set BufferSize
FRowBufferSize:=(SizeOf(PPChar)*ColumnCount);
// Sqlite is typeless (allows any type in any field)
// regardless of what is in Create Table, but returns
// exactly what is in Create Table statement
// here is a trick to get the datatype.
// If the field contains another type, may have problems
for i:= 0 to ColumnCount - 1 do
begin
ColumnStr:= UpperCase(StrPas(ColumnNames[i + ColumnCount]));
if (ColumnStr = 'INTEGER') or (ColumnStr = 'INT') then
begin
if AutoIncrementKey and
(UpperCase(StrPas(ColumnNames[i])) = UpperCase(PrimaryKey)) then
begin
AType:= ftAutoInc;
FAutoIncFieldNo:=i;
end
else
AType:= ftInteger;
FieldSize:=SizeOf(LongInt);
end else if Pos('VARCHAR',ColumnStr) = 1 then
begin
AType:= ftString;
FieldSize:=0;
end else if Pos('BOOL',ColumnStr) = 1 then
begin
AType:= ftBoolean;
FieldSize:=SizeOf(WordBool);
end else if Pos('AUTOINC',ColumnStr) = 1 then
begin
AType:= ftAutoInc;
FieldSize:=SizeOf(LongInt);
if FAutoIncFieldNo = -1 then
FAutoIncFieldNo:= i;
end else if (Pos('FLOAT',ColumnStr)=1) or (Pos('NUMERIC',ColumnStr)=1) then
begin
AType:= ftFloat;
FieldSize:=SizeOf(Double);
end else if (ColumnStr = 'DATETIME') then
begin
AType:= ftDateTime;
FieldSize:=SizeOf(TDateTime);
end else if (ColumnStr = 'DATE') then
begin
AType:= ftDate;
FieldSize:=SizeOf(TDateTime);
end else if (ColumnStr = 'TIME') then
begin
AType:= ftTime;
FieldSize:=SizeOf(TDateTime);
end else if (ColumnStr = 'LARGEINT') then
begin
AType:= ftLargeInt;
FieldSize:=SizeOf(LargeInt);
end else if (ColumnStr = 'TEXT') then
begin
AType:= ftMemo;
FieldSize:=0;
end else if (ColumnStr = 'CURRENCY') then
begin
AType:= ftCurrency;
FieldSize:=SizeOf(Double);
end else if (ColumnStr = 'WORD') then
begin
AType:= ftWord;
FieldSize:=SizeOf(Word);
end else
begin
AType:=ftString;
FieldSize:=0;
end;
FieldDefs.Add(StrPas(ColumnNames[i]), AType, FieldSize, False);
//Set the pchar2sql function
if AType in [ftString,ftMemo] then
FGetSqlStr[i]:=@Char2SqlStr
else
FGetSqlStr[i]:=@Num2SqlStr;
end;
sqlite_finalize(vm, nil);
{
if FSqliteReturnId <> SQLITE_ABORT then
DatabaseError(SqliteReturnString,Self);
}
FRowBufferSize:=(SizeOf(PPChar)*FieldDefs.Count);
end;
function TSqliteDataset.GetRowsAffected: Integer;