diff --git a/fcl/db/sqlite/customsqliteds.pas b/fcl/db/sqlite/customsqliteds.pas index 1225bee94c..a4b9101c38 100644 --- a/fcl/db/sqlite/customsqliteds.pas +++ b/fcl/db/sqlite/customsqliteds.pas @@ -57,7 +57,9 @@ type end; TSqliteCallback = function (UserData:Pointer; Columns:longint; Values:PPchar; ColumnNames:PPchar):longint;cdecl; - + TGetSqlStrFunction = function (APChar: PChar): String; + + { TCustomSqliteDataset } TCustomSqliteDataset = class(TDataSet) @@ -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; diff --git a/fcl/db/sqlite/sqlite3ds.pas b/fcl/db/sqlite/sqlite3ds.pas index 27dee5e4af..69d29c7b71 100644 --- a/fcl/db/sqlite/sqlite3ds.pas +++ b/fcl/db/sqlite/sqlite3ds.pas @@ -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} diff --git a/fcl/db/sqlite/sqliteds.pas b/fcl/db/sqlite/sqliteds.pas index e2e2a985ce..8b126b1525 100644 --- a/fcl/db/sqlite/sqliteds.pas +++ b/fcl/db/sqlite/sqliteds.pas @@ -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;