* Allow LIKE in filters

This commit is contained in:
Michaël Van Canneyt 2023-12-13 20:36:03 +01:00
parent 91021a0107
commit 5013439753
2 changed files with 101 additions and 15 deletions

View File

@ -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;

View File

@ -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);