* Patch from Luiz Americo

- implements AutoInc fields
 - add IndexFieldName: now the user can choose what field use as primary key (used to apply updates).
   There's no  more necessity of using _ROWID_
 - SetRecNo and GetRecNo are working
 - Fixed issues when used in lazarus (i will send a package to lazarus list)
This commit is contained in:
michael 2005-01-12 19:14:57 +00:00
parent 102de9b240
commit abe11d29c2

View File

@ -1,4 +1,4 @@
unit SqliteDS; unit sqliteds;
{ {
This is SqliteDS/TSqliteDataset, a TDataset descendant class for use with fpc compiler This is SqliteDS/TSqliteDataset, a TDataset descendant class for use with fpc compiler
@ -23,7 +23,9 @@ unit SqliteDS;
{$Mode ObjFpc} {$Mode ObjFpc}
{$H+} {$H+}
{ $Define DEBUG} { $Define USE_SQLITEDS_INTERNALS}
{ $Define DEBUG}
interface interface
uses Classes, SysUtils, Db; uses Classes, SysUtils, Db;
@ -44,6 +46,10 @@ type
FFileName: String; FFileName: String;
FSql: String; FSql: String;
FTableName: String; FTableName: String;
FIndexFieldName: String;
FIndexFieldNo: Integer;
FAutoIncFieldNo: Integer;
FNextAutoInc:Integer;
FCurrentItem: PDataRecord; FCurrentItem: PDataRecord;
FBeginItem: PDataRecord; FBeginItem: PDataRecord;
FEndItem: PDataRecord; FEndItem: PDataRecord;
@ -52,6 +58,9 @@ type
FRowBufferSize: Integer; FRowBufferSize: Integer;
FRowCount: Integer; FRowCount: Integer;
FRecordCount: Integer; FRecordCount: Integer;
FExpectedAppends: Integer;
FExpectedDeletes: Integer;
FExpectedUpdates: Integer;
FSqliteReturnId: Integer; FSqliteReturnId: Integer;
FDataAllocated: Boolean; FDataAllocated: Boolean;
FSaveOnClose: Boolean; FSaveOnClose: Boolean;
@ -88,6 +97,9 @@ type
function IsCursorOpen: Boolean; override; function IsCursorOpen: Boolean; override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override; procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override; procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
procedure SetExpectedAppends(AValue:Integer);
procedure SetExpectedUpdates(AValue:Integer);
procedure SetExpectedDeletes(AValue:Integer);
procedure SetFieldData(Field: TField; Buffer: Pointer); override; procedure SetFieldData(Field: TField; Buffer: Pointer); override;
procedure SetRecNo(Value: Integer); override; procedure SetRecNo(Value: Integer); override;
public public
@ -100,17 +112,27 @@ type
function ExecSQL:Integer; function ExecSQL:Integer;
function ExecSQL(ASql:String):Integer; function ExecSQL(ASql:String):Integer;
function SqliteReturnString: String; function SqliteReturnString: String;
{$Ifdef DEBUG} {$ifdef USE_SQLITEDS_INTERNALS}
property BeginItem: PDataRecord read FBeginItem; property BeginItem: PDataRecord read FBeginItem;
property EndItem: PDataRecord read FEndItem; property EndItem: PDataRecord read FEndItem;
{$Endif} property UpdatedItems: TList read FUpdatedItems;
property AddedItems: TList read FAddedItems;
property DeletedItems: TList read FDeletedItems;
{$endif}
property ExpectedAppends: Integer read FExpectedAppends write SetExpectedAppends;
property ExpectedUpdates: Integer read FExpectedUpdates write SetExpectedUpdates;
property ExpectedDeletes: Integer read FExpectedDeletes write SetExpectedDeletes;
property SqliteReturnId: Integer read FSqliteReturnId; property SqliteReturnId: Integer read FSqliteReturnId;
published
property FileName: String read FFileName write FFileName;
property IndexFieldName: String read FIndexFieldName write FIndexFieldName;
property SaveOnClose: Boolean read FSaveOnClose write FSaveOnClose;
property SQL: String read FSql write SetSql; property SQL: String read FSql write SetSql;
property TableName: String read FTableName write FTableName; property TableName: String read FTableName write FTableName;
property FileName: String read FFileName write FFileName; //property Active;
property SaveOnClose: Boolean read FSaveOnClose write FSaveOnClose; property FieldDefs;
published
property Active; //Events
property BeforeOpen; property BeforeOpen;
property AfterOpen; property AfterOpen;
property BeforeClose; property BeforeClose;
@ -137,6 +159,21 @@ implementation
uses SQLite; uses SQLite;
function GetAutoIncValue(NextValue: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
var
CodeError, TempInt: Integer;
begin
TempInt:=-1;
if ColumnValues[0] <> nil then
begin
Val(StrPas(ColumnValues[0]),TempInt,CodeError);
if CodeError <> 0 then
DatabaseError('SqliteDs - Error trying to get last autoinc value');
end;
Integer(NextValue^):=Succ(TempInt);
Result:=1;
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;
@ -181,7 +218,15 @@ begin
begin begin
AType:= ftTime; AType:= ftTime;
FieldSize:=SizeOf(TDateTime); FieldSize:=SizeOf(TDateTime);
end else end else if (ColumnStr = 'AUTOINC') then
begin
if TSqliteDataset(TheDataset).Tablename = '' then
DatabaseError('Sqliteds - AutoInc fields requires Tablename to be set');
AType:= ftAutoInc;
FieldSize:=SizeOf(Integer);
if TSqliteDataset(TheDataset).FAutoIncFieldNo = -1 then
TSqliteDataset(TheDataset).FAutoIncFieldNo:= Counter;
end else
begin begin
AType:= ftString; AType:= ftString;
FieldSize:=0; FieldSize:=0;
@ -215,6 +260,11 @@ var
ColumnNames,ColumnValues:PPChar; ColumnNames,ColumnValues:PPChar;
Counter:Integer; Counter:Integer;
begin begin
//Get AutoInc Field initial value
if FAutoIncFieldNo <> -1 then
sqlite_exec(FSqliteHandle,PChar('Select Max('+Fields[FAutoIncFieldNo].FieldName+') from ' + FTableName),
@GetAutoIncValue,@FNextAutoInc,nil);
FSqliteReturnId:=sqlite_compile(FSqliteHandle,Pchar(FSql),nil,@vm,nil); FSqliteReturnId:=sqlite_compile(FSqliteHandle,Pchar(FSql),nil,@vm,nil);
if FSqliteReturnId <> SQLITE_OK then if FSqliteReturnId <> SQLITE_OK then
case FSqliteReturnId of case FSqliteReturnId of
@ -223,6 +273,7 @@ begin
else else
DatabaseError('Unknow Error',Self); DatabaseError('Unknow Error',Self);
end; end;
FDataAllocated:=True; FDataAllocated:=True;
New(FBeginItem); New(FBeginItem);
FBeginItem^.Next:=nil; FBeginItem^.Next:=nil;
@ -358,7 +409,7 @@ begin
begin begin
Move(FieldRow^,PChar(Buffer)^,StrLen(FieldRow)+1); Move(FieldRow^,PChar(Buffer)^,StrLen(FieldRow)+1);
end; end;
ftInteger,ftBoolean,ftWord: ftInteger,ftBoolean,ftWord,ftAutoInc:
begin begin
Val(StrPas(FieldRow),LongInt(Buffer^),ValError); Val(StrPas(FieldRow),LongInt(Buffer^),ValError);
Result:= ValError = 0; Result:= ValError = 0;
@ -416,22 +467,27 @@ var
TempItem,TempActive:PDataRecord; TempItem,TempActive:PDataRecord;
begin begin
Result:= -1; Result:= -1;
if FRecordCount = 0 then
Exit;
TempItem:=FBeginItem; TempItem:=FBeginItem;
TempActive:=PPDataRecord(ActiveBuffer)^; TempActive:=PPDataRecord(ActiveBuffer)^;
while TempActive <> TempItem do if TempActive = FCacheItem then // Record not posted yet
begin Result:=FRecordCount
if TempItem^.Next <> nil then else
while TempActive <> TempItem do
begin begin
inc(Result); if TempItem^.Next <> nil then
TempItem:=TempItem^.Next; begin
end inc(Result);
else TempItem:=TempItem^.Next;
begin end
Result:=-1; else
DatabaseError('GetRecNo - ActiveItem Not Found',Self); begin
break; Result:=-1;
DatabaseError('Sqliteds.GetRecNo - ActiveItem Not Found',Self);
break;
end;
end; end;
end;
end; end;
function TSqliteDataset.GetRecordSize: Word; function TSqliteDataset.GetRecordSize: Word;
@ -456,6 +512,8 @@ begin
NewItem^.Next:=FEndItem; NewItem^.Next:=FEndItem;
FEndItem^.Previous:=NewItem; FEndItem^.Previous:=NewItem;
Inc(FRecordCount); Inc(FRecordCount);
if FAutoIncFieldNo <> - 1 then
Inc(FNextAutoInc);
FAddedItems.Add(NewItem); FAddedItems.Add(NewItem);
end; end;
@ -499,6 +557,10 @@ begin
else else
FCurrentItem:= FCurrentItem^.Next; FCurrentItem:= FCurrentItem^.Next;
end; end;
// Dec FNextAutoInc
if FAutoIncFieldNo <> -1 then
if StrToInt(StrPas(TempItem^.Row[FAutoIncFieldNo])) = (FNextAutoInc - 1) then
Dec(FNextAutoInc);
end; end;
procedure TSqliteDataset.InternalFirst; procedure TSqliteDataset.InternalFirst;
@ -532,12 +594,19 @@ end;
procedure TSqliteDataset.InternalInitRecord(Buffer: PChar); procedure TSqliteDataset.InternalInitRecord(Buffer: PChar);
var var
Counter:Integer; Counter:Integer;
TempStr:String;
begin begin
for Counter:= 0 to FRowCount - 1 do for Counter:= 0 to FRowCount - 1 do
begin begin
StrDispose(FCacheItem^.Row[Counter]); StrDispose(FCacheItem^.Row[Counter]);
FCacheItem^.Row[Counter]:=nil; FCacheItem^.Row[Counter]:=nil;
end; end;
if FAutoIncFieldNo <> - 1 then
begin
Str(FNextAutoInc,TempStr);
FCacheItem^.Row[FAutoIncFieldNo]:=StrAlloc(Length(TempStr)+1);
StrPCopy(FCacheItem^.Row[FAutoIncFieldNo],TempStr);
end;
PPDataRecord(Buffer)^:=FCacheItem; PPDataRecord(Buffer)^:=FCacheItem;
end; end;
@ -548,15 +617,22 @@ end;
procedure TSqliteDataset.InternalOpen; procedure TSqliteDataset.InternalOpen;
begin begin
FAutoIncFieldNo:=-1;
if not FileExists(FFileName) then if not FileExists(FFileName) then
DatabaseError('File '+FFileName+' not found',Self); DatabaseError('File '+FFileName+' not found',Self);
FSqliteHandle:=sqlite_open(PChar(FFileName),0,FDBError); FSqliteHandle:=sqlite_open(PChar(FFileName),0,nil);
InternalInitFieldDefs; InternalInitFieldDefs;
BuildLinkedList;
FCurrentItem:=FBeginItem;
if DefaultFields then if DefaultFields then
CreateFields; CreateFields;
BindFields(True); BindFields(True);
// Get indexfieldno if available
if FIndexFieldName <> '' then
FIndexFieldNo:=FieldByName(FIndexFieldName).Index
else
FIndexFieldNo:=FAutoIncFieldNo;
BuildLinkedList;
FCurrentItem:=FBeginItem;
end; end;
procedure TSqliteDataset.InternalPost; procedure TSqliteDataset.InternalPost;
@ -585,6 +661,24 @@ begin
PPDataRecord(Buffer)^^.BookmarkFlag := Value; PPDataRecord(Buffer)^^.BookmarkFlag := Value;
end; end;
procedure TSqliteDataset.SetExpectedAppends(AValue:Integer);
begin
if Assigned(FAddedItems) then
FAddedItems.Capacity:=AValue;
end;
procedure TSqliteDataset.SetExpectedUpdates(AValue:Integer);
begin
if Assigned(FUpdatedItems) then
FUpdatedItems.Capacity:=AValue;
end;
procedure TSqliteDataset.SetExpectedDeletes(AValue:Integer);
begin
if Assigned(FDeletedItems) then
FDeletedItems.Capacity:=AValue;
end;
procedure TSqliteDataset.SetFieldData(Field: TField; Buffer: Pointer); procedure TSqliteDataset.SetFieldData(Field: TField; Buffer: Pointer);
var var
TempStr:String; TempStr:String;
@ -622,8 +716,16 @@ begin
end; end;
procedure TSqliteDataset.SetRecNo(Value: Integer); procedure TSqliteDataset.SetRecNo(Value: Integer);
var
Counter:Integer;
TempItem:PDataRecord;
begin begin
// if Value >= FRecordCount then
DatabaseError('SqliteDs - Record Number Out Of Range');
TempItem:=FBeginItem;
for Counter := 0 to Value do
TempItem:=TempItem^.Next;
PPDataRecord(ActiveBuffer)^:=TempItem;
end; end;
// Specific functions // Specific functions
@ -652,12 +754,19 @@ end;
function TSqliteDataset.ApplyUpdates:Boolean; function TSqliteDataset.ApplyUpdates:Boolean;
var var
CounterFields,CounterItems:Integer; CounterFields,CounterItems:Integer;
SqlTemp:String; SqlTemp,KeyName:String;
Quote:Char; Quote:Char;
begin begin
Result:=False; Result:=False;
if (FTableName <> '') and (Fields[0].FieldName = '_ROWID_') then if (FTableName <> '') and (FIndexFieldNo <> -1) then
begin begin
KeyName:=Fields[FIndexFieldNo].FieldName;
{$ifdef DEBUG}
if FIndexFieldNo = FAutoIncFieldNo then
WriteLn('Using an AutoInc field as primary key');
WriteLn('IndexFieldName: ',KeyName);
WriteLn('IndexFieldNo: ',FIndexFieldNo);
{$endif}
SqlTemp:='BEGIN TRANSACTION; '; SqlTemp:='BEGIN TRANSACTION; ';
// Update changed records // Update changed records
For CounterItems:= 0 to FUpdatedItems.Count - 1 do For CounterItems:= 0 to FUpdatedItems.Count - 1 do
@ -678,7 +787,7 @@ begin
SqlTemp:=SqlTemp + Fields[CounterFields].FieldName +' = NULL , '; SqlTemp:=SqlTemp + Fields[CounterFields].FieldName +' = NULL , ';
end; end;
system.delete(SqlTemp,Length(SqlTemp)-2,2); system.delete(SqlTemp,Length(SqlTemp)-2,2);
SqlTemp:=SqlTemp+'WHERE _ROWID_ = '+StrPas(PDataRecord(FUpdatedItems[CounterItems])^.Row[0])+';'; SqlTemp:=SqlTemp+'WHERE '+KeyName+' = '+StrPas(PDataRecord(FUpdatedItems[CounterItems])^.Row[FIndexFieldNo])+';';
end; end;
// Add new records // Add new records
For CounterItems:= 0 to FAddedItems.Count - 1 do For CounterItems:= 0 to FAddedItems.Count - 1 do
@ -711,8 +820,8 @@ begin
// Delete Items // Delete Items
For CounterItems:= 0 to FDeletedItems.Count - 1 do For CounterItems:= 0 to FDeletedItems.Count - 1 do
begin begin
SqlTemp:=SqlTemp+'DELETE FROM '+FTableName+ ' WHERE _ROWID_ = '+ SqlTemp:=SqlTemp+'DELETE FROM '+FTableName+ ' WHERE '+KeyName+' = '+
StrPas(PDataRecord(FDeletedItems[CounterItems])^.Row[0])+';'; StrPas(PDataRecord(FDeletedItems[CounterItems])^.Row[FIndexFieldNo])+';';
end; end;
SqlTemp:=SqlTemp+'END TRANSACTION; '; SqlTemp:=SqlTemp+'END TRANSACTION; ';
{$ifdef DEBUG} {$ifdef DEBUG}
@ -734,6 +843,12 @@ var
SqlTemp:String; SqlTemp:String;
Counter:Integer; Counter:Integer;
begin begin
{$ifdef DEBUG}
if FTableName = '' then
WriteLn('CreateTable : TableName Not Set');
if FieldDefs.Count = 0 then
WriteLn('CreateTable : FieldDefs Not Initialized');
{$endif}
if (FTableName <> '') and (FieldDefs.Count > 0) then if (FTableName <> '') and (FieldDefs.Count > 0) then
begin begin
FSqliteHandle:= sqlite_open(PChar(FFileName),0,FDBError); FSqliteHandle:= sqlite_open(PChar(FFileName),0,FDBError);
@ -758,6 +873,8 @@ begin
SqlTemp:=SqlTemp + ' DATE'; SqlTemp:=SqlTemp + ' DATE';
ftTime: ftTime:
SqlTemp:=SqlTemp + ' TIME'; SqlTemp:=SqlTemp + ' TIME';
ftAutoInc:
SqlTemp:=SqlTemp + ' AUTOINC';
else else
SqlTemp:=SqlTemp + ' VARCHAR'; SqlTemp:=SqlTemp + ' VARCHAR';
end; end;