Page 1 of 2
BLOB corrupted from previous version
Posted: 25.01.2010, 16:51
by fodox
I have an application in Delphi5 with Zeos 6.6.2-RC, and now I installed Delphi 2010 with Zeos 7.0.0-dev. I use SQlite version 3 with the same DLL.
The archives created with D5 are incompatible if read from D2010. In particular blob data is corrupted: data compressed with zlib can't be decompressed; jpeg doesn't match; sometimes the size I retrieve is less than the original.
I tried to store a stream of 1000 bytes in D5, filled five times with numbers 1..200. Reading the stream from D2010 the size is 999bytes and the sequence is similar but sometimes an error occurs.
How can I solve?
Thanks,
Fodox
Posted: 25.01.2010, 21:32
by liquidsphere
Got same issue. I managed to overcome it by changing how zeos decodes and encodes strings.
First, in ZDbcSqliteUtils unit I added my own 2 functions:
Code: Select all
function DecodeStringToBin(Value: AnsiString): AnsiString;
begin
if Odd(Length(Value)) then
Value := '0'+Value;
SetLength(Result, Length(Value) div 2);
HexToBin(PAnsiChar(Value), PAnsiChar(Result), Length(Result));
end;
function EncodeStringToHex(Value: ansistring): ansistring;
begin
SetLength(Result, Length(Value)*2);
BinToHex(PAnsiChar(Value), PAnsiChar(Result), Length(Value));
Result := AnsiQuotedStr(Result,'''');
end;
Then, in ZDbcSqliteResultset I changed GetBlob function:
Code: Select all
function TZSQLiteResultSet.GetBlob(ColumnIndex: Integer): IZBlob;
var
Stream: TStream;
begin
{$IFNDEF DISABLE_CHECKING}
CheckBlobColumn(ColumnIndex);
{$ENDIF}
Stream := nil;
try
if not IsNull(ColumnIndex) then
begin
if TZAbstractResultSetMetadata(Metadata).GetColumnType(ColumnIndex)
<> stBinaryStream then
Stream := TStringStream.Create(GetString(ColumnIndex))
else
Stream := TStringStream.Create(DecodeStringToBin(GetString(ColumnIndex))); //My fix for blob reading
Result := TZAbstractBlob.CreateWithStream(Stream)
end
else
Result := TZAbstractBlob.CreateWithStream(nil);
finally
if Assigned(Stream) then
Stream.Free;
end;
end;
And in ZDbcSqliteStatement unit changed TZSQLitePreparedStatement.PrepareSQLParam function:
Code: Select all
function TZSQLitePreparedStatement.PrepareSQLParam(ParamIndex: Integer): string;
var
Value: TZVariant;
TempBytes: TByteDynArray;
TempBlob: IZBlob;
begin
TempBytes := nil;
if InParamCount <= ParamIndex then
raise EZSQLException.Create(SInvalidInputParameterCount);
Value := InParamValues[ParamIndex];
if DefVarManager.IsNull(Value) then
Result := 'NULL'
else
begin
case InParamTypes[ParamIndex] of
stBoolean:
if SoftVarManager.GetAsBoolean(Value) then
Result := '''Y'''
else
Result := '''N''';
stByte, stShort, stInteger, stLong, stBigDecimal, stFloat, stDouble:
Result := SoftVarManager.GetAsString(Value);
stString, stBytes:
Result := GetEscapeString(SoftVarManager.GetAsString(Value));
stDate:
Result := '''' + FormatDateTime('yyyy-mm-dd',
SoftVarManager.GetAsDateTime(Value)) + '''';
stTime:
Result := '''' + FormatDateTime('hh:mm:ss',
SoftVarManager.GetAsDateTime(Value)) + '''';
stTimestamp:
Result := '''' + FormatDateTime('yyyy-mm-dd hh:mm:ss',
SoftVarManager.GetAsDateTime(Value)) + '''';
stAsciiStream, stUnicodeStream, stBinaryStream:
begin
TempBlob := DefVarManager.GetAsInterface(Value) as IZBlob;
if not TempBlob.IsEmpty then
begin
if InParamTypes[ParamIndex] = stBinaryStream then
Result := EncodeStringToHex(TempBlob.GetString) //My Fix for blob storing
else
Result := GetEscapeString(TempBlob.GetString);
end
else
Result := 'NULL';
end;
end;
end;
end;
It's not the most elegant solution but it works for me. It would be better to encode blobs to default Sqlite format.
Posted: 26.01.2010, 00:18
by mdaems
liquidsphere,
Seems like you have some sqlite experience.
It would be better to encode blobs to default Sqlite format.
Do you know how to do that?
Changes between 6.6 and 7.0 are probably due to the new string handling in Delphi 2009 we tried to handle.
W8 : before rushing into it : please check if patch
http://fisheye2.atlassian.com/changelog/zeos/?cs=763 is already done in your copy. I only merged this to trunk a few days ago.
Mark
Posted: 28.01.2010, 17:07
by fodox
liquidsphere,
your solution doesn't work for me. It's ok if I store streams with D2010 and I retrieve it always with D2010, but I have archives created with D5 and now I must read them with D2010.
I tried the simple stream of 1000 bytes (five times 1..200). Saving the stream with D5 and reading with D2010, I obtain a stream with half size, all bytes are 0.
Thank you anyway.
Posted: 02.02.2010, 16:52
by fodox
I applied the patch, and my problem is in ZDBCsqliteUtils.pas unit, in function DecodeString, at this line:
Code: Select all
{$IFDEF DELPHI12_UP}
value := utf8decode(value);
{$ENDIF}
If I write blob data with D2010 and then I read, there is no corruption (apparently ...). But if I try to read when the data blob is written with D5 data is corrupted, becouse zeos lib in D5 doesn't write in utf8; in this case I have to remove the line shown up.
I don't know how to convert old archives of my applications with the new feature of zeos, so I would like to disable writing data in utf8 in D2010. Where can I find those lines of code?
Posted: 20.12.2010, 08:53
by wwywt
i get the same problem
my data blob written with delphi2007
now i must read it with delphi2010...
Posted: 20.12.2010, 21:02
by mdaems
Who's taking the challenge of writing a convertor or a backward compatibility patch?
See discussion in
http://zeos.firmos.at/viewtopic.php?t=3011
Mark
Posted: 22.12.2010, 06:33
by wwywt
i have solve the problem of reading old vision of blob data.
it need test.it's my code;
Code: Select all
ZDbcSqLiteResultSet.pas
TZSQLiteResultSet = class(TZAbstractResultSet)
private
FInfo:TStrings;
public
constructor Create(PlainDriver: IZSQLitePlainDriver; Statement: IZStatement;
SQL: string; Handle: Psqlite; StmtHandle: Psqlite_vm;
ColumnCount: Integer; ColumnNames: PPAnsiChar; ColumnValues: PPAnsiChar; Info: TStrings = nil);
constructor TZSQLiteResultSet.Create(PlainDriver: IZSQLitePlainDriver;
Statement: IZStatement; SQL: string; Handle: Psqlite;
StmtHandle: Psqlite_vm; ColumnCount: Integer; ColumnNames: PPAnsiChar;
ColumnValues: PPAnsiChar; Info: TStrings = nil);
begin
FInfo := Info;
inherited Create(Statement, SQL, TZSQLiteResultSetMetadata.Create(
Statement.GetConnection.GetMetadata, SQL, Self));
FHandle := Handle;
FStmtHandle := StmtHandle;
FPlainDriver := PlainDriver;
ResultSetConcurrency := rcReadOnly;
FColumnCount := ColumnCount;
FColumnNames := ColumnNames;
FColumnValues := ColumnValues;
Open;
end;
function TZSQLiteResultSet.GetBlob(ColumnIndex: Integer): IZBlob;
var
Stream: TStream;
begin
{$IFNDEF DISABLE_CHECKING}
CheckBlobColumn(ColumnIndex);
{$ENDIF}
Stream := nil;
try
if not IsNull(ColumnIndex) then
begin
if TZAbstractResultSetMetadata(Metadata).GetColumnType(ColumnIndex)
<> stBinaryStream then
Stream := TStringStream.Create(GetString(ColumnIndex))
else
begin
if SameText(FInfo.Values['OldBLOB'], 'TRUE') then
begin
Stream := TStringStream.Create(DecodeStringAnsi(GetString(ColumnIndex)));
end
else
begin
Stream := TStringStream.Create(DecodeString(GetString(ColumnIndex)));
end;
end;
Result := TZAbstractBlob.CreateWithStream(Stream)
end
else
Result := TZAbstractBlob.CreateWithStream(nil);
finally
if Assigned(Stream) then
Stream.Free;
end;
end;
ZDbcSqLiteStatement.pas
function TZSQLiteStatement.CreateResultSet(const SQL: string; StmtHandle: Psqlite_vm;
ColumnCount: Integer; ColumnNames: PPAnsiChar; ColumnValues: PPAnsiChar): IZResultSet;
var
CachedResolver: TZSQLiteCachedResolver;
NativeResultSet: TZSQLiteResultSet;
CachedResultSet: TZCachedResultSet;
begin
{ Creates a native result set. }
NativeResultSet := TZSQLiteResultSet.Create(FPlainDriver, Self, SQL, FHandle,
StmtHandle, ColumnCount, ColumnNames, ColumnValues,self.info);
NativeResultSet.SetConcurrency(rcReadOnly);
{ Creates a cached result set. }
CachedResolver := TZSQLiteCachedResolver.Create(FPlainDriver, FHandle, Self,
NativeResultSet.GetMetaData);
CachedResultSet := TZCachedResultSet.Create(NativeResultSet, SQL,
CachedResolver);
{ Fetches all rows to prevent blocking. }
CachedResultSet.SetType(rtScrollInsensitive);
CachedResultSet.Last;
CachedResultSet.BeforeFirst;
CachedResultSet.SetConcurrency(GetResultSetConcurrency);
Result := CachedResultSet;
end;
ZDbcSqLiteUtils.pas
function EncodeStringAnsi(Value: Ansistring): Ansistring;
function DecodeStringAnsi(Value: Ansistring): Ansistring;
implementation
function EncodeStringAnsi(Value: Ansistring): Ansistring;
var
I: Integer;
SrcLength, DestLength: Integer;
SrcBuffer, DestBuffer: PAnsiChar;
begin
SrcLength := Length(Value);
SrcBuffer := PAnsiChar(Value);
DestLength := 2;
for I := 1 to SrcLength do
begin
if SrcBuffer^ in [#0, '''', '%'] then
Inc(DestLength, 2)
else
Inc(DestLength);
Inc(SrcBuffer);
end;
SrcBuffer := PAnsiChar(Value);
SetLength(Result, DestLength);
DestBuffer := PAnsiChar(Result);
DestBuffer^ := '''';
Inc(DestBuffer);
for I := 1 to SrcLength do
begin
if SrcBuffer^ = #0 then
begin
DestBuffer[0] := '%';
DestBuffer[1] := '0';
Inc(DestBuffer, 2);
end
else if SrcBuffer^ = '%' then
begin
DestBuffer[0] := '%';
DestBuffer[1] := '%';
Inc(DestBuffer, 2);
end
else if SrcBuffer^ = '''' then
begin
DestBuffer[0] := '''';
DestBuffer[1] := '''';
Inc(DestBuffer, 2);
end
else
begin
DestBuffer^ := SrcBuffer^;
Inc(DestBuffer);
end;
Inc(SrcBuffer);
end;
DestBuffer^ := '''';
end;
function DecodeStringAnsi(Value: Ansistring): Ansistring;
var
SrcLength, DestLength: Integer;
SrcBuffer, DestBuffer: PAnsiChar;
begin
SrcLength := Length(Value);
SrcBuffer := PAnsiChar(Value);
SetLength(Result, SrcLength);
DestLength := 0;
DestBuffer := PAnsiChar(Result);
while SrcLength > 0 do
begin
if SrcBuffer^ = '%' then
begin
Inc(SrcBuffer);
if SrcBuffer^ <> '0' then
DestBuffer^ := SrcBuffer^
else
DestBuffer^ := #0;
Inc(SrcBuffer);
Dec(SrcLength, 2);
end
else
begin
DestBuffer^ := SrcBuffer^;
Inc(SrcBuffer);
Dec(SrcLength);
end;
Inc(DestBuffer);
Inc(DestLength);
end;
SetLength(Result, DestLength);
end;
now when i want to read old data just use
zquery.Properties.Add('OldBLOB=true');
then it will use ansistring format .
i need more people test if the solution is OK.
PS :when I try to post blob data (an icon ) to my database i got an error.
any body know how to do?
my error message:SQL ERROR:unrecognized token:''''''
Posted: 24.12.2010, 09:11
by wwywt
i think if we can found a functionin delphi2010 which result is the same with the function Encodingstring. then we can solve the problem of write old vision blob data.
function EncodeString(Value: string): string;
var
I: Integer;
SrcLength, DestLength: Integer;
SrcBuffer, DestBuffer: PAnsiChar;
begin
SrcLength := Length(Value);
SrcBuffer := PAnsiChar(Value);
DestLength := 2;
for I := 1 to SrcLength do
begin
if SrcBuffer^ in [#0, '''', '%'] then
Inc(DestLength, 2)
else
Inc(DestLength);
Inc(SrcBuffer);
end;
SrcBuffer := PAnsiChar(Value);
SetLength(Result, DestLength);
DestBuffer := PAnsiChar(Result);
DestBuffer^ := '''';
Inc(DestBuffer);
for I := 1 to SrcLength do
begin
if SrcBuffer^ = #0 then
begin
DestBuffer[0] := '%';
DestBuffer[1] := '0';
Inc(DestBuffer, 2);
end
else if SrcBuffer^ = '%' then
begin
DestBuffer[0] := '%';
DestBuffer[1] := '%';
Inc(DestBuffer, 2);
end
else if SrcBuffer^ = '''' then
begin
DestBuffer[0] := '''';
DestBuffer[1] := '''';
Inc(DestBuffer, 2);
end
else
begin
DestBuffer^ := SrcBuffer^;
Inc(DestBuffer);
end;
Inc(SrcBuffer);
end;
DestBuffer^ := '''';
end;
Posted: 25.12.2010, 09:00
by mdaems
wwywt,
I don't understand the meaning of you second piece of code.
BUT:
If you tested reading BLOBs from an old database with your first patch, I think it's the right solution. I hope you can also add the 'old encoding on write' to the patch.
The code doesn't break behaviour when the parameter is not set. So for me it's safe enough to commit to testing branch.
Thanks for the good job!
Mark
Posted: 27.12.2010, 05:00
by wwywt
i found the same function encodingstring get the different result between delphi2007 and delphi2010 . i think it maybe the reson of cause write old data error. i'm still try to solve it. if i have a solution i will post to here.
Posted: 27.12.2010, 09:30
by mdaems
Isn't the problem in TZSQLitePreparedStatement.PrepareSQLParam function? I think there you could do the same (but inverted) trick as you did in TZSQLiteResultSet.GetBlob. (which means using your EncodeAnsi function when the oldBLOB setting is used)
Mark
Posted: 27.12.2010, 11:04
by wwywt
emm.. i found may be the problem is caused by an ansistring convert to a string. see my test demo in delphi2010.
Code: Select all
function StreamToString(mStream: TStream): AnsiString;
var
I: Integer;
begin
Result := '';
if not Assigned(mStream) then Exit;
SetLength(Result, mStream.Size);
for I := 0 to Pred(mStream.Size) do try
mStream.Position := I;
mStream.Read(Result[Succ(I)], 1);
except
Result := '';
end;
end;
function EncodeString(Value: ansistring): ansistring;
var
I: Integer;
SrcLength, DestLength: Integer;
SrcBuffer, DestBuffer: PansiChar;
begin
SrcLength := Length(Value);
SrcBuffer := PansiChar(Value);
DestLength := 2;
for I := 1 to SrcLength do
begin
if SrcBuffer^ in [#0, '''', '%'] then
Inc(DestLength, 2)
else Inc(DestLength);
Inc(SrcBuffer);
end;
SrcBuffer := PansiChar(Value);
SetLength(Result, DestLength);
DestBuffer := PansiChar(Result);
DestBuffer^ := '''';
Inc(DestBuffer);
for I := 1 to SrcLength do
begin
if SrcBuffer^ = #0 then
begin
DestBuffer[0] := '%';
DestBuffer[1] := '0';
Inc(DestBuffer, 2);
end
else if SrcBuffer^ = '%' then
begin
DestBuffer[0] := '%';
DestBuffer[1] := '%';
Inc(DestBuffer, 2);
end
else if SrcBuffer^ = '''' then
begin
DestBuffer[0] := '''';
DestBuffer[1] := '''';
Inc(DestBuffer, 2);
end
else
begin
DestBuffer^ := SrcBuffer^;
Inc(DestBuffer);
end;
Inc(SrcBuffer);
end;
DestBuffer^ := '''';
end;
procedure TForm1.Button1Click(Sender: TObject);
var a,c:AnsiString;
b:string;
mm:TFileStream;
begin
mm:=TFileStream.Create('c:\skype.ico',fmOpenRead);
try
mm.Position:=0;
a:=StreamToString(mm);
a:=EncodeString(a);
OutputDebugStringA(PAnsiChar('a:'+a));
b:=string(a);
OutputDebugString(PChar('b:'+b));
c:='';
c:=AnsiString(b);
OutputDebugStringA(PAnsiChar('c:'+c));
finally
mm.free;
end;
end;
i compare the out put info. the string b is not the same of ansistring a.
maybe it's the reason:when encodingstringansi function get a correct sql string. the sql string(ansi) was convert to a string(unicode). in this covert process the sql string has been changed!so i can't write a correct data to database.i don't know how to solve this problem.
Posted: 27.12.2010, 13:38
by mdaems
You should handle BLOB's using SQL parameters. Just take it as a 'best practise'.
That's where the difficulty with Delphi 2009 comes from. Inserting a BLOB value inside an SQL string just doesn't work anymore (unless it is a Character LOB maybe, as simple characters can be converted during the Aansistring to string conversion). Believe me, it's not only a SqlLite problem. The new string format just isn't usable to handle BLOB's anymore.
When updating the BLOB using parameters you will probably need to change the TZSQLitePreparedStatement.PrepareSQLParam function to use old encoding. (If you want to discuss this, I'm online on IM most of this week, I think)
Mark
Posted: 28.12.2010, 03:29
by wwywt
MARK
My pool english...hehe...i will contact you on MSN.
wwywt.