Browse Source

* Patch from Luiz Americo
- fixes bug #7925
- optimization for ApplyUpdates
- clean InitFieldDefs implementation

git-svn-id: trunk@5683 -

michael 18 years ago
parent
commit
a166a31b61
3 changed files with 172 additions and 56 deletions
  1. 63 44
      fcl/db/sqlite/customsqliteds.pas
  2. 12 3
      fcl/db/sqlite/sqlite3ds.pas
  3. 97 9
      fcl/db/sqlite/sqliteds.pas

+ 63 - 44
fcl/db/sqlite/customsqliteds.pas

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

+ 12 - 3
fcl/db/sqlite/sqlite3ds.pas

@@ -95,7 +95,7 @@ procedure TSqlite3Dataset.InternalInitFieldDefs;
 var
 var
   vm:Pointer;
   vm:Pointer;
   ColumnStr:String;
   ColumnStr:String;
-  i,FieldSize:Integer;
+  i,ColumnCount,FieldSize:Integer;
   AType:TFieldType;
   AType:TFieldType;
 begin
 begin
   {$ifdef DEBUG}
   {$ifdef DEBUG}
@@ -105,7 +105,12 @@ begin
   FieldDefs.Clear;
   FieldDefs.Clear;
   sqlite3_prepare(FSqliteHandle,PChar(FSql),-1,@vm,nil);
   sqlite3_prepare(FSqliteHandle,PChar(FSql),-1,@vm,nil);
   sqlite3_step(vm);
   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
   begin
    ColumnStr:= UpperCase(StrPas(sqlite3_column_decltype(vm,i)));
    ColumnStr:= UpperCase(StrPas(sqlite3_column_decltype(vm,i)));
    if (ColumnStr = 'INTEGER') or (ColumnStr = 'INT') then
    if (ColumnStr = 'INTEGER') or (ColumnStr = 'INT') then
@@ -170,13 +175,17 @@ begin
      FieldSize:=0;
      FieldSize:=0;
    end;
    end;
    FieldDefs.Add(StrPas(sqlite3_column_name(vm,i)), AType, FieldSize, False);
    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}
    {$ifdef DEBUG}
    writeln('  Field[',i,'] Name: ',sqlite3_column_name(vm,i));
    writeln('  Field[',i,'] Name: ',sqlite3_column_name(vm,i));
    writeln('  Field[',i,'] Type: ',sqlite3_column_decltype(vm,i));
    writeln('  Field[',i,'] Type: ',sqlite3_column_decltype(vm,i));
    {$endif}
    {$endif}
   end;
   end;
   sqlite3_finalize(vm);
   sqlite3_finalize(vm);
-  FRowBufferSize:=(SizeOf(PPChar)*FieldDefs.Count);
   {$ifdef DEBUG}
   {$ifdef DEBUG}
   writeln('  FieldDefs.Count: ',FieldDefs.Count);
   writeln('  FieldDefs.Count: ',FieldDefs.Count);
   {$endif}
   {$endif}

+ 97 - 9
fcl/db/sqlite/sqliteds.pas

@@ -57,9 +57,6 @@ implementation
 uses
 uses
   sqlite,db;
   sqlite,db;
 
 
-var
-  DummyAutoIncFieldNo:Integer;
-
 //function sqlite_last_statement_changes(dbhandle:Pointer):longint;cdecl;external 'sqlite' name 'sqlite_last_statement_changes';
 //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;
 function GetAutoIncValue(NextValue: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
@@ -77,6 +74,7 @@ begin
   Result:=1;
   Result:=1;
 end;
 end;
 
 
+{
 function GetFieldDefs(TheDataset: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
 function GetFieldDefs(TheDataset: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
 var
 var
   FieldSize:Word;
   FieldSize:Word;
@@ -84,6 +82,8 @@ var
   AType:TFieldType;
   AType:TFieldType;
   ColumnStr:String;
   ColumnStr:String;
 begin
 begin
+ //Prepare the array of pchar2sql functions
+ SetLength(TCustomSqliteDataset(TheDataset).FGetSqlStr,Columns);
  // Sqlite is typeless (allows any type in any field)
  // Sqlite is typeless (allows any type in any field)
  // regardless of what is in Create Table, but returns
  // regardless of what is in Create Table, but returns
  // exactly what is in Create Table statement
  // exactly what is in Create Table statement
@@ -155,10 +155,11 @@ begin
      FieldSize:=0;
      FieldSize:=0;
    end;
    end;
    TDataset(TheDataset).FieldDefs.Add(StrPas(ColumnNames[i]), AType, FieldSize, False);
    TDataset(TheDataset).FieldDefs.Add(StrPas(ColumnNames[i]), AType, FieldSize, False);
+   //Set
  end;
  end;
  Result:=-1;
  Result:=-1;
 end;
 end;
-
+}
 
 
 { TSqliteDataset }
 { TSqliteDataset }
 
 
@@ -179,17 +180,104 @@ begin
 end;
 end;
 
 
 procedure TSqliteDataset.InternalInitFieldDefs;
 procedure TSqliteDataset.InternalInitFieldDefs;
+var
+  ColumnCount,i:Integer;
+  FieldSize:Word;
+  AType:TFieldType;
+  vm:Pointer;
+  ColumnNames,ColumnValues:PPChar;
+  ColumnStr:String;
 begin
 begin
   FieldDefs.Clear;
   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
   if FSqliteReturnId <> SQLITE_ABORT then
      DatabaseError(SqliteReturnString,Self);
      DatabaseError(SqliteReturnString,Self);
   }
   }
-  FRowBufferSize:=(SizeOf(PPChar)*FieldDefs.Count);
 end;
 end;
 
 
 function TSqliteDataset.GetRowsAffected: Integer;
 function TSqliteDataset.GetRowsAffected: Integer;