{ Only ffdb.pas uses this unit. ffdb is using only this classes or types: TFilterExpr PExprNode TExprParser !!! CODE TAKEN FROM DELPHI7 - BORLAND CODE !!! } { *************************************************************************** } { } { Kylix and Delphi Cross-Platform Visual Component Library } { } { Copyright (c) 1995, 2001 Borland Software Corporation } { } { *************************************************************************** } {$I ffdefine.inc} //Original called in Delphi: DbCommon.pas // called only from ffdb.pas unit lazffdelphi1; {$T-,H+,X+,R-} interface {$IFDEF MSWINDOWS} uses Windows, Variants, Classes, DB, {$ifdef fpc}lazffdelphi2{$else}SqlTimSt{$endif}; {$ENDIF} {$IFDEF LINUX} uses Libc, Variants, Classes, DB, {$ifdef fpc}lazffdelphi2{$else}SqlTimSt{$endif}; {$ENDIF} type TCANOperator = ( coNOTDEFINED, { } coISBLANK, { coUnary; is operand blank. } coNOTBLANK, { coUnary; is operand not blank. } coEQ, { coBinary, coCompare; equal. } coNE, { coBinary; NOT equal. } coGT, { coBinary; greater than. } coLT, { coBinary; less than. } coGE, { coBinary; greater or equal. } coLE, { coBinary; less or equal. } coNOT, { coUnary; NOT } coAND, { coBinary; AND } coOR, { coBinary; OR } coTUPLE2, { coUnary; Entire record is operand. } coFIELD2, { coUnary; operand is field } coCONST2, { coUnary; operand is constant } coMINUS, { coUnary; minus. } coADD, { coBinary; addition. } coSUB, { coBinary; subtraction. } coMUL, { coBinary; multiplication. } coDIV, { coBinary; division. } coMOD, { coBinary; modulo division. } coREM, { coBinary; remainder of division. } coSUM, { coBinary, accumulate sum of. } coCOUNT, { coBinary, accumulate count of. } coMIN, { coBinary, find minimum of. } coMAX, { coBinary, find maximum of. } coAVG, { coBinary, find average of. } coCONT, { coBinary; provides a link between two } coUDF2, { coBinary; invokes a User defined fn } coCONTINUE2, { coUnary; Stops evaluating records } coLIKE, { coCompare, extended binary compare } coIN, { coBinary field in list of values } coLIST2, { List of constant values of same type } coUPPER, { coUnary: upper case } coLOWER, { coUnary: lower case } coFUNC2, { coFunc: Function } coLISTELEM2, { coListElem: List Element } coASSIGN { coBinary: Field assignment } ); NODEClass = ( { Node Class } nodeNULL, { Null node } nodeUNARY, { Node is a unary } nodeBINARY, { Node is a binary } nodeCOMPARE, { Node is a compare } nodeFIELD, { Node is a field } nodeCONST, { Node is a constant } nodeTUPLE, { Node is a record } nodeCONTINUE, { Node is a continue node } nodeUDF, { Node is a UDF node } nodeLIST, { Node is a LIST node } nodeFUNC, { Node is a Function node } nodeLISTELEM { Node is a List Element node } ); {Soner: Don't used in FlashFiler or in interface part const CANEXPRSIZE = 10; // SizeOf(CANExpr) CANHDRSIZE = 8; // SizeOf(CANHdr) CANEXPRVERSION = 2; } type TExprData = array of Byte; TFieldMap = array[TFieldType] of Byte; { TFilterExpr } type TParserOption = (poExtSyntax, poAggregate, poDefaultExpr, poUseOrigNames, poFieldNameGiven, poFieldDepend); TParserOptions = set of TParserOption; TExprNodeKind = (enField, enConst, enOperator, enFunc); TExprScopeKind = (skField, skAgg, skConst); PExprNode = ^TExprNode; TExprNode = record FNext: PExprNode; FKind: TExprNodeKind; FPartial: Boolean; FOperator: TCANOperator; FData: Variant; FLeft: PExprNode; FRight: PExprNode; FDataType: TFieldType; FDataSize: Integer; FArgs: TList; FScopeKind: TExprScopeKind; end; TFilterExpr = class private FDataSet: TDataSet; FFieldMap: TFieldMap; FOptions: TFilterOptions; FParserOptions: TParserOptions; FNodes: PExprNode; FExprBuffer: TExprData; FExprBufSize: Integer; FExprNodeSize: Integer; FExprDataSize: Integer; FFieldName: string; FDependentFields: TBits; function FieldFromNode(Node: PExprNode): TField; function GetExprData(Pos, Size: Integer): PChar; function PutConstBCD(const Value: Variant; Decimals: Integer): Integer; function PutConstFMTBCD(const Value: Variant; Decimals: Integer): Integer; function PutConstBool(const Value: Variant): Integer; function PutConstDate(const Value: Variant): Integer; function PutConstDateTime(const Value: Variant): Integer; function PutConstSQLTimeStamp(const Value: Variant): Integer; function PutConstFloat(const Value: Variant): Integer; function PutConstInt(DataType: TFieldType; const Value: Variant): Integer; function PutConstNode(DataType: TFieldType; Data: PChar; Size: Integer): Integer; function PutConstStr(const Value: string): Integer; function PutConstTime(const Value: Variant): Integer; function PutData(Data: PChar; Size: Integer): Integer; function PutExprNode(Node: PExprNode; ParentOp: TCANOperator): Integer; function PutFieldNode(Field: TField; Node: PExprNode): Integer; function PutNode(NodeType: NodeClass; OpType: TCANOperator; OpCount: Integer): Integer; procedure SetNodeOp(Node, Index, Data: Integer); function PutConstant(Node: PExprNode): Integer; function GetFieldByName(Name: string) : TField; public constructor Create(DataSet: TDataSet; Options: TFilterOptions; ParseOptions: TParserOptions; const FieldName: string; DepFields: TBits; FieldMap: TFieldMap); destructor Destroy; override; function NewCompareNode(Field: TField; Operator: TCANOperator; const Value: Variant): PExprNode; function NewNode(Kind: TExprNodeKind; Operator: TCANOperator; const Data: Variant; Left, Right: PExprNode): PExprNode; function GetFilterData(Root: PExprNode): TExprData; property DataSet: TDataSet write FDataSet; end; { TExprParser } TExprToken = (etEnd, etSymbol, etName, etLiteral, etLParen, etRParen, etEQ, etNE, etGE, etLE, etGT, etLT, etADD, etSUB, etMUL, etDIV, etComma, etLIKE, etISNULL, etISNOTNULL, etIN); TExprParser = class private FDecimalSeparator: Char; FFilter: TFilterExpr; FFieldMap: TFieldMap; FText: string; FSourcePtr: PChar; FTokenPtr: PChar; FTokenString: string; FStrTrue: string; FStrFalse: string; FToken: TExprToken; FPrevToken: TExprToken; FFilterData: TExprData; FNumericLit: Boolean; FDataSize: Integer; FParserOptions: TParserOptions; FFieldName: string; FDataSet: TDataSet; FDependentFields: TBits; procedure NextToken; function NextTokenIsLParen : Boolean; function ParseExpr: PExprNode; function ParseExpr2: PExprNode; function ParseExpr3: PExprNode; function ParseExpr4: PExprNode; function ParseExpr5: PExprNode; function ParseExpr6: PExprNode; function ParseExpr7: PExprNode; function TokenName: string; function TokenSymbolIs(const S: string): Boolean; function TokenSymbolIsFunc(const S: string) : Boolean; procedure GetFuncResultInfo(Node: PExprNode); procedure TypeCheckArithOp(Node: PExprNode); procedure GetScopeKind(Root, Left, Right : PExprNode); public constructor Create(DataSet: TDataSet; const Text: string; Options: TFilterOptions; ParserOptions: TParserOptions; const FieldName: string; DepFields: TBits; FieldMap: TFieldMap); destructor Destroy; override; procedure SetExprParams(const Text: string; Options: TFilterOptions; ParserOptions: TParserOptions; const FieldName: string); property FilterData: TExprData read FFilterData; property DataSize: Integer read FDataSize; end; { Field Origin parser } {Soner: Don't used in FlashFiler or in interface part type TFieldInfo = record DatabaseName: string; TableName: string; OriginalFieldName: string; end; function GetFieldInfo(const Origin: string; var FieldInfo: TFieldInfo): Boolean; } { SQL Parser } {Soner: Don't used in FlashFiler or in interface part type TSQLToken = (stUnknown, stTableName, stFieldName, stAscending, stDescending, stSelect, stFrom, stWhere, stGroupBy, stHaving, stUnion, stPlan, stOrderBy, stForUpdate, stEnd, stPredicate, stValue, stIsNull, stIsNotNull, stLike, stAnd, stOr, stNumber, stAllFields, stComment, stDistinct); const SQLSections = [stSelect, stFrom, stWhere, stGroupBy, stHaving, stUnion, stPlan, stOrderBy, stForUpdate]; function NextSQLToken(var p: PChar; out Token: string; CurSection: TSQLToken): TSQLToken; function GetIndexForOrderBy(const SQL: string; DataSet: TDataSet): TIndexDef; function GetTableNameFromSQL(const SQL: string): string; function GetTableNameFromQuery(const SQL: string): string; function AddParamSQLForDetail(Params: TParams; SQL: string; Native: Boolean; QuoteChar: string = ''): string; function IsMultiTableQuery(const SQL: string): Boolean; } implementation uses SysUtils, dbconst, FMTBcd; //soner this was in interface part ............. const CANEXPRSIZE = 10; { SizeOf(CANExpr) } CANHDRSIZE = 8; { SizeOf(CANHdr) } CANEXPRVERSION = 2; type TFieldInfo = record DatabaseName: string; TableName: string; OriginalFieldName: string; end; TSQLToken = (stUnknown, stTableName, stFieldName, stAscending, stDescending, stSelect, stFrom, stWhere, stGroupBy, stHaving, stUnion, stPlan, stOrderBy, stForUpdate, stEnd, stPredicate, stValue, stIsNull, stIsNotNull, stLike, stAnd, stOr, stNumber, stAllFields, stComment, stDistinct); const SQLSections = [stSelect, stFrom, stWhere, stGroupBy, stHaving, stUnion, stPlan, stOrderBy, stForUpdate]; // .................... end of soner this was in interface part ............. //FROM Delphi/DBConsts.pas ================================ resourcestring SExprTermination = 'Filterausdruck fehlerhaft abgeschlossen'; SExprNameError = 'Nicht begrenzter Feldname'; SExprStringError = 'Nicht begrenzte String-Konstante'; SExprInvalidChar = 'Ungültiges Zeichen in Filterausdruck: ''%s'''; SExprNoLParen = '''('' erwartet, aber %s vorgefunden'; SExprNoRParen = ''')'' erwartet, jedoch %s vorgefunden'; SExprNoRParenOrComma = ''')'' oder '','' erwartet, jedoch %s vorgefunden'; SExprExpected = 'Ausdruck erwartet, jedoch %s vorgefunden'; SExprBadField = 'Feld ''%s'' kann nicht in einem Filterausdruck verwendet werden'; SExprBadNullTest = 'NULL ist nur mit ''='' und ''<>'' erlaubt'; SExprRangeError = 'Konstante außerhalb des zulässigen Wertebereichs'; SExprNotBoolean = 'Feld ''%s'' ist kein boolescher Typ'; SExprIncorrect = 'Ungültiger Filterausdruck'; SExprNothing = 'leer'; SExprTypeMis = 'Fehlende Typübereinstimmung im Ausdruck'; SExprBadScope = 'Die Operation kann keine Zusammenfassungswerte mit Datensatzwerten mischen'; SExprNoArith = 'Arithmetische Filterausdrücke werden nicht unterstützt'; SExprNotAgg = 'Der Ausdruck ist kein Aggregat-Ausdruck'; SExprBadConst = 'Die Konstante ist nicht vom richtigen Typ %s'; SExprNoAggFilter = 'In Filtern sind keine Aggregationsausdrücke erlaubt'; SExprEmptyInList = 'Die IN-Liste darf nicht leer bleiben'; SExprNoAggOnCalcs = 'Feld ''%s'' ist nicht der korrekte Typ eines berechneten Feldes für eine Aggregierung; verwenden Sie internalcalc'; SInvalidKeywordUse = 'Ungültige Verwendung eines Schlüsselworts'; STextFalse = 'Falsch'; STextTrue = 'Wahr'; //END FROM DBConsts.pas ================================ { SQL Parser } function NextSQLToken(var p: PChar; out Token: string; CurSection: TSQLToken): TSQLToken; var DotStart: Boolean; function NextTokenIs(Value: string; var Str: string): Boolean; var Tmp: PChar; S: string; begin Tmp := p; NextSQLToken(Tmp, S, CurSection); Result := AnsiCompareText(Value, S) = 0; if Result then begin Str := Str + ' ' + S; p := Tmp; end; end; function GetSQLToken(var Str: string): TSQLToken; var l: PChar; s: string; begin if Length(Str) = 0 then Result := stEnd else if (Str = '*') and (CurSection = stSelect) then Result := stAllFields else if DotStart then Result := stFieldName else if (AnsiCompareText('DISTINCT', Str) = 0) and (CurSection = stSelect) then Result := stDistinct else if (AnsiCompareText('ASC', Str) = 0) or (AnsiCompareText('ASCENDING', Str) = 0)then Result := stAscending else if (AnsiCompareText('DESC', Str) = 0) or (AnsiCompareText('DESCENDING', Str) = 0)then Result := stDescending else if AnsiCompareText('SELECT', Str) = 0 then Result := stSelect else if AnsiCompareText('AND', Str) = 0 then Result := stAnd else if AnsiCompareText('OR', Str) = 0 then Result := stOr else if AnsiCompareText('LIKE', Str) = 0 then Result := stLike else if (AnsiCompareText('IS', Str) = 0) then begin if NextTokenIs('NULL', Str) then Result := stIsNull else begin l := p; s := Str; if NextTokenIs('NOT', Str) and NextTokenIs('NULL', Str) then Result := stIsNotNull else begin p := l; Str := s; Result := stValue; end; end; end else if AnsiCompareText('FROM', Str) = 0 then Result := stFrom else if AnsiCompareText('WHERE', Str) = 0 then Result := stWhere else if (AnsiCompareText('GROUP', Str) = 0) and NextTokenIs('BY', Str) then Result := stGroupBy else if AnsiCompareText('HAVING', Str) = 0 then Result := stHaving else if AnsiCompareText('UNION', Str) = 0 then Result := stUnion else if AnsiCompareText('PLAN', Str) = 0 then Result := stPlan else if (AnsiCompareText('FOR', Str) = 0) and NextTokenIs('UPDATE', Str) then Result := stForUpdate else if (AnsiCompareText('ORDER', Str) = 0) and NextTokenIs('BY', Str) then Result := stOrderBy else if AnsiCompareText('NULL', Str) = 0 then Result := stValue else if CurSection = stFrom then Result := stTableName else Result := stFieldName; end; var TokenStart: PChar; procedure StartToken; begin if not Assigned(TokenStart) then TokenStart := p; end; var Literal: Char; Mark: PChar; begin TokenStart := nil; DotStart := False; while True do begin case p^ of '"','''','`': begin StartToken; Literal := p^; Mark := p; repeat Inc(p) until (p^ in [Literal,#0]); if p^ = #0 then begin p := Mark; Inc(p); end else begin Inc(p); SetString(Token, TokenStart, p - TokenStart); Mark := PChar(Token); Token := AnsiExtractQuotedStr(Mark, Literal); if DotStart then Result := stFieldName else if p^ = '.' then Result := stTableName else Result := stValue; Exit; end; end; '/': begin StartToken; Inc(p); if p^ in ['/','*'] then begin if p^ = '*' then begin repeat Inc(p) until (p = #0) or ((p^ = '*') and (p[1] = '/')); end else while not (p^ in [#0, #10, #13]) do Inc(p); SetString(Token, TokenStart, p - TokenStart); Result := stComment; Exit; end; end; ' ', #10, #13, ',', '(': begin if Assigned(TokenStart) then begin SetString(Token, TokenStart, p - TokenStart); Result := GetSQLToken(Token); Exit; end else while (p^ in [' ', #10, #13, ',', '(']) do Inc(p); end; '.': begin if Assigned(TokenStart) then begin SetString(Token, TokenStart, p - TokenStart); Result := stTableName; Exit; end else begin DotStart := True; Inc(p); end; end; '=','<','>': begin if not Assigned(TokenStart) then begin TokenStart := p; while p^ in ['=','<','>'] do Inc(p); SetString(Token, TokenStart, p - TokenStart); Result := stPredicate; Exit; end; Inc(p); end; '0'..'9': begin if not Assigned(TokenStart) then begin TokenStart := p; while p^ in ['0'..'9','.'] do Inc(p); SetString(Token, TokenStart, p - TokenStart); Result := stNumber; Exit; end else Inc(p); end; #0: begin if Assigned(TokenStart) then begin SetString(Token, TokenStart, p - TokenStart); Result := GetSQLToken(Token); Exit; end else begin Result := stEnd; Token := ''; Exit; end; end; else StartToken; Inc(p); end; end; end; function AddParamSQLForDetail(Params: TParams; SQL: string; Native: Boolean; QuoteChar: string = ''): string; const SWhere = ' where '; { do not localize } SAnd = ' and '; { do not localize } function GenerateParamSQL: string; var I: Integer; ParamName: string; begin for I := 0 to Params.Count -1 do begin if QuoteChar = '"' then ParamName := '"' + StringReplace(Params[I].Name, '"', '""', [rfReplaceAll] ) + '"' else ParamName := QuoteChar + Params[I].Name +QuoteChar; if I > 0 then Result := Result + SAnd; if Native then Result := Result + format('%s = ?', [ParamName]) else Result := Result + format('%s = :%s', [ParamName, ParamName]); end; if pos(SWhere, LowerCase(Result)) > 0 then Result := SAnd + Result else Result := SWhere + Result; end; function AddWhereClause: string; var Start: PChar; Rest, FName: string; SQLToken, CurSection: TSQLToken; begin Start := PChar(SQL); CurSection := stUnknown; repeat SQLToken := NextSQLToken(Start, FName, CurSection); until SQLToken in [stFrom, stEnd]; if SQLToken = stFrom then NextSQLToken(Start, FName, CurSection); Rest := string(Start); if Rest = '' then Result := SQL + ' ' + GenerateParamSQL else Result := Copy(SQL, 1, pos(Rest, SQL)) + ' ' + GenerateParamSQL + Rest; end; begin Result := SQL; if (Params.Count > 0) then Result := AddWhereClause; end; function GetTableNameFromSQL(const SQL: string): string; var Start: PChar; Token: string; SQLToken, CurSection: TSQLToken; begin Result := ''; Start := PChar(SQL); CurSection := stUnknown; repeat SQLToken := NextSQLToken(Start, Token, CurSection); if SQLToken in SQLSections then CurSection := SQLToken; until SQLToken in [stEnd, stFrom]; if SQLToken = stFrom then begin repeat SQLToken := NextSQLToken(Start, Token, CurSection); if SQLToken in SQLSections then CurSection := SQLToken else // stValue is returned if TableNames contain quote chars. if (SQLToken = stTableName) or (SQLToken = stValue) then begin Result := Token; while (Start[0] = '.') and not (SQLToken in [stEnd]) do begin SQLToken := NextSqlToken(Start, Token, CurSection); Result := Result + '.' + Token; end; Exit; end; until (CurSection <> stFrom) or (SQLToken in [stEnd, stTableName]); end; end; // SQL might be a direct tablename; function GetTableNameFromQuery(const SQL: string): string; begin if pos( 'select', lowercase(SQL) ) < 1 then Result := SQL else Result := GetTableNameFromSQL(SQL); end; function IsMultiTableQuery(const SQL: string): Boolean; const SInnerJoin = 'inner join '; { do not localize } SOuterJoin = 'outer join '; { do not localize } var Start: PChar; SResult, Token: string; SQLToken, CurSection: TSQLToken; begin SResult := ''; Start := PChar(SQL); CurSection := stUnknown; Result := True; repeat SQLToken := NextSQLToken(Start, Token, CurSection); if SQLToken in SQLSections then CurSection := SQLToken; until SQLToken in [stEnd, stFrom]; if SQLToken = stFrom then begin repeat SQLToken := NextSQLToken(Start, Token, CurSection); if SQLToken in SQLSections then CurSection := SQLToken else // stValue is returned if TableNames contain quote chars. if (SQLToken = stTableName) or (SQLToken = stValue) then begin SResult := Token; while (Start[0] = '.') and not (SQLToken in [stEnd]) do begin SQLToken := NextSqlToken(Start, Token, CurSection); SResult := SResult + '.' + Token; end; if (Start[0] = ',') or (Start[1] = ',') then exit; NextSqlToken(Start, Token, CurSection); if Assigned(AnsiStrPos(Start, PChar(SInnerJoin))) or Assigned(AnsiStrPos(Start, PChar(SOuterJoin))) then Exit; SQLToken := NextSqlToken(Start, Token, CurSection); if SQLToken = stTableName then Exit; Result := False; Exit; end; until (CurSection <> stFrom) or (SQLToken in [stEnd, stTableName]); end; end; function GetIndexForOrderBy(const SQL: string; DataSet: TDataSet): TIndexDef; function AddField(const Fields, NewField: string): string; begin Result := Fields; if Fields <> '' then Result := Fields + ';' + NewField else Result := NewField; end; var Start: PChar; Token, LastField, SaveField: string; SQLToken, CurSection: TSQLToken; FieldIndex: Integer; begin Result := nil; Start := PChar(SQL); CurSection := stUnknown; repeat SQLToken := NextSQLToken(Start, Token, CurSection); if SQLToken in SQLSections then CurSection := SQLToken; until SQLToken in [stEnd, stOrderBy]; if SQLToken = stOrderBy then begin Result := TIndexDef.Create(nil); try LastField := ''; repeat SQLToken := NextSQLToken(Start, Token, CurSection); if SQLToken in SQLSections then CurSection := SQLToken else case SQLToken of stTableName: ; stFieldName: begin LastField := Token; { Verify that we parsed a valid field name, not something like "UPPER(Foo)" } if not Assigned(Dataset.FindField(LastField)) then continue; Result.Fields := AddField(Result.Fields, LastField); SaveField := LastField; end; stAscending: ; stDescending: Result.DescFields := AddField(Result.DescFields, SaveField); stNumber: begin FieldIndex := StrToInt(Token); if DataSet.FieldCount >= FieldIndex then LastField := DataSet.Fields[FieldIndex - 1].FieldName else if DataSet.FieldDefs.Count >= FieldIndex then LastField := DataSet.FieldDefs[FieldIndex - 1].Name else { DB2 specific syntax "FETCH FIRST n ROWS ONLY" is blocked here, so commenting out the following line } //SysUtils.Abort; continue; Result.Fields := AddField(Result.Fields, LastField); end; end; until (CurSection <> stOrderBy) or (SQLToken = stEnd); finally if Result.Fields = '' then begin Result.Free; Result := nil; end; end; end; end; function GetFieldInfo(const Origin: string; var FieldInfo: TFieldInfo): Boolean; var Current: PChar; Values: array[0..4] of string; I: Integer; function GetPChar(const S: string): PChar; begin if S <> '' then Result := PChar(Pointer(S)) else Result := ''; end; procedure Split(const S: string); begin Current := PChar(Pointer(S)); end; function NextItem: string; var C: PChar; I: PChar; Terminator: Char; Ident: array[0..1023] of Char; begin Result := ''; C := Current; I := Ident; while C^ in ['.',' ',#0] do if C^ = #0 then Exit else Inc(C); Terminator := '.'; if C^ = '"' then begin Terminator := '"'; Inc(C); end; while not (C^ in [Terminator, #0]) do begin if C^ in LeadBytes then begin I^ := C^; Inc(C); Inc(I); end else if C^ = '\' then begin Inc(C); if C^ in LeadBytes then begin I^ := C^; Inc(C); Inc(I); end; if C^ = #0 then Dec(C); end; I^ := C^; Inc(C); Inc(I); end; SetString(Result, Ident, I - Ident); if (Terminator = '"') and (C^ <> #0) then Inc(C); Current := C; end; function PopValue: PChar; begin if I >= 0 then begin Result := GetPChar(Values[I]); Dec(I); end else Result := ''; end; begin Result := False; if (Origin = '') then Exit; Split(Origin); I := -1; repeat Inc(I); Values[I] := NextItem; until (Values[I] = '') or (I = High(Values)); if I = High(Values) then Exit; Dec(I); FieldInfo.OriginalFieldName := StrPas(PopValue); FieldInfo.TableName := StrPas(PopValue); FieldInfo.DatabaseName := StrPas(PopValue); Result := (FieldInfo.OriginalFieldName <> '') and (FieldInfo.TableName <> ''); end; const StringFieldTypes = [ftString, ftFixedChar, ftWideString, ftGuid]; BlobFieldTypes = [ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary, ftOraBlob, ftOraClob]; function IsNumeric(DataType: TFieldType): Boolean; begin Result := DataType in [ftSmallint, ftInteger, ftWord, ftFloat, ftCurrency, ftBCD, ftAutoInc, ftLargeint, ftFMTBcd]; end; function IsTemporal(DataType: TFieldType): Boolean; begin Result := DataType in [ftDate, ftTime, ftDateTime, ftTimeStamp]; end; { TFilterExpr } constructor TFilterExpr.Create(DataSet: TDataSet; Options: TFilterOptions; ParseOptions: TParserOptions; const FieldName: string; DepFields: TBits; FieldMap: TFieldMap); begin FFieldMap := FieldMap; FDataSet := DataSet; FOptions := Options; FFieldName := FieldName; FParserOptions := ParseOptions; FDependentFields := DepFields; end; destructor TFilterExpr.Destroy; var Node: PExprNode; begin SetLength(FExprBuffer, 0); while FNodes <> nil do begin Node := FNodes; FNodes := Node^.FNext; if (Node^.FKind = enFunc) and (Node^.FArgs <> nil) then Node^.FArgs.Free; Dispose(Node); end; end; function TFilterExpr.FieldFromNode(Node: PExprNode): TField; begin Result := GetFieldByName(Node^.FData); if not (Result.FieldKind in [fkData, fkInternalCalc]) then DatabaseErrorFmt(SExprBadField, [Result.FieldName]); end; function TFilterExpr.GetExprData(Pos, Size: Integer): PChar; begin SetLength(FExprBuffer, FExprBufSize + Size); Move(FExprBuffer[Pos], FExprBuffer[Pos + Size], FExprBufSize - Pos); Inc(FExprBufSize, Size); Result := PChar(FExprBuffer) + Pos; end; function TFilterExpr.GetFilterData(Root: PExprNode): TExprData; begin FExprBufSize := CANExprSize; SetLength(FExprBuffer, FExprBufSize); PutExprNode(Root, coNOTDEFINED); PWord(@FExprBuffer[0])^ := CANEXPRVERSION; { iVer } PWord(@FExprBuffer[2])^ := FExprBufSize; { iTotalSize } PWord(@FExprBuffer[4])^ := $FFFF; { iNodes } PWord(@FExprBuffer[6])^ := CANEXPRSIZE; { iNodeStart } PWord(@FExprBuffer[8])^ := FExprNodeSize + CANEXPRSIZE; { iLiteralStart } Result := FExprBuffer; end; function TFilterExpr.NewCompareNode(Field: TField; Operator: TCANOperator; const Value: Variant): PExprNode; var ConstExpr: PExprNode; begin ConstExpr := NewNode(enConst, coNOTDEFINED, Value, nil, nil); ConstExpr^.FDataType := Field.DataType; ConstExpr^.FDataSize := Field.Size; Result := NewNode(enOperator, Operator, Unassigned, NewNode(enField, coNOTDEFINED, Field.FieldName, nil, nil), ConstExpr); end; function TFilterExpr.NewNode(Kind: TExprNodeKind; Operator: TCANOperator; const Data: Variant; Left, Right: PExprNode): PExprNode; var Field : TField; begin New(Result); with Result^ do begin FNext := FNodes; FKind := Kind; FPartial := False; FOperator := Operator; FData := Data; FLeft := Left; FRight := Right; end; FNodes := Result; if Kind = enField then begin Field := GetFieldByName(Data); if Field = nil then DatabaseErrorFmt(SFieldNotFound, [Data]); Result^.FDataType := Field.DataType; Result^.FDataSize := Field.Size; end; end; function TFilterExpr.PutConstBCD(const Value: Variant; Decimals: Integer): Integer; var C: Currency; BCD: TBcd; begin if VarType(Value) = varString then C := StrToCurr(string(TVarData(Value).VString)) else C := Value; CurrToBCD(C, BCD, 32, Decimals); Result := PutConstNode(ftBCD, @BCD, 18); end; function TFilterExpr.PutConstFMTBCD(const Value: Variant; Decimals: Integer): Integer; var BCD: TBcd; begin if VarType(Value) = varString then BCD := StrToBcd(string(TVarData(Value).VString)) else BCD := VarToBcd(Value); Result := PutConstNode(ftBCD, @BCD, 18); end; function TFilterExpr.PutConstBool(const Value: Variant): Integer; var B: WordBool; begin B := Value; Result := PutConstNode(ftBoolean, @B, SizeOf(WordBool)); end; function TFilterExpr.PutConstDate(const Value: Variant): Integer; var DateTime: TDateTime; TimeStamp: TTimeStamp; begin if VarType(Value) = varString then DateTime := StrToDate(string(TVarData(Value).VString)) else DateTime := VarToDateTime(Value); TimeStamp := DateTimeToTimeStamp(DateTime); Result := PutConstNode(ftDate, @TimeStamp.Date, 4); end; function TFilterExpr.PutConstDateTime(const Value: Variant): Integer; var DateTime: TDateTime; DateData: Double; begin if VarType(Value) = varString then DateTime := StrToDateTime(string(TVarData(Value).VString)) else DateTime := VarToDateTime(Value); DateData := TimeStampToMSecs(DateTimeToTimeStamp(DateTime)); Result := PutConstNode(ftDateTime, @DateData, 8); end; function TFilterExpr.PutConstSQLTimeStamp(const Value: Variant): Integer; var TimeStamp: TSQLTimeStamp; begin if VarType(Value) = varString then TimeStamp := StrToSQLTimeStamp(string(TVarData(Value).VString)) else TimeStamp := VarToSQLTimeStamp(Value); Result := PutConstNode(ftTimeStamp, @TimeStamp, 16); end; function TFilterExpr.PutConstFloat(const Value: Variant): Integer; var F: Double; begin if VarType(Value) = varString then F := StrToFloat(string(TVarData(Value).VString)) else F := Value; Result := PutConstNode(ftFloat, @F, SizeOf(Double)); end; function TFilterExpr.PutConstInt(DataType: TFieldType; const Value: Variant): Integer; var I, Size: Integer; begin if VarType(Value) = varString then I := StrToInt(string(TVarData(Value).VString)) else I := Value; Size := 2; case DataType of ftSmallint: if (I < -32768) or (I > 32767) then DatabaseError(SExprRangeError); ftWord: if (I < 0) or (I > 65535) then DatabaseError(SExprRangeError); else Size := 4; end; Result := PutConstNode(DataType, @I, Size); end; function TFilterExpr.PutConstNode(DataType: TFieldType; Data: PChar; Size: Integer): Integer; begin Result := PutNode(nodeCONST, coCONST2, 3); SetNodeOp(Result, 0, FFieldMap[DataType]); SetNodeOp(Result, 1, Size); SetNodeOp(Result, 2, PutData(Data, Size)); end; function TFilterExpr.PutConstStr(const Value: string): Integer; var Str: string; Buffer: array[0..255] of Char; begin if Length(Value) >= SizeOf(Buffer) then Str := Copy(Value, 1, SizeOf(Buffer) - 1) else Str := Value; FDataSet.Translate(PChar(Str), Buffer, True); Result := PutConstNode(ftString, Buffer, Length(Str) + 1); end; function TFilterExpr.PutConstTime(const Value: Variant): Integer; var DateTime: TDateTime; TimeStamp: TTimeStamp; begin if VarType(Value) = varString then DateTime := StrToTime(string(TVarData(Value).VString)) else DateTime := VarToDateTime(Value); TimeStamp := DateTimeToTimeStamp(DateTime); Result := PutConstNode(ftTime, @TimeStamp.Time, 4); end; function TFilterExpr.PutData(Data: PChar; Size: Integer): Integer; begin Move(Data^, GetExprData(FExprBufSize, Size)^, Size); Result := FExprDataSize; Inc(FExprDataSize, Size); end; function TFilterExpr.PutConstant(Node: PExprNode): Integer; begin Result := 0; case Node^.FDataType of ftSmallInt, ftInteger, ftWord, ftAutoInc: Result := PutConstInt(Node^.FDataType, Node^.FData); ftFloat, ftCurrency: Result := PutConstFloat(Node^.FData); ftString, ftWideString, ftFixedChar, ftGuid: {$ifdef fpc} if VarIsArray(Node^.FData) then //soner solves : "Invalid Variant Type Cast": Result := PutConstStr(Node^.FData[0]) else {$endif} Result := PutConstStr(Node^.FData); ftDate: Result := PutConstDate(Node^.FData); ftTime: Result := PutConstTime(Node^.FData); ftDateTime: Result := PutConstDateTime(Node^.FData); ftTimeStamp: Result := PutConstSQLTimeStamp(Node^.FData); ftBoolean: Result := PutConstBool(Node^.FData); ftBCD: Result := PutConstBCD(Node^.FData, Node^.FDataSize); ftFMTBcd: Result := PutConstFMTBCD(Node^.FData, Node^.FDataSize); else DatabaseErrorFmt(SExprBadConst, [Node^.FData]); end; end; function TFilterExpr.PutExprNode(Node: PExprNode; ParentOp: TCANOperator): Integer; const ReverseOperator: array[coEQ..coLE] of TCANOperator = (coEQ, coNE, coLT, coGT, coLE, coGE); BoolFalse: WordBool = False; var Field: TField; Left, Right, Temp : PExprNode; LeftPos, RightPos, ListElem, PrevListElem, I: Integer; Operator: TCANOperator; CaseInsensitive, PartialLength, L: Integer; S: string; begin Result := 0; case Node^.FKind of enField: begin Field := FieldFromNode(Node); if (ParentOp in [coOR, coNOT, coAND, coNOTDEFINED]) and (Field.DataType = ftBoolean) then begin Result := PutNode(nodeBINARY, coNE, 2); SetNodeOp(Result, 0, PutFieldNode(Field, Node)); SetNodeOp(Result, 1, PutConstNode(ftBoolean, @BoolFalse, SizeOf(WordBool))); end else Result := PutFieldNode(Field, Node); end; enConst: Result := PutConstant(Node); enOperator: case Node^.FOperator of coIN: begin Result := PutNode(nodeBINARY, coIN, 2); SetNodeOp(Result, 0, PutExprNode(Node^.FLeft,Node^.FOperator)); ListElem := PutNode(nodeLISTELEM, coLISTELEM2, 2); SetNodeOp(Result, 1, ListElem); PrevListElem := ListElem; for I := 0 to Node^.FArgs.Count - 1 do begin LeftPos := PutExprNode(Node^.FArgs.Items[I],Node^.FOperator); if I = 0 then begin SetNodeOp(PrevListElem, 0, LeftPos); SetNodeOp(PrevListElem, 1, 0); end else begin ListElem := PutNode(nodeLISTELEM, coLISTELEM2, 2); SetNodeOp(ListElem, 0, LeftPos); SetNodeOp(ListElem, 1, 0); SetNodeOp(PrevListElem, 1, ListElem); PrevListElem := ListElem; end; end; end; coNOT, coISBLANK, coNOTBLANK: begin Result := PutNode(nodeUNARY, Node^.FOperator, 1); SetNodeOp(Result, 0, PutExprNode(Node^.FLeft,Node^.FOperator)); end; coEQ..coLE, coAND,coOR, coADD..coDIV, coLIKE, coASSIGN: begin Operator := Node^.FOperator; Left := Node^.FLeft; Right := Node^.FRight; if (Operator in [coEQ..coLE]) and (Right^.FKind = enField) and (Left^.FKind <> enField) then begin Temp := Left; Left := Right; Right := Temp; Operator := ReverseOperator[Operator]; end; Result := 0; if (Left^.FKind = enField) and (Right^.FKind = enConst) and ((Node^.FOperator = coEQ) or (Node^.FOperator = coNE) or (Node^.FOperator = coLIKE)) then begin if VarIsNull(Right^.FData) then begin case Node^.FOperator of coEQ: Operator := coISBLANK; coNE: Operator := coNOTBLANK; else DatabaseError(SExprBadNullTest); end; Result := PutNode(nodeUNARY, Operator, 1); SetNodeOp(Result, 0, PutExprNode(Left,Node^.FOperator)); end else if (Right^.FDataType in StringFieldTypes) then begin {$ifdef fpc} if VarIsArray(Right^.FData) then //soner solves : "Invalid Variant Type Cast": s:=Right^.FData[0] else {$endif} S := Right^.FData; //soner this dont work, i get "Invalid Variant Type Cast": VarToStr(Right^.FData) L := Length(S); if L <> 0 then begin CaseInsensitive := 0; PartialLength := 0; if foCaseInsensitive in FOptions then CaseInsensitive := 1; if Node^.FPartial then PartialLength := L else if not (foNoPartialCompare in FOptions) and (L > 1) and (S[L] = '*') then begin Delete(S, L, 1); PartialLength := L - 1; end; if (CaseInsensitive <> 0) or (PartialLength <> 0) then begin Result := PutNode(nodeCOMPARE, Operator, 4); SetNodeOp(Result, 0, CaseInsensitive); SetNodeOp(Result, 1, PartialLength); SetNodeOp(Result, 2, PutExprNode(Left,Node^.FOperator)); SetNodeOp(Result, 3, PutConstStr(S)); end; end; end; end; if Result = 0 then begin if (Operator = coISBLANK) or (Operator = coNOTBLANK) then begin Result := PutNode(nodeUNARY, Operator, 1); LeftPos := PutExprNode(Left,Node^.FOperator); SetNodeOp(Result, 0, LeftPos); end else begin Result := PutNode(nodeBINARY, Operator, 2); LeftPos := PutExprNode(Left,Node^.FOperator); RightPos := PutExprNode(Right,Node^.FOperator); SetNodeOp(Result, 0, LeftPos); SetNodeOp(Result, 1, RightPos); end; end; end; end; enFunc: begin Result := PutNode(nodeFUNC, coFUNC2, 2); SetNodeOp(Result, 0, PutData(PChar(string(Node^.FData)), Length(string(Node^.FData)) + 1)); if Node^.FArgs <> nil then begin ListElem := PutNode(nodeLISTELEM, coLISTELEM2, 2); SetNodeOp(Result, 1, ListElem); PrevListElem := ListElem; for I := 0 to Node^.FArgs.Count - 1 do begin LeftPos := PutExprNode(Node^.FArgs.Items[I],Node^.FOperator); if I = 0 then begin SetNodeOp(PrevListElem, 0, LeftPos); SetNodeOp(PrevListElem, 1, 0); end else begin ListElem := PutNode(nodeLISTELEM, coLISTELEM2, 2); SetNodeOp(ListElem, 0, LeftPos); SetNodeOp(ListElem, 1, 0); SetNodeOp(PrevListElem, 1, ListElem); PrevListElem := ListElem; end; end; end else SetNodeOp(Result, 1, 0); end; end; end; function TFilterExpr.PutFieldNode(Field: TField; Node: PExprNode): Integer; var Buffer: array[0..255] of Char; begin if poFieldNameGiven in FParserOptions then FDataSet.Translate(PChar(Field.FieldName), Buffer, True) else FDataSet.Translate(PChar(string(Node^.FData)), Buffer, True); Result := PutNode(nodeFIELD, coFIELD2, 2); SetNodeOp(Result, 0, Field.FieldNo); SetNodeOp(Result, 1, PutData(Buffer, StrLen(Buffer) + 1)); end; function TFilterExpr.PutNode(NodeType: NodeClass; OpType: TCANOperator; OpCount: Integer): Integer; var Size: Integer; Data: PChar; begin Size := CANHDRSIZE + OpCount * SizeOf(Word); Data := GetExprData(CANEXPRSIZE + FExprNodeSize, Size); PInteger(@Data[0])^ := Integer(NodeType); { CANHdr.nodeClass } PInteger(@Data[4])^ := Integer(OpType); { CANHdr.coOp } Result := FExprNodeSize; Inc(FExprNodeSize, Size); end; procedure TFilterExpr.SetNodeOp(Node, Index, Data: Integer); begin PWordArray(PChar(FExprBuffer) + (CANEXPRSIZE + Node + CANHDRSIZE))^[Index] := Data; end; function TFilterExpr.GetFieldByName(Name: string) : TField; var I: Integer; F: TField; FieldInfo: TFieldInfo; begin Result := nil; if poFieldNameGiven in FParserOptions then Result := FDataSet.FieldByName(FFieldName) else if poUseOrigNames in FParserOptions then begin for I := 0 to FDataset.FieldCount - 1 do begin F := FDataSet.Fields[I]; if GetFieldInfo(F.Origin, FieldInfo) and (AnsiCompareStr(Name, FieldInfo.OriginalFieldName) = 0) then begin Result := F; Exit; end; end; end; if Result = nil then Result := FDataSet.FieldByName(Name); if (Result <> nil) and (Result.FieldKind = fkCalculated) and (poAggregate in FParserOptions) then DatabaseErrorFmt(SExprNoAggOnCalcs, [Result.FieldName]); if (poFieldDepend in FParserOptions) and (Result <> nil) and (FDependentFields <> nil) then FDependentFields[Result.FieldNo-1] := True; end; constructor TExprParser.Create(DataSet: TDataSet; const Text: string; Options: TFilterOptions; ParserOptions: TParserOptions; const FieldName: string; DepFields: TBits; FieldMap: TFieldMap); begin FDecimalSeparator := DecimalSeparator; FFieldMap := FieldMap; FStrTrue := STextTrue; FStrFalse := STextFalse; FDataSet := DataSet; FDependentFields := DepFields; FFilter := TFilterExpr.Create(DataSet, Options, ParserOptions, FieldName, DepFields, FieldMap); if Text <> '' then SetExprParams(Text, Options, ParserOptions, FieldName); end; destructor TExprParser.Destroy; begin FFilter.Free; end; procedure TExprParser.SetExprParams(const Text: string; Options: TFilterOptions; ParserOptions: TParserOptions; const FieldName: string); var Root, DefField: PExprNode; begin FParserOptions := ParserOptions; if FFilter <> nil then FFilter.Free; FFilter := TFilterExpr.Create(FDataSet, Options, ParserOptions, FieldName, FDependentFields, FFieldMap); FText := Text; FSourcePtr := PChar(Text); FFieldName := FieldName; NextToken; Root := ParseExpr; if FToken <> etEnd then DatabaseError(SExprTermination); if (poAggregate in FParserOptions) and (Root^.FScopeKind <> skAgg) then DatabaseError(SExprNotAgg); if (not (poAggregate in FParserOptions)) and (Root^.FScopeKind = skAgg) then DatabaseError(SExprNoAggFilter); if poDefaultExpr in ParserOptions then begin DefField := FFilter.NewNode(enField, coNOTDEFINED, FFieldName, nil, nil); if (IsTemporal(DefField^.FDataType) and (Root^.FDataType in StringFieldTypes)) or ((DefField^.FDataType = ftBoolean ) and (Root^.FDataType in StringFieldTypes)) then Root^.FDataType := DefField^.FDataType; if not ((IsTemporal(DefField^.FDataType) and IsTemporal(Root^.FDataType)) or (IsNumeric(DefField^.FDataType) and IsNumeric(Root^.FDataType)) or ((DefField^.FDataType in StringFieldTypes) and (Root^.FDataType in StringFieldTypes)) or ((DefField^.FDataType = ftBoolean) and (Root^.FDataType = ftBoolean))) then DatabaseError(SExprTypeMis); Root := FFilter.NewNode(enOperator, coASSIGN, Unassigned, Root, DefField); end; if not (poAggregate in FParserOptions) and not(poDefaultExpr in ParserOptions) and (Root^.FDataType <> ftBoolean ) then DatabaseError(SExprIncorrect); FFilterData := FFilter.GetFilterData(Root); FDataSize := FFilter.FExprBufSize; end; function TExprParser.NextTokenIsLParen : Boolean; var P : PChar; begin P := FSourcePtr; while (P^ <> #0) and (P^ <= ' ') do Inc(P); Result := P^ = '('; end; function EndOfLiteral(var P : PChar): Boolean; var FName: String; PTemp: PChar; begin Inc(P); Result := P^ <> ''''; if Result then begin // now, look for 'John's Horse' if AnsiStrScan(P, '''') <> Nil then // found another ' begin PTemp := P; // don't advance P while PTemp[0] in [ ' ', ')' ] do Inc(PTemp); if NextSQLToken(PTemp, FName, stValue) in [stFieldName, stUnknown] then begin // 'John's Horse' case: not really end of literal Result := False; Dec(P); end; end; end; end; procedure TExprParser.NextToken; type ASet = Set of Char; var P, TokenStart: PChar; L: Integer; StrBuf: array[0..255] of Char; function IsKatakana(const Chr: Byte): Boolean; begin {$IFDEF MSWINDOWS} Result := (SysLocale.PriLangID = LANG_JAPANESE) and (Chr in [$A1..$DF]); {$ENDIF} {$IFDEF LINUX} Result := False; {$ENDIF} end; procedure Skip(TheSet: ASet); begin while TRUE do begin if P^ in LeadBytes then Inc(P, 2) else if (P^ in TheSet) or IsKatakana(Byte(P^)) then Inc(P) else Exit; end; end; begin FPrevToken := FToken; FTokenString := ''; P := FSourcePtr; while (P^ <> #0) and (P^ <= ' ') do Inc(P); if (P^ <> #0) and (P^ = '/') and (P[1] <> #0) and (P[1] = '*')then begin P := P + 2; while (P^ <> #0) and (P^ <> '*') do Inc(P); if (P^ = '*') and (P[1] <> #0) and (P[1] = '/') then P := P + 2 else DatabaseErrorFmt(SExprInvalidChar, [P^]); end; while (P^ <> #0) and (P^ <= ' ') do Inc(P); FTokenPtr := P; case P^ of 'A'..'Z', 'a'..'z', '_', #$81..#$fe: begin TokenStart := P; if not SysLocale.FarEast then begin Inc(P); while P^ in ['A'..'Z', 'a'..'z', '0'..'9', '_', '.', '[', ']'] do Inc(P); end else Skip(['A'..'Z', 'a'..'z', '0'..'9', '_', '.', '[', ']']); SetString(FTokenString, TokenStart, P - TokenStart); FToken := etSymbol; if CompareText(FTokenString, 'LIKE') = 0 then { do not localize } FToken := etLIKE else if CompareText(FTokenString, 'IN') = 0 then { do not localize } FToken := etIN else if CompareText(FTokenString, 'IS') = 0 then { do not localize } begin while (P^ <> #0) and (P^ <= ' ') do Inc(P); TokenStart := P; Skip(['A'..'Z', 'a'..'z']); SetString(FTokenString, TokenStart, P - TokenStart); if CompareText(FTokenString, 'NOT')= 0 then { do not localize } begin while (P^ <> #0) and (P^ <= ' ') do Inc(P); TokenStart := P; Skip(['A'..'Z', 'a'..'z']); SetString(FTokenString, TokenStart, P - TokenStart); if CompareText(FTokenString, 'NULL') = 0 then FToken := etISNOTNULL else DatabaseError(SInvalidKeywordUse); end else if CompareText (FTokenString, 'NULL') = 0 then { do not localize } begin FToken := etISNULL; end else DatabaseError(SInvalidKeywordUse); end; end; '[': begin Inc(P); TokenStart := P; P := AnsiStrScan(P, ']'); if P = nil then DatabaseError(SExprNameError); SetString(FTokenString, TokenStart, P - TokenStart); FToken := etName; Inc(P); end; '''': begin Inc(P); L := 0; while True do begin if P^ = #0 then DatabaseError(SExprStringError); if P^ = '''' then if EndOfLiteral(P) then Break; if L < SizeOf(StrBuf) then begin StrBuf[L] := P^; Inc(L); end; Inc(P); end; SetString(FTokenString, StrBuf, L); FToken := etLiteral; FNumericLit := False; end; '-', '0'..'9': begin if (FPrevToken <> etLiteral) and (FPrevToken <> etName) and (FPrevToken <> etSymbol)and (FPrevToken <> etRParen) then begin TokenStart := P; Inc(P); while (P^ in ['0'..'9', FDecimalSeparator, 'e', 'E', '+', '-']) do Inc(P); if ((P-1)^ = ',') and (FDecimalSeparator = ',') and (P^ = ' ') then Dec(P); SetString(FTokenString, TokenStart, P - TokenStart); FToken := etLiteral; FNumericLit := True; end else begin FToken := etSUB; Inc(P); end; end; '(': begin Inc(P); FToken := etLParen; end; ')': begin Inc(P); FToken := etRParen; end; '<': begin Inc(P); case P^ of '=': begin Inc(P); FToken := etLE; end; '>': begin Inc(P); FToken := etNE; end; else FToken := etLT; end; end; '=': begin Inc(P); FToken := etEQ; end; '>': begin Inc(P); if P^ = '=' then begin Inc(P); FToken := etGE; end else FToken := etGT; end; '+': begin Inc(P); FToken := etADD; end; '*': begin Inc(P); FToken := etMUL; end; '/': begin Inc(P); FToken := etDIV; end; ',': begin Inc(P); FToken := etComma; end; #0: FToken := etEnd; else DatabaseErrorFmt(SExprInvalidChar, [P^]); end; FSourcePtr := P; end; function TExprParser.ParseExpr: PExprNode; begin Result := ParseExpr2; while TokenSymbolIs('OR') do begin NextToken; Result := FFilter.NewNode(enOperator, coOR, Unassigned, Result, ParseExpr2); GetScopeKind(Result, Result^.FLeft, Result^.FRight); Result^.FDataType := ftBoolean; end; end; function TExprParser.ParseExpr2: PExprNode; begin Result := ParseExpr3; while TokenSymbolIs('AND') do begin NextToken; Result := FFilter.NewNode(enOperator, coAND, Unassigned, Result, ParseExpr3); GetScopeKind(Result, Result^.FLeft, Result^.FRight); Result^.FDataType := ftBoolean; end; end; function TExprParser.ParseExpr3: PExprNode; begin if TokenSymbolIs('NOT') then begin NextToken; Result := FFilter.NewNode(enOperator, coNOT, Unassigned, ParseExpr4, nil); Result^.FDataType := ftBoolean; end else Result := ParseExpr4; GetScopeKind(Result, Result^.FLeft, Result^.FRight); end; function TExprParser.ParseExpr4: PExprNode; const Operators: array[etEQ..etLT] of TCANOperator = ( coEQ, coNE, coGE, coLE, coGT, coLT); var Operator: TCANOperator; Left, Right: PExprNode; begin Result := ParseExpr5; if (FToken in [etEQ..etLT]) or (FToken = etLIKE) or (FToken = etISNULL) or (FToken = etISNOTNULL) or (FToken = etIN) then begin case FToken of etEQ..etLT: Operator := Operators[FToken]; etLIKE: Operator := coLIKE; etISNULL: Operator := coISBLANK; etISNOTNULL: Operator := coNOTBLANK; etIN: Operator := coIN; else Operator := coNOTDEFINED; end; NextToken; Left := Result; if Operator = coIN then begin if FToken <> etLParen then DatabaseErrorFmt(SExprNoLParen, [TokenName]); NextToken; Result := FFilter.NewNode(enOperator, coIN, Unassigned, Left, nil); Result.FDataType := ftBoolean; if FToken <> etRParen then begin Result.FArgs := TList.Create; repeat Right := ParseExpr; if IsTemporal(Left.FDataType) then Right.FDataType := Left.FDataType; Result.FArgs.Add(Right); if (FToken <> etComma) and (FToken <> etRParen) then DatabaseErrorFmt(SExprNoRParenOrComma, [TokenName]); if FToken = etComma then NextToken; until (FToken = etRParen) or (FToken = etEnd); if FToken <> etRParen then DatabaseErrorFmt(SExprNoRParen, [TokenName]); NextToken; end else DatabaseError(SExprEmptyInList); end else begin if (Operator <> coISBLANK) and (Operator <> coNOTBLANK) then Right := ParseExpr5 else Right := nil; Result := FFilter.NewNode(enOperator, Operator, Unassigned, Left, Right); if Right <> nil then begin if (Left^.FKind = enField) and (Right^.FKind = enConst) then begin Right^.FDataType := Left^.FDataType; Right^.FDataSize := Left^.FDataSize; end else if (Right^.FKind = enField) and (Left^.FKind = enConst) then begin Left^.FDataType := Right^.FDataType; Left^.FDataSize := Right^.FDataSize; end; end; if (Left^.FDataType in BlobFieldTypes) and (Operator = coLIKE) then begin if Right^.FKind = enConst then Right^.FDataType := ftString; end else if (Operator <> coISBLANK) and (Operator <> coNOTBLANK) and ((Left^.FDataType in (BlobFieldTypes + [ftBytes])) or ((Right <> nil) and (Right^.FDataType in (BlobFieldTypes + [ftBytes])))) then DatabaseError(SExprTypeMis); Result.FDataType := ftBoolean; if Right <> nil then begin if IsTemporal(Left.FDataType) and (Right.FDataType in StringFieldTypes) then Right.FDataType := Left.FDataType else if IsTemporal(Right.FDataType) and (Left.FDataType in StringFieldTypes) then Left.FDataType := Right.FDataType; end; GetScopeKind(Result, Left, Right); end; end; end; function TExprParser.ParseExpr5: PExprNode; const Operators: array[etADD..etDIV] of TCANOperator = ( coADD, coSUB, coMUL, coDIV); var Operator: TCANOperator; Left, Right: PExprNode; begin Result := ParseExpr6; while FToken in [etADD, etSUB] do begin if not (poExtSyntax in FParserOptions) then DatabaseError(SExprNoArith); Operator := Operators[FToken]; Left := Result; NextToken; Right := ParseExpr6; Result := FFilter.NewNode(enOperator, Operator, Unassigned, Left, Right); TypeCheckArithOp(Result); GetScopeKind(Result, Left, Right); end; end; function TExprParser.ParseExpr6: PExprNode; const Operators: array[etADD..etDIV] of TCANOperator = ( coADD, coSUB, coMUL, coDIV); var Operator: TCANOperator; Left, Right: PExprNode; begin Result := ParseExpr7; while FToken in [etMUL, etDIV] do begin if not (poExtSyntax in FParserOptions) then DatabaseError(SExprNoArith); Operator := Operators[FToken]; Left := Result; NextToken; Right := ParseExpr7; Result := FFilter.NewNode(enOperator, Operator, Unassigned, Left, Right); TypeCheckArithOp(Result); GetScopeKind(Result, Left, Right); end; end; function TExprParser.ParseExpr7: PExprNode; var FuncName: string; begin case FToken of etSymbol: if (poExtSyntax in FParserOptions) and NextTokenIsLParen and TokenSymbolIsFunc(FTokenString) then begin Funcname := FTokenString; NextToken; if FToken <> etLParen then DatabaseErrorFmt(SExprNoLParen, [TokenName]); NextToken; if (CompareText(FuncName,'count') = 0) and (FToken = etMUL) then begin FuncName := 'COUNT(*)'; NextToken; end; Result := FFilter.NewNode(enFunc, coNOTDEFINED, FuncName, nil, nil); if FToken <> etRParen then begin Result.FArgs := TList.Create; repeat Result.FArgs.Add(ParseExpr); if (FToken <> etComma) and (FToken <> etRParen) then DatabaseErrorFmt(SExprNoRParenOrComma, [TokenName]); if FToken = etComma then NextToken; until (FToken = etRParen) or (FToken = etEnd); end else Result.FArgs := nil; GetFuncResultInfo(Result); end else if TokenSymbolIs('NULL') then begin Result := FFilter.NewNode(enConst, coNOTDEFINED, Variants.Null, nil, nil); Result.FScopeKind := skConst; end else if TokenSymbolIs(FStrTrue) then begin Result := FFilter.NewNode(enConst, coNOTDEFINED, 1, nil, nil); Result.FScopeKind := skConst; end else if TokenSymbolIs(FStrFalse) then begin Result := FFilter.NewNode(enConst, coNOTDEFINED, 0, nil, nil); Result.FScopeKind := skConst; end else begin Result := FFilter.NewNode(enField, coNOTDEFINED, FTokenString, nil, nil); Result.FScopeKind := skField; end; etName: begin Result := FFilter.NewNode(enField, coNOTDEFINED, FTokenString, nil, nil); Result.FScopeKind := skField; end; etLiteral: begin Result := FFilter.NewNode(enConst, coNOTDEFINED, FTokenString, nil, nil); if FNumericLit then Result^.FDataType := ftFloat else Result^.FDataType := ftString; Result.FScopeKind := skConst; end; etLParen: begin NextToken; Result := ParseExpr; if FToken <> etRParen then DatabaseErrorFmt(SExprNoRParen, [TokenName]); end; else DatabaseErrorFmt(SExprExpected, [TokenName]); Result := nil; end; NextToken; end; procedure TExprParser.GetScopeKind(Root, Left, Right : PExprNode); begin if (Left = nil) and (Right = nil) then Exit; if Right = nil then begin Root.FScopeKind := Left.FScopeKind; Exit; end; if ((Left^.FScopeKind = skField) and (Right^.FScopeKind = skAgg)) or ((Left^.FScopeKind = skAgg) and (Right^.FScopeKind = skField)) then DatabaseError(SExprBadScope); if (Left^.FScopeKind = skConst) and (Right^.FScopeKind = skConst) then Root^.FScopeKind := skConst else if (Left^.FScopeKind = skAgg) or (Right^.FScopeKind = skAgg) then Root^.FScopeKind := skAgg else if (Left^.FScopeKind = skField) or (Right^.FScopeKind = skField) then Root^.FScopeKind := skField; end; procedure TExprParser.GetFuncResultInfo(Node : PExprNode); begin Node^.FDataType := ftString; if (CompareText(Node^.FData, 'COUNT(*)') <> 0 ) and (CompareText(Node^.FData,'GETDATE') <> 0 ) and ( (Node^.FArgs = nil ) or ( Node^.FArgs.Count = 0) ) then DatabaseError(SExprTypeMis); if (Node^.FArgs <> nil) and (Node^.FArgs.Count > 0) then Node^.FScopeKind := PExprNode(Node^.FArgs.Items[0])^.FScopeKind; if (CompareText(Node^.FData , 'SUM') = 0) or (CompareText(Node^.FData , 'AVG') = 0) then begin Node^.FDataType := ftFloat; Node^.FScopeKind := skAgg; end else if (CompareText(Node^.FData , 'MIN') = 0) or (CompareText(Node^.FData , 'MAX') = 0) then begin Node^.FDataType := PExprNode(Node^.FArgs.Items[0])^.FDataType; Node^.FScopeKind := skAgg; end else if (CompareText(Node^.FData , 'COUNT') = 0) or (CompareText(Node^.FData , 'COUNT(*)') = 0) then begin Node^.FDataType := ftInteger; Node^.FScopeKind := skAgg; end else if (CompareText(Node^.FData , 'YEAR') = 0) or (CompareText(Node^.FData , 'MONTH') = 0) or (CompareText(Node^.FData , 'DAY') = 0) or (CompareText(Node^.FData , 'HOUR') = 0) or (CompareText(Node^.FData , 'MINUTE') = 0) or (CompareText(Node^.FData , 'SECOND') = 0 ) then begin Node^.FDataType := ftInteger; Node^.FScopeKind := PExprNode(Node^.FArgs.Items[0])^.FScopeKind; end else if CompareText(Node^.FData , 'GETDATE') = 0 then begin Node^.FDataType := ftDateTime; Node^.FScopeKind := skConst; end else if CompareText(Node^.FData , 'DATE') = 0 then begin Node^.FDataType := ftDate; Node^.FScopeKind := PExprNode(Node^.FArgs.Items[0])^.FScopeKind; end else if CompareText(Node^.FData , 'TIME') = 0 then begin Node^.FDataType := ftTime; Node^.FScopeKind := PExprNode(Node^.FArgs.Items[0])^.FScopeKind; end; end; function TExprParser.TokenName: string; begin if FSourcePtr = FTokenPtr then Result := SExprNothing else begin SetString(Result, FTokenPtr, FSourcePtr - FTokenPtr); Result := '''' + Result + ''''; end; end; function TExprParser.TokenSymbolIs(const S: string): Boolean; begin Result := (FToken = etSymbol) and (CompareText(FTokenString, S) = 0); end; function TExprParser.TokenSymbolIsFunc(const S: string) : Boolean; begin Result := (CompareText(S, 'UPPER') = 0) or (CompareText(S, 'LOWER') = 0) or (CompareText(S, 'SUBSTRING') = 0) or (CompareText(S, 'TRIM') = 0) or (CompareText(S, 'TRIMLEFT') = 0) or (CompareText(S, 'TRIMRIGHT') = 0) or (CompareText(S, 'YEAR') = 0) or (CompareText(S, 'MONTH') = 0) or (CompareText(S, 'DAY') = 0) or (CompareText(S, 'HOUR') = 0) or (CompareText(S, 'MINUTE') = 0) or (CompareText(S, 'SECOND') = 0) or (CompareText(S, 'GETDATE') = 0) or (CompareText(S, 'DATE') = 0) or (CompareText(S, 'TIME') = 0) or (CompareText(S, 'SUM') = 0) or (CompareText(S, 'MIN') = 0) or (CompareText(S, 'MAX') = 0) or (CompareText(S, 'AVG') = 0) or (CompareText(S, 'COUNT') = 0); end; procedure TExprParser.TypeCheckArithOp(Node: PExprNode); begin with Node^ do begin if IsNumeric(FLeft.FDataType) and IsNumeric(FRight.FDataType) then FDataType := ftFloat else if (FLeft.FDataType in StringFieldTypes) and (FRight.FDataType in StringFieldTypes) and (FOperator = coADD) then FDataType := ftString else if IsTemporal(FLeft.FDataType) and IsNumeric(FRight.FDataType) and (FOperator = coADD) then FDataType := ftDateTime else if IsTemporal(FLeft.FDataType) and IsNumeric(FRight.FDataType) and (FOperator = coSUB) then FDataType := FLeft.FDataType else if IsTemporal(FLeft.FDataType) and IsTemporal(FRight.FDataType) and (FOperator = coSUB) then FDataType := ftFloat else if (FLeft.FDataType in StringFieldTypes) and IsTemporal(FRight.FDataType) and (FOperator = coSUB) then begin FLeft.FDataType := FRight.FDataType; FDataType := ftFloat; end else if ( FLeft.FDataType in StringFieldTypes) and IsNumeric(FRight.FDataType )and (FLeft.FKind = enConst) then FLeft.FDataType := ftDateTime else DatabaseError(SExprTypeMis); end; end; end.