Great, Ralf! Thank you! Yeah, double PSUpdate was a great problem. With DISQLite I use a workaround, like this: .ModifySQL := 'insert or update TABLE ...." which helps me to eliminate the "Key violation" problems. I'll try to test your code in a few days. Currently my workaround works ok in my case, so I can make a temporary build of my project. Then I'll try to understand and test your code! Thank you! 2009/2/18 Delphi Inspiration <delphi@xxxxxxxx> > Jone Word wrote: > > >There's an incorrect processing of BLOB field with BindDataSet, which > results in incorrect posting of this field into DB using TDataSetProvider & > TDISQLite** objects. This happens when BLOB data contains null (#0) > characters. BLOB is being > >treated as a string! > > You are right, I did think of the implicit string conversions in my code. > Unfortunately, the BLOB must be treated as a string, because this is what > TBlobField.Old/NewValue returns. However, I did not pass it to the DB on a > byte level and the #0 escaped my test suite. Many thanks for the catch! > > >I spent hours and hours finding why my BLOB object is always being > truncated. > > I had also spend hours on the blob issue, and looked at it again just now. > I still believe that there is an intricate problem in how TClientDataSet > passes BLOBs to PSUpdateRecord (see source code comments). However, I took > up your suggestion and modified the BindDataSet procedure using a dirty > TDataSet cracker trick (below). > > There is also an acknowledged bug in Provider.pas: D2005, D2006, and D2007 > execute PSUpdateRecord twice. This results in double database inserts. There > is no workaround. See http://qc.codegear.com/wc/qcmain.aspx?d=26092 for > disucsion and fix. > > I would be glad if you could test my new code and report if you meet any > problems. If you do, I would also appreciate if you could send a test case > which escaped my notice. > > I'd like to include the changes in the next version of DISQLite3, which I > hope to release in just a few days. And yes: It will also include the new > life-backup API! > > Ralf > > > //------------------------------------------------------------------------------ > > type > TDataSetCracker = class(TDataSet); > > { Binds TDataSet to a prepared statement. Only available fields will > be bound - unavailable parameters stay untouched. } > procedure BindDataSet(const AStmt: sqlite3_stmt; const ADataSet: TDataSet); > var > f: TField; > i: Integer; > Old: Boolean; > p: PAnsiChar; > SaveState: TDataSetState; > Value: Variant; > ValueBlob: RawByteString; > begin > for i := 1 to sqlite3_bind_parameter_count(AStmt) do > begin > p := sqlite3_bind_parameter_name(AStmt, i); > // Only named parameters starting with ':'. > if Assigned(p) and (p^ = ':') then > begin > Inc(p); > Old := IsOld(p); > if Old then > Inc(p, 4); > f := ADataSet.FindField(sqlite3_decode_utf8(p)); > if Assigned(f) then > if f.IsBlob then > begin > { Special blob handling necessary because all Values > returned > from TBlobField are Variants of type AnsiString. > > Additional TBlobField problem: There seems to be no way to > resolve modifications to blob fields reliably. > > TBlobField.OldValue always returns an empty variant, > TBlobField.NewValue an empty string for unmodified blobs. > > In addition, D2005 and later truncate strings with #0 > chars > during string -> variant conversation. > > We looked hard for a solution, but it seems best not to > use > TBlobFields in WHERE clauses with PSUpdateRecord. } > if Old then > SaveState := > TDataSetCracker(ADataSet).SetTempState(dsOldValue) > else > SaveState := > TDataSetCracker(ADataSet).SetTempState(dsNewValue); > try > ValueBlob := f.AsString; > finally > TDataSetCracker(ADataSet).restorestate(SaveState); > end; > sqlite3_check_stmt(sqlite3_bind_blob( > AStmt, i, > PAnsiChar(ValueBlob), Length(ValueBlob), > sqlite3_Destroy_AnsiString), AStmt); > Pointer(ValueBlob) := nil; > end > else > begin > if Old then > Value := f.OldValue > else > begin > Value := f.NewValue; > if VarIsEmpty(Value) then > Value := f.OldValue; > end; > if not VarIsEmpty(Value) then > sqlite3_check_stmt(sqlite3_bind_variant( > AStmt, i, Value), AStmt); > end; > end; > end; > end; > > > //------------------------------------------------------------------------------ > > > So I've corrected the code so now it works OK. The following is what I > did: > > > >OLD code (DISQLite3DataSet.pas): > >-------------------------- > > if f.IsBlob then > > sqlite3_check_stmt(sqlite3_bind_str(AStmt, i, Utf8String(Value)), > AStmt); > >-------------------------- > > > >NEW code: > >------------------------- > > SValue: String; > >.... > > if f.IsBlob then > > begin > > if Old then > > SValue := tblobfield(f).OldValue else > > SValue := tblobfield(f).NewValue; > > sqlite3_check_stmt(sqlite3_bind_blob(AStmt, i, > PAnsiChar(svalue), tblobfield(f).blobsize, SQLITE_TRANSIENT), AStmt); > > end > >---------------------------- > > _______________________________________________ > Delphi Inspiration mailing list > yunqa@xxxxxxxxxxxxx > //www.freelists.org/list/yunqa > > > >