Browse Source

* Allow LIKE in filters

Michaël Van Canneyt 1 year ago
parent
commit
5013439753
2 changed files with 101 additions and 15 deletions
  1. 100 15
      packages/fcl-base/src/fpexprpars.pas
  2. 1 0
      packages/fcl-db/src/jsondataset.pas

+ 100 - 15
packages/fcl-base/src/fpexprpars.pas

@@ -24,9 +24,9 @@ interface
 
 
 uses
 uses
 {$IFDEF FPC_DOTTEDUNITS} 
 {$IFDEF FPC_DOTTEDUNITS} 
-  System.Classes, System.SysUtils, System.Contnrs;
+  System.Classes, System.SysUtils, System.Contnrs, JSApi.JS;
 {$ELSE}
 {$ELSE}
-  Classes, SysUtils, contnrs;
+  Classes, SysUtils, contnrs, js;
 {$ENDIF}
 {$ENDIF}
 
 
 Type
 Type
@@ -35,7 +35,7 @@ Type
                 ttMod, ttMul, ttLeft, ttRight, ttLessThanEqual,
                 ttMod, ttMul, ttLeft, ttRight, ttLessThanEqual,
                 ttLargerThanEqual, ttunequal, ttNumber, ttString, ttIdentifier,
                 ttLargerThanEqual, ttunequal, ttNumber, ttString, ttIdentifier,
                 ttComma, ttAnd, ttOr, ttXor, ttTrue, ttFalse, ttNot, ttif,
                 ttComma, ttAnd, ttOr, ttXor, ttTrue, ttFalse, ttNot, ttif,
-                ttCase, ttPower, ttEOF); // keep ttEOF last
+                ttCase, ttPower, ttLike, ttEOF); // keep ttEOF last
 
 
   TExprFloat = Double;
   TExprFloat = Double;
 
 
@@ -45,7 +45,7 @@ Const
                   ttunequal, ttPower];
                   ttunequal, ttPower];
   ttComparisons = [ttLargerThan,ttLessthan,
   ttComparisons = [ttLargerThan,ttLessthan,
                    ttLargerThanEqual,ttLessthanEqual,
                    ttLargerThanEqual,ttLessthanEqual,
-                   ttEqual,ttUnequal];
+                   ttEqual,ttUnequal, ttLike];
 
 
 Type
 Type
 
 
@@ -66,6 +66,7 @@ Type
     FToken : String;
     FToken : String;
     FTokenType : TTokenType;
     FTokenType : TTokenType;
   private
   private
+    FAllowLike: Boolean;
     function GetCurrentChar: Char;
     function GetCurrentChar: Char;
     procedure ScanError(Msg: String);
     procedure ScanError(Msg: String);
   protected
   protected
@@ -88,6 +89,7 @@ Type
     Property Source : String Read FSource Write SetSource;
     Property Source : String Read FSource Write SetSource;
     Property Pos : Integer Read FPos;
     Property Pos : Integer Read FPos;
     Property CurrentChar : Char Read GetCurrentChar;
     Property CurrentChar : Char Read GetCurrentChar;
+    Property AllowLike : Boolean Read FAllowLike Write FAllowLike;
   end;
   end;
 
 
   EExprScanner = Class(Exception);
   EExprScanner = Class(Exception);
@@ -131,7 +133,7 @@ Type
   Protected
   Protected
     Procedure CheckSameNodeTypes;
     Procedure CheckSameNodeTypes;
   Public
   Public
-    Constructor Create(ALeft,ARight : TFPExprNode);
+    Constructor Create(ALeft,ARight : TFPExprNode); virtual;
     Destructor Destroy; override;
     Destructor Destroy; override;
     Procedure InitAggregate; override;
     Procedure InitAggregate; override;
     Procedure UpdateAggregate; override;
     Procedure UpdateAggregate; override;
@@ -196,6 +198,21 @@ Type
     Function AsString : string ; override;
     Function AsString : string ; override;
   end;
   end;
 
 
+  { TFPEqualOperation }
+
+  { TFPLikeOperation }
+
+  TFPLikeOperation = Class(TFPBooleanResultOperation)
+  Protected
+    FRE : TJSRegexp;
+    FLast : String;
+    Function GetNodeValue : TFPExpressionResult; override;
+  Public
+    procedure check; override;
+    Function AsString : string ; override;
+  end;
+
+
   { TFPUnequalOperation }
   { TFPUnequalOperation }
 
 
   TFPUnequalOperation = Class(TFPEqualOperation)
   TFPUnequalOperation = Class(TFPEqualOperation)
@@ -712,10 +729,12 @@ Type
     function GetAsCurrency: Currency;
     function GetAsCurrency: Currency;
     function GetAsInteger: NativeInt;
     function GetAsInteger: NativeInt;
     function GetAsString: String;
     function GetAsString: String;
+    function GetAllowLike: Boolean;
     function MatchNodes(Todo, Match: TFPExprNode): TFPExprNode;
     function MatchNodes(Todo, Match: TFPExprNode): TFPExprNode;
     procedure CheckNodes(var Left, Right: TFPExprNode);
     procedure CheckNodes(var Left, Right: TFPExprNode);
     procedure SetBuiltIns(const AValue: TBuiltInCategories);
     procedure SetBuiltIns(const AValue: TBuiltInCategories);
     procedure SetIdentifiers(const AValue: TFPExprIdentifierDefs);
     procedure SetIdentifiers(const AValue: TFPExprIdentifierDefs);
+    procedure SetAllowLike(AValue: Boolean);
   Protected
   Protected
     procedure ParserError(Msg: String);
     procedure ParserError(Msg: String);
     procedure SetExpression(const AValue: String); virtual;
     procedure SetExpression(const AValue: String); virtual;
@@ -751,6 +770,7 @@ Type
     Function HasAggregate : Boolean;
     Function HasAggregate : Boolean;
     Procedure InitAggregate;
     Procedure InitAggregate;
     Procedure UpdateAggregate;
     Procedure UpdateAggregate;
+    Property AllowLike : Boolean Read GetAllowLike Write SetAllowLike;
     Property AsFloat : TExprFloat Read GetAsFloat;
     Property AsFloat : TExprFloat Read GetAsFloat;
     Property AsCurrency : Currency Read GetAsCurrency;
     Property AsCurrency : Currency Read GetAsCurrency;
     Property AsInteger : NativeInt Read GetAsInteger;
     Property AsInteger : NativeInt Read GetAsInteger;
@@ -876,6 +896,7 @@ Resourcestring
   SErrCaseLabelNotAConst = 'Case label %d "%s" is not a constant expression';
   SErrCaseLabelNotAConst = 'Case label %d "%s" is not a constant expression';
   SErrCaseLabelType = 'Case label %d "%s" needs type %s, but has type %s';
   SErrCaseLabelType = 'Case label %d "%s" needs type %s, but has type %s';
   SErrCaseValueType = 'Case value %d "%s" needs type %s, but has type %s';
   SErrCaseValueType = 'Case value %d "%s" needs type %s, but has type %s';
+  SErrStringTypeRequired = 'Expression requires string type, got type "%s" for %s';
 
 
 { ---------------------------------------------------------------------
 { ---------------------------------------------------------------------
   Auxiliary functions
   Auxiliary functions
@@ -1243,14 +1264,14 @@ begin
   end;
   end;
 end;
 end;
 
 
-Procedure TFPExpressionScanner.SkipWhiteSpace;
+procedure TFPExpressionScanner.SkipWhiteSpace;
 
 
 begin
 begin
   While (FChar in WhiteSpace) and (FPos<=LSource) do
   While (FChar in WhiteSpace) and (FPos<=LSource) do
     NextPos;
     NextPos;
 end;
 end;
 
 
-Function TFPExpressionScanner.DoDelimiter : TTokenType;
+function TFPExpressionScanner.DoDelimiter: TTokenType;
 
 
 Var
 Var
   B : Boolean;
   B : Boolean;
@@ -1293,13 +1314,14 @@ begin
 
 
 end;
 end;
 
 
-Procedure TFPExpressionScanner.ScanError(Msg : String);
+procedure TFPExpressionScanner.ScanError(Msg: String);
 
 
 begin
 begin
   Raise EExprScanner.Create(Msg)
   Raise EExprScanner.Create(Msg)
 end;
 end;
 
 
-Function TFPExpressionScanner.DoString : TTokenType;
+
+function TFPExpressionScanner.DoString: TTokenType;
 
 
   Function TerminatingChar(C : Char) : boolean;
   Function TerminatingChar(C : Char) : boolean;
 
 
@@ -1351,7 +1373,7 @@ begin
 end;
 end;
 {$endif}
 {$endif}
 
 
-Function TFPExpressionScanner.DoNumber(AKind: TNumberKind) : TTokenType;
+function TFPExpressionScanner.DoNumber(AKind: TNumberKind): TTokenType;
 
 
 Var
 Var
   C : Char;
   C : Char;
@@ -1402,7 +1424,7 @@ begin
   Result:=ttNumber;
   Result:=ttNumber;
 end;
 end;
 
 
-Function TFPExpressionScanner.DoIdentifier : TTokenType;
+function TFPExpressionScanner.DoIdentifier: TTokenType;
 
 
 Var
 Var
   C : Char;
   C : Char;
@@ -1445,11 +1467,13 @@ begin
     Result:=ttcase
     Result:=ttcase
   else if (S='mod') then
   else if (S='mod') then
     Result:=ttMod
     Result:=ttMod
+  else if (S='like') and AllowLike then
+    Result:=ttLike
   else
   else
     Result:=ttIdentifier;
     Result:=ttIdentifier;
 end;
 end;
 
 
-Function TFPExpressionScanner.GetToken : TTokenType;
+function TFPExpressionScanner.GetToken: TTokenType;
 
 
 Var
 Var
   C : Char;
   C : Char;
@@ -1588,6 +1612,11 @@ begin
   FIdentifiers.Assign(AValue)
   FIdentifiers.Assign(AValue)
 end;
 end;
 
 
+procedure TFPExpressionParser.SetAllowLike(AValue: Boolean);
+begin
+  FScanner.AllowLike:=aValue;
+end;
+
 function TFPExpressionParser.Evaluate: TFPExpressionResult;
 function TFPExpressionParser.Evaluate: TFPExpressionResult;
 begin
 begin
   If (FExpression='') then
   If (FExpression='') then
@@ -1598,12 +1627,12 @@ begin
 end;
 end;
 
 
 
 
-procedure TFPExpressionParser.EvaluateExpression(Out Result: TFPExpressionResult);
+procedure TFPExpressionParser.EvaluateExpression(out Result: TFPExpressionResult);
 begin
 begin
   Result:=Evaluate;
   Result:=Evaluate;
 end;
 end;
 
 
-function TFPExpressionParser.ExtractNode(Var N : TFPExprNode) : Boolean;
+function TFPExpressionParser.ExtractNode(var N: TFPExprNode): Boolean;
 begin
 begin
   Result:=Assigned(FExprNode);
   Result:=Assigned(FExprNode);
   if Result then
   if Result then
@@ -1619,7 +1648,7 @@ begin
   Raise EExprParser.Create(Msg);
   Raise EExprParser.Create(Msg);
 end;
 end;
 
 
-Class function TFPExpressionParser.ConvertNode(Todo : TFPExprNode; ToType : TResultType): TFPExprNode;
+class function TFPExpressionParser.ConvertNode(Todo: TFPExprNode; ToType: TResultType): TFPExprNode;
 begin
 begin
   Result:=ToDo;
   Result:=ToDo;
   Case ToDo.NodeType of
   Case ToDo.NodeType of
@@ -1714,6 +1743,11 @@ begin
   Result:=String(Res.resValue);
   Result:=String(Res.resValue);
 end;
 end;
 
 
+function TFPExpressionParser.GetAllowLike: Boolean;
+begin
+  Result:=FScanner.AllowLike;
+end;
+
 {
 {
   Checks types of todo and match. If ToDO can be converted to it matches
   Checks types of todo and match. If ToDO can be converted to it matches
   the type of match, then a node is inserted.
   the type of match, then a node is inserted.
@@ -1824,6 +1858,7 @@ begin
         ttLargerThanEqual  : C:=TFPGreaterThanEqualOperation;
         ttLargerThanEqual  : C:=TFPGreaterThanEqualOperation;
         ttEqual            : C:=TFPEqualOperation;
         ttEqual            : C:=TFPEqualOperation;
         ttUnequal          : C:=TFPUnequalOperation;
         ttUnequal          : C:=TFPUnequalOperation;
+        ttLike             : C:=TFPLikeOperation;
       Else
       Else
         ParserError(SErrUnknownComparison)
         ParserError(SErrUnknownComparison)
       end;
       end;
@@ -3238,6 +3273,56 @@ begin
   Result.ResultType:=rtBoolean;
   Result.ResultType:=rtBoolean;
 end;
 end;
 
 
+{ TFPLikeOperation }
+
+function TFPLikeOperation.GetNodeValue: TFPExpressionResult;
+
+const
+  RESpecials = '([\$\+\[\]\(\)\\\.\*\^\?\|])';
+
+Var
+  S: String;
+  RE : TJSRegexp;
+
+begin
+  // We need to recreate a regexp every time, since the RE can change any time the right-hand expression changes
+  S:=String(Right.NodeValue.resValue);
+  if (FLast<>'') and (S=FLast) then
+    RE:=FRE
+  else
+    begin
+    FLast:=S;
+    S:=TJSString(S).replace(TJSRegexp.new(RESpecials,'g'),'\$1');
+    S:=StringReplace(S,'%','(.*)',[rfReplaceAll]);
+    S:=StringReplace(S,'_','(.)',[rfReplaceAll]);
+    try
+      // Writeln('Ex: ',FLast,' -> ',S);
+      FRE:=TJSRegexp.New(S,'i');
+      Re:=FRe;
+    except
+      Result.resValue:=False;
+      exit;
+    end;
+    end;
+  Result.resValue:=RE.Test(String(Left.NodeValue.resValue));
+  // Writeln('Checking : ',flast,' (',FRE.Source,') on ',Left.NodeValue.resValue,' : ',  Result.resValue);
+end;
+
+
+procedure TFPLikeOperation.check;
+begin
+  if Left.NodeType<>rtString then
+    RaiseParserError(SErrStringTypeRequired,[ResultTypeName(Left.NodeType),Left.AsString]);
+  if Right.NodeType<>rtString then
+    RaiseParserError(SErrStringTypeRequired,[ResultTypeName(Right.NodeType),Right.AsString]);
+  inherited check;
+end;
+
+function TFPLikeOperation.AsString: string;
+begin
+  Result:=Left.AsString+' LIKE '+Right.AsString;
+end;
+
 { TFPUnequalOperation }
 { TFPUnequalOperation }
 
 
 function TFPUnequalOperation.AsString: string;
 function TFPUnequalOperation.AsString: string;

+ 1 - 0
packages/fcl-db/src/jsondataset.pas

@@ -1392,6 +1392,7 @@ Var
 
 
 begin
 begin
   Result:=FilterExpressionClass.Create(Self);
   Result:=FilterExpressionClass.Create(Self);
+  Result.AllowLike:=True;
   for I:=0 to Fields.Count-1 do
   for I:=0 to Fields.Count-1 do
     if not (Fields[i].DataType in [ftBlob,ftMemo]) then
     if not (Fields[i].DataType in [ftBlob,ftMemo]) then
       Result.Identifiers.AddVariable(Fields[i].FieldName,FieldTypeToExpressionType(Fields[i].DataType),@GetFilterField);
       Result.Identifiers.AddVariable(Fields[i].FieldName,FieldTypeToExpressionType(Fields[i].DataType),@GetFilterField);