{ This file is part of the Free Pascal run time library. Copyright (c) 1999-2000 by Michael Van Canneyt, member of the Free Pascal development team BufDataset implementation See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} { --------------------------------------------------------------------- TBufDataSet ---------------------------------------------------------------------} constructor TBufDataset.Create(AOwner : TComponent); begin Inherited Create(AOwner); SetLength(FUpdateBuffer,0); BookmarkSize := sizeof(TBufBookmark); // temporary set it here FPacketRecords := 10; end; destructor TBufDataset.Destroy; begin inherited destroy; end; Function TBufDataset.GetCanModify: Boolean; begin Result:= False; end; function TBufDataset.AllocRecordBuffer: PChar; begin result := AllocMem(FRecordsize + sizeof(TBufBookmark)); result^ := #1; // this 'deletes' the record end; procedure TBufDataset.FreeRecordBuffer(var Buffer: PChar); begin ReAllocMem(Buffer,0); end; procedure TBufDataset.InternalOpen; begin CalcRecordSize; FBRecordcount := 0; FBDeletedRecords := 0; FBBuffercount := 0; FBCurrentrecord := -1; FOpen:=True; FIsEOF := false; FIsbOF := true; end; procedure TBufDataset.InternalClose; var i : integer; begin FOpen:=False; CancelUpdates; for i := 0 to FBRecordCount-1 do FreeRecordBuffer(FBBuffers[i]); If FBBufferCount > 0 then ReAllocMem(FBBuffers,0); FBRecordcount := 0; FBBuffercount := 0; FBCurrentrecord := -1; FIsEOF := true; FIsbOF := true; end; procedure TBufDataset.InternalFirst; begin FBCurrentRecord := -1; FIsEOF := false; end; procedure TBufDataset.InternalLast; begin repeat until getnextpacket < FPacketRecords; FIsBOF := false; FBCurrentRecord := FBRecordcount; end; procedure unSetDeleted(NullMask : pbyte); //inline; begin NullMask[0] := NullMask[0] and not 1; end; procedure SetDeleted(NullMask : pbyte); //inline; begin NullMask[0] := NullMask[0] or 1; end; function GetDeleted(NullMask : pbyte) : boolean; //inline; begin result := (NullMask[0] and 1) = 1; end; procedure unSetFieldIsNull(NullMask : pbyte;x : longint); //inline; begin inc(x); NullMask[x div 8] := (NullMask[x div 8]) and not (1 shl (x mod 8)); end; procedure SetFieldIsNull(NullMask : pbyte;x : longint); //inline; begin inc(x); NullMask[x div 8] := (NullMask[x div 8]) or (1 shl (x mod 8)); end; function GetFieldIsNull(NullMask : pbyte;x : longint) : boolean; //inline; begin inc(x); result := ord(NullMask[x div 8]) and (1 shl (x mod 8)) > 0 end; function TBufDataset.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; var x : longint; RecUpdBuf : PRecUpdateBuffer; FieldUpdBuf : PFieldUpdateBuffer; NullMask : pbyte; begin Result := grOK; case GetMode of gmPrior : if FIsBOF then result := grBOF else if FBCurrentRecord <= 0 then begin Result := grBOF; FBCurrentRecord := -1; end else begin Dec(FBCurrentRecord); FIsEof := false; end; gmCurrent : if (FBCurrentRecord < 0) or (FBCurrentRecord >= FBRecordCount) then Result := grError; gmNext : if FIsEOF then result := grEOF else if FBCurrentRecord >= (FBRecordCount - 1) then begin if getnextpacket > 0 then begin Inc(FBCurrentRecord); FIsBof := false; end else begin FIsEOF := true; result:=grEOF; end end else begin Inc(FBCurrentRecord); FIsBof := false; end; end; if Result = grOK then begin if GetDeleted(pbyte(FBBuffers[FBCurrentRecord])) then begin if getmode = gmCurrent then if DoCheck then begin Result := grError; DatabaseError(SDeletedRecord); exit; end else getmode := gmnext; Result := GetRecord(Buffer,getmode,DoCheck); exit end; with PBufBookmark(Buffer + RecordSize)^ do begin BookmarkData := FBCurrentRecord; BookmarkFlag := bfCurrent; end; move(FBBuffers[FBCurrentRecord]^,buffer^,RecordSize); // Cached Updates: If GetRecordUpdateBuffer(FBCurrentRecord,RecUpdBuf) then begin NullMask := pbyte(buffer); inc(buffer,FNullmaskSize); for x := 0 to FieldDefs.count-1 do begin if GetFieldUpdateBuffer(x,RecUpdBuf,FieldUpdBuf) then If not FieldUpdBuf^.IsNull then begin unSetFieldIsNull(NullMask,x); move(FieldUpdBuf^.NewValue^,buffer^,GetFieldSize(FieldDefs[x])); end else SetFieldIsNull(NullMask,x); Inc(Buffer, GetFieldSize(FieldDefs[x])); end; end; end else if (Result = grError) and doCheck then DatabaseError('No record'); end; function TBufDataset.GetRecordUpdateBuffer(rno : integer;var RecUpdBuf : PRecUpdateBuffer) : boolean; var r : integer; begin Result := False; for r := 0 to high(FUpdateBuffer) do if (FUpdateBuffer[r].RecordNo = rno) and (@FUpdateBuffer[r] <> FEditBuf) then // Neglect the edit-buffer begin RecUpdBuf := @FUpdateBuffer[r]; Result := True; Break; end; end; function TBufDataset.GetFieldUpdateBuffer(fieldno : integer;RecUpdBuf : PRecUpdateBuffer;var FieldUpdBuf : pFieldUpdateBuffer) : boolean; var f : integer; begin Result := False; for f := 0 to High(RecUpdBuf^.FieldsUpdateBuffer) do if RecUpdBuf^.FieldsUpdateBuffer[f].FieldNo = fieldno then begin FieldUpdBuf := @RecUpdBuf^.FieldsUpdateBuffer[f]; Result := True; Break; end; end; procedure TBufDataset.InternalSetToRecord(Buffer: PChar); begin FBCurrentRecord := PBufBookmark(Buffer + RecordSize)^.BookmarkData; FIsEOF := False; FIsBOF := False; end; procedure TBufDataset.SetBookmarkData(Buffer: PChar; Data: Pointer); begin PBufBookmark(Buffer + RecordSize)^.BookmarkData := PInteger(Data)^; end; procedure TBufDataset.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); begin PBufBookmark(Buffer + RecordSize)^.BookmarkFlag := Value; end; procedure TBufDataset.GetBookmarkData(Buffer: PChar; Data: Pointer); begin PInteger(Data)^ := PBufBookmark(Buffer + RecordSize)^.BookmarkData; end; function TBufDataset.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; begin Result := PBufBookmark(Buffer + RecordSize)^.BookmarkFlag; end; procedure TBufDataset.InternalGotoBookmark(ABookmark: Pointer); begin FBCurrentRecord := Plongint(ABookmark)^; FIsEOF := False; FIsBOF := False; end; function TBufDataset.getnextpacket : integer; var i : integer; b : boolean; begin i := 0; if FPacketRecords > 0 then begin if FBBufferCount < FBRecordCount+FPacketRecords then begin FBBufferCount := FBBuffercount + FPacketRecords; ReAllocMem(FBBuffers,FBBuffercount*SizeOf(PChar)); end; repeat FBBuffers[FBRecordCount+i] := AllocRecordBuffer; b := (loadbuffer(FBBuffers[FBRecordCount+i])<>grOk); inc(i); until (i = FPacketRecords) or b; if b then begin dec(i); FreeRecordBuffer(FBBuffers[FBRecordCount+i]); end; FBRecordCount := FBRecordCount + i; end; result := i; end; function TBufDataset.GetFieldSize(FieldDef : TFieldDef) : longint; begin case FieldDef.DataType of ftString, ftFixedChar: result := FieldDef.Size + 1; ftSmallint, ftInteger, ftword : result := sizeof(longint); ftBoolean : result := sizeof(wordbool); ftBCD : result := sizeof(currency); ftFloat : result := sizeof(double); ftTime, ftDate, ftDateTime : result := sizeof(TDateTime) else Result := 10 end; end; function TBufDataset.LoadBuffer(Buffer : PChar): TGetResult; var NullMask : pbyte; x : longint; begin if not Fetch then begin Result := grEOF; Exit; end; NullMask := pointer(buffer); fillchar(Nullmask^,FNullmaskSize,0); inc(buffer,FNullmaskSize); for x := 0 to FieldDefs.count-1 do begin if not LoadField(FieldDefs[x],buffer) then SetFieldIsNull(NullMask,x); inc(buffer,GetFieldSize(FieldDefs[x])); end; Result := grOK; end; function TBufDataset.GetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean): Boolean; begin Result := GetFieldData(Field, Buffer); end; function TBufDataset.GetFieldData(Field: TField; Buffer: Pointer): Boolean; var x : longint; CurrBuff : pchar; begin Result := False; If Field.Fieldno > 0 then // If = 0, then calculated field or something similar begin if state = dsOldValue then begin if FApplyingUpdates then CurrBuff := FBBuffers[fbcurrentrecord] // This makes it possible for ApplyUpdates to get values from deleted records else CurrBuff := FBBuffers[GetRecNo]; end else begin CurrBuff := ActiveBuffer; if not assigned(CurrBuff) or GetDeleted(pbyte(CurrBuff)) then begin result := false; exit; end; end; if GetFieldIsnull(pbyte(CurrBuff),Field.Fieldno-1) then begin result := false; exit; end; inc(Currbuff,FNullmaskSize); for x := 0 to FieldDefs.count-1 do begin if (Field.FieldName = FieldDefs[x].Name) then begin // a nil-buffer is allowed for the fields.isNull function if assigned(buffer) then Move(CurrBuff^, Buffer^, GetFieldSize(FieldDefs[x])); Result := True; Break; end else Inc(CurrBuff, GetFieldSize(FieldDefs[x])); end; end; end; procedure TBufDataset.SetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean); begin SetFieldData(Field,Buffer); end; procedure TBufDataset.SetFieldData(Field: TField; Buffer: Pointer); var x : longint; CurrBuff : pointer; NullMask : pbyte; FieldUpdBuf : PFieldUpdateBuffer; begin if not (state in [dsEdit, dsInsert]) then begin DatabaseErrorFmt(SNotInEditState,[NAme],self); exit; end; If Field.Fieldno > 0 then // If = 0, then calculated field or something begin CurrBuff := ActiveBuffer; NullMask := CurrBuff; inc(Currbuff,FNullmaskSize); for x := 0 to FieldDefs.count-1 do begin if (Field.FieldName = FieldDefs[x].Name) then begin if assigned(buffer) then begin Move(Buffer^, CurrBuff^, GetFieldSize(FieldDefs[x])); unSetFieldIsNull(NullMask,x); end else SetFieldIsNull(NullMask,x); // cached updates with FEditBuf^ do begin if not GetFieldUpdateBuffer(x,FEditBuf,FieldUpdBuf) then begin SetLength(FieldsUpdateBuffer,length(FieldsUpdateBuffer)+1); FieldUpdBuf := @FieldsUpdateBuffer[high(FieldsUpdateBuffer)]; GetMem(FieldUpdBuf^.NewValue,GetFieldSize(FieldDefs[x])); FieldUpdBuf^.FieldNo := x; end; if assigned(buffer) then begin Move(Buffer^, FieldUpdBuf^.NewValue^, GetFieldSize(FieldDefs[x])); FieldUpdBuf^.IsNull := False; end else FieldUpdBuf^.IsNull := True; end; Break; end else Inc(CurrBuff, GetFieldSize(FieldDefs[x])); end; if not (State in [dsCalcFields, dsFilter, dsNewValue]) then DataEvent(deFieldChange, Ptrint(Field)); end; end; procedure TBufDataset.InternalEdit; begin if not GetRecordUpdateBuffer(recno,FEditBuf) then begin If not assigned(FEditBuf) then begin SetLength(FUpdateBuffer,length(FUpdateBuffer)+1); FEditBuf := @FUpdateBuffer[high(FUpdateBuffer)]; end; FEditBuf^.UpdateKind := ukModify; FEditBuf^.RecordNo := getrecno; end; end; procedure TBufDataset.InternalInsert; begin if FBRecordCount > FBBufferCount-1 then begin inc(FBBufferCount); ReAllocMem(FBBuffers,FBBuffercount*SizeOf(PChar)); end; inc(FBRecordCount); FBCurrentRecord := FBRecordCount -1; FBBuffers[FBCurrentRecord] := AllocRecordBuffer; fillchar(FBBuffers[FBCurrentRecord]^,FNullmaskSize,255); unSetDeleted(pbyte(FBBuffers[FBCurrentRecord])); fillchar(ActiveBuffer^,FNullmaskSize,255); unSetDeleted(pbyte(ActiveBuffer)); // cached updates: If not assigned(FEditBuf) then begin SetLength(FUpdateBuffer,length(FUpdateBuffer)+1); FEditBuf := @FUpdateBuffer[high(FUpdateBuffer)]; end; FEditBuf^.RecordNo := FBCurrentRecord; FEditBuf^.UpdateKind := ukInsert; with PBufBookmark(ActiveBuffer + RecordSize)^ do begin BookmarkData := FBCurrentRecord; BookmarkFlag := bfInserted; end; end; procedure TBufDataset.InternalDelete; var tel : integer; begin SetDeleted(pbyte(FBBuffers[FBCurrentRecord])); SetDeleted(pbyte(ActiveBuffer)); inc(FBDeletedRecords); if GetRecordUpdateBuffer(recno,FEditBuf) and (FEditBuf^.UpdateKind = ukInsert) then begin if assigned(FEditBuf^.FieldsUpdateBuffer) then for tel := 0 to high(FEditBuf^.FieldsUpdateBuffer) do if not FEditBuf^.FieldsUpdateBuffer[tel].IsNull then freemem(FEditBuf^.FieldsUpdateBuffer[tel].NewValue); setlength(FEditBuf^.FieldsUpdateBuffer,0); FEditBuf^.RecordNo := -1; end else begin If not assigned(FEditBuf) then begin SetLength(FUpdateBuffer,length(FUpdateBuffer)+1); FEditBuf := @FUpdateBuffer[high(FUpdateBuffer)]; end; FEditBuf^.RecordNo := FBCurrentRecord; FEditBuf^.UpdateKind := ukDelete; end; FEditBuf := nil; end; function TBufDataset.ApplyRecUpdate(UpdateKind : TUpdateKind) : boolean; begin Result := False; end; procedure TBufDataset.CancelUpdates; var r,f : integer; begin for r := 0 to high(FUpdateBuffer) do begin if FUpdateBuffer[r].RecordNo > -1 then if FUpdateBuffer[r].UpdateKind = ukDelete then begin dec(FBDeletedRecords); unSetDeleted(pbyte(FBBuffers[FUpdateBuffer[r].RecordNo])); end else if FUpdateBuffer[r].UpdateKind = ukInsert then begin inc(FBDeletedRecords); SetDeleted(pbyte(FBBuffers[FUpdateBuffer[r].RecordNo])); end; for f := 0 to high(FUpdateBuffer[r].FieldsUpdateBuffer) do FreeMem(FUpdateBuffer[r].FieldsUpdateBuffer[f].newvalue); end; SetLength(FUpdateBuffer,0); if FOpen then Resync([]); end; procedure TBufDataset.ApplyUpdates; var SaveBookmark : Integer; r,i : Integer; buffer : PChar; x : integer; FieldUpdBuf : PFieldUpdateBuffer; NullMask : pbyte; begin CheckBrowseMode; if IsEmpty then exit; SaveBookMark := GetRecNo; r := 0; while r < Length(FUpdateBuffer) do begin if (@FUpdateBuffer[r] <> FEditBuf) and // Neglect edit-buffer (FUpdateBuffer[r].RecordNo <> -1) then // And the 'deleted' buffers begin FApplyingUpdates := true; if FUpdateBuffer[r].UpdateKind = ukDelete then InternalGotoBookmark(@(FUpdateBuffer[r].RecordNo)) else begin InternalGotoBookMark(@FUpdateBuffer[r].RecordNo); Resync([rmExact,rmCenter]); end; if ApplyRecUpdate(FUpdateBuffer[r].UpdateKind) then begin buffer := FBBuffers[FUpdateBuffer[r].RecordNo]; NullMask := pbyte(buffer); inc(buffer,FNullmaskSize); for x := 0 to FieldDefs.count-1 do begin if GetFieldUpdateBuffer(x,@FUpdateBuffer[r],FieldUpdBuf) then If not FieldUpdBuf^.IsNull then begin unSetFieldIsNull(NullMask,x); move(FieldUpdBuf^.NewValue^,buffer^,GetFieldSize(FieldDefs[x])); FreeMem(FieldUpdBuf^.NewValue); end else SetFieldIsNull(NullMask,x); Inc(Buffer, GetFieldSize(FieldDefs[x])); end; for i := r to high(FUpdateBuffer)-1 do FUpdateBuffer[i] := FupdateBuffer[i+1]; dec(r); SetLength(FUpdateBuffer,high(FUpdateBuffer)); end; FApplyingUpdates := False; end; inc(r); end; if not GetDeleted(pbyte(FBBuffers[savebookmark])) then begin InternalGotoBookMark(@SaveBookMark); Resync([rmExact,rmCenter]); end; end; procedure TBufDataset.InternalPost; begin if state in [dsEdit, dsInsert] then begin if Length(FUpdateBuffer[High(FUpdateBuffer)].FieldsUpdateBuffer) > 0 then FEditBuf := nil; end; end; procedure TBufDataset.InternalCancel; var tel : integer; begin if state in [dsEdit, dsInsert] then begin if state = dsInsert then begin SetDeleted(pbyte(FBBuffers[FBCurrentRecord])); SetDeleted(pbyte(ActiveBuffer)); inc(FBDeletedRecords); end; FEditBuf^.RecordNo := -1; // clear the fieldbuffers if assigned(FEditBuf^.FieldsUpdateBuffer) then for tel := 0 to high(FEditBuf^.FieldsUpdateBuffer) do if not FEditBuf^.FieldsUpdateBuffer[tel].IsNull then freemem(FEditBuf^.FieldsUpdateBuffer[tel].NewValue); setlength(FEditBuf^.FieldsUpdateBuffer,0); end; end; procedure TBufDataset.CalcRecordSize; var x : longint; begin FNullmaskSize := 1+((FieldDefs.count) div 8); FRecordSize := FNullmaskSize; for x := 0 to FieldDefs.count-1 do inc(FRecordSize, GetFieldSize(FieldDefs[x])); end; function TBufDataset.GetRecordSize : Word; begin result := FRecordSize; end; procedure TBufDataset.InternalInitRecord(Buffer: PChar); begin FillChar(Buffer^, FRecordSize, #0); end; procedure TBufDataset.SetRecNo(Value: Longint); begin GotoBookmark(@value); end; function TBufDataset.GetRecNo: Longint; begin GetBookmarkData(ActiveBuffer,@Result); end; function TBufDataset.IsCursorOpen: Boolean; begin Result := FOpen; end; Function TBufDataset.GetRecordCount: Longint; begin Result := FBRecordCount-FBDeletedRecords; end;