diff --git a/packages/fcl-db/jsondataset.pas b/packages/fcl-db/jsondataset.pas index 0e76f2e..5a6bb74 100644 --- a/packages/fcl-db/jsondataset.pas +++ b/packages/fcl-db/jsondataset.pas @@ -5,7 +5,7 @@ unit JSONDataset; interface uses - Types, JS, DB, Classes, SysUtils, typinfo; + Types, JS, DB, Classes, SysUtils, typinfo, fpexprpars; type TBaseJSONDataset = Class; @@ -255,14 +255,28 @@ type // When editing, this object is edited. FEditIdx : Integer; FEditRow : JSValue; + // When filtering, this is the current row; + FFilterRow : JSValue; FUseDateTimeFormatFields: Boolean; FRowType: TJSONRowType; + FFilterExpression : TFPExpressionParser; + function GetFilterField(const AName: String): TFPExpressionResult; procedure SetActiveIndex(AValue: String); procedure SetIndexes(AValue: TJSONIndexDefs); procedure SetMetaData(AValue: TJSObject); procedure SetRows(AValue: TJSArray); procedure SetRowType(AValue: TJSONRowType); protected + // Determine filter value type based on field type + function FieldTypeToExpressionType(aDataType: TFieldType): TResultType; virtual; + // Callback for IsNull filter function. + function GetFilterIsNull(const Args: TExprParameterArray): TFPExpressionResult; virtual; + // Expression parser class. Override this to create a customized version. + function FilterExpressionClass: TFPExpressionParserClass; virtual; + // Create filter expression. + function CreateFilterExpression: TFPExpressionParser; virtual; + // Function called to check if current buffer should be accepted. + function DoFilterRecord: Boolean; virtual; // Override this to return customized version. function CreateIndexDefs: TJSONIndexDefs; virtual; // override this to return a customized version if you are so inclined @@ -287,6 +301,8 @@ type procedure InternalCancel; override; procedure InternalInitFieldDefs; override; procedure InternalSetToRecord(Buffer: TDataRecord); override; + procedure SetFilterText(const Value: string); override; + procedure SetFiltered(Value: Boolean); override; function GetFieldClass(FieldType: TFieldType): TFieldClass; override; function IsCursorOpen: Boolean; override; // Bookmark operations @@ -1142,36 +1158,141 @@ begin FCurrentIndex:=FDefaultIndex; end; +function TBaseJSONDataSet.FilterExpressionClass : TFPExpressionParserClass; + +begin + Result:=TFPExpressionParser; +end; + +function TBaseJSONDataSet.GetFilterIsNull(Const Args : TExprParameterArray) : TFPExpressionResult; + +begin + Result.ResultType:=rtBoolean; + Result.ResValue:=FieldByName(String(Args[0].resValue)).IsNull; +end; + +function TBaseJSONDataSet.FieldTypeToExpressionType(aDataType : TFieldType) : TResultType; + +begin + Case aDataType of + ftMemo, + ftFixedChar, + ftString : Result:=rtString; + ftInteger, + ftAutoInc, + ftLargeInt : Result:=rtInteger; + ftBoolean : Result:=rtBoolean; + ftFloat : Result:=rtFloat; + ftDate, + ftTime, + ftDateTime : Result:=rtDateTime; + else + DatabaseErrorFmt('Fields of type %s are not supported in filter expressions.',[Fieldtypenames[aDataType]],Self); + end; +end; + +function TBaseJSONDataSet.GetFilterField(Const AName : String) : TFPExpressionResult; + +Var + F : TField; + C : Currency; + +begin + F:=FieldByName(aName); + Result.resultType:=FieldTypeToExpressionType(F.DataType); + case Result.resultType of + rtBoolean : Result.resValue:=F.AsBoolean; + rtInteger : Result.resValue:=F.AsLargeInt; + rtFloat : Result.resValue:=F.AsFloat; + rtDateTime : Result.resValue:=F.AsDateTime; + rtString : Result.resValue:=F.AsString; + rtCurrency : + begin + C:=Currency(F.AsFloat); + Result.resValue:=C; + end; + end; +// Writeln('Filtering field ',aName,'value: ',result.resValue); +end; + + +function TBaseJSONDataSet.CreateFilterExpression : TFPExpressionParser; + +Var + I : Integer; + +begin + Result:=FilterExpressionClass.Create(Self); + for I:=0 to Fields.Count-1 do + Result.Identifiers.AddVariable(Fields[i].FieldName,FieldTypeToExpressionType(Fields[i].DataType),@GetFilterField); + Result.Identifiers.AddFunction('IsNull','B','S',@GetFilterIsNull); + Result.Expression:=Filter; +end; + +function TBaseJSONDataSet.DoFilterRecord : Boolean; + +Var + DS : TDatasetState; + +begin + // Writeln('Filtering'); + Result:=True; + DS:=SetTempState(dsFilter); + try + if Assigned(OnFilterRecord) then + begin + OnFilterRecord(Self,Result); + if Not Result then + Exit; + end; + if not Filtered or (Filter='') then + Exit; + if (FFilterExpression=Nil) then + FFilterExpression:=CreateFilterExpression; + Result:=FFilterExpression.AsBoolean; + finally + RestoreState(DS); + end; +end; + function TBaseJSONDataSet.GetRecord(Var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult; Var BkmIdx : Integer; - + recordAccepted : Boolean; begin Result := grOK; // default - case GetMode of - gmNext: // move on - if fCurrent < fCurrentIndex.Count - 1 then - Inc (fCurrent) - else - Result := grEOF; // end of file - gmPrior: // move back - if fCurrent > 0 then - Dec (fCurrent) - else - Result := grBOF; // begin of file - gmCurrent: // check if empty - if fCurrent >= fCurrentIndex.Count then - Result := grEOF; - end; - if Result = grOK then // read the data - begin - BkmIdx:=FCurrentIndex.RecordIndex[FCurrent]; - Buffer.Data:=FRows[bkmIdx]; - Buffer.BookmarkFlag := bfCurrent; - Buffer.Bookmark:=BkmIdx; - CalculateFields(Buffer); + Repeat + recordAccepted:=True; + case GetMode of + gmNext: // move on + if fCurrent < fCurrentIndex.Count - 1 then + Inc (fCurrent) + else + Result := grEOF; // end of file + gmPrior: // move back + if fCurrent > 0 then + Dec (fCurrent) + else + Result := grBOF; // begin of file + gmCurrent: // check if empty + if fCurrent >= fCurrentIndex.Count then + Result := grEOF; end; + if Result = grOK then // read the data + begin + BkmIdx:=FCurrentIndex.RecordIndex[FCurrent]; + Buffer.Data:=FRows[bkmIdx]; + Buffer.BookmarkFlag := bfCurrent; + Buffer.Bookmark:=BkmIdx; + CalculateFields(Buffer); + if Filtered then + begin + FFilterRow:=Buffer.Data; + recordAccepted:=DoFilterRecord; + end; + end; + until recordAccepted; end; function TBaseJSONDataSet.GetRecordCount: Integer; @@ -1355,6 +1476,22 @@ begin FCurrent:=FCurrentIndex.FindRecord(Integer(Buffer.Bookmark)); end; +procedure TBaseJSONDataSet.SetFilterText(const Value: string); +begin + inherited SetFilterText(Value); + FreeAndNil(FFilterExpression); + if Active then + Resync([rmCenter]); +end; + +procedure TBaseJSONDataSet.SetFiltered(Value: Boolean); +begin + inherited SetFiltered(Value); + FreeAndNil(FFilterExpression); + if Active then + Resync([rmCenter]); +end; + function TBaseJSONDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass; begin If UseDateTimeFormatFields and (FieldType in [ftDate,ftDateTime,ftTime]) then @@ -1445,6 +1582,8 @@ var begin if State in [dsCalcFields,dsInternalCalc] then R:=CalcBuffer.data + else if (State=dsFilter) then + R:=FFilterRow else if (FEditIdx=Buffer.Bookmark) then begin if State=dsOldValue then @@ -1525,6 +1664,7 @@ end; destructor TBaseJSONDataSet.Destroy; begin + FreeAndNil(FFilterExpression); FreeAndNil(FIndexes); FEditIdx:=-1; FreeData;