mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-30 12:00:57 +02:00
* Patch from Luiz Americo
- fixes bug #7925 - optimization for ApplyUpdates - clean InitFieldDefs implementation git-svn-id: trunk@5683 -
This commit is contained in:
parent
f3f8a76559
commit
a166a31b61
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user