home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Delphi Anthology
/
aDELPHI.iso
/
Runimage
/
Delphi50
/
Source
/
Vcl
/
dbcommon.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1999-08-11
|
58KB
|
1,813 lines
{*******************************************************}
{ }
{ Borland Delphi Visual Component Library }
{ Common Database Code }
{ }
{ Copyright (c) 1995,99 Inprise Corporation }
{ }
{*******************************************************}
unit DBCommon;
{$T-,H+,X+,R-}
interface
uses Windows, Classes, DB;
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 }
);
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 PutConstBool(const Value: Variant): Integer;
function PutConstDate(const Value: Variant): Integer;
function PutConstDateTime(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
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 }
type
TFieldInfo = record
DatabaseName: string;
TableName: string;
OriginalFieldName: string;
end;
function GetFieldInfo(const Origin: string; var FieldInfo: TFieldInfo): Boolean;
{ SQL Parser }
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;
implementation
uses SysUtils, DBConsts;
{ 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 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
if SQLToken = stTableName then
begin
Result := Token;
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: 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;
Result.Fields := AddField(Result.Fields, LastField);
end;
stAscending: ;
stDescending:
Result.DescFields := AddField(Result.DescFields, LastField);
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
SysUtils.Abort;
Result.Fields := AddField(Result.Fields, LastField);
end;
end;
until (CurSection <> stOrderBy) or (SQLToken = stEnd);
except
Result.Free;
Result := nil;
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];
end;
function IsTemporal(DataType: TFieldType): Boolean;
begin
Result := DataType in [ftDate, ftTime, ftDateTime];
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.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.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:
Result := PutConstStr(Node^.FData);
ftDate:
Result := PutConstDate(Node^.FData);
ftTime:
Result := PutConstTime(Node^.FData);
ftDateTime:
Result := PutConstDateTime(Node^.FData);
ftBoolean:
Result := PutConstBool(Node^.FData);
ftBCD:
Result := PutConstBCD(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
S := 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
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;
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
Result := (SysLocale.PriLangID = LANG_JAPANESE) and (Chr in [$A1..$DF]);
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
begin
Inc(P);
if P^ <> '''' then Break;
end;
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', DecimalSeparator, 'e', 'E', '+', '-']) do
Inc(P);
if ((P-1)^ = ',') and (DecimalSeparator = ',') 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, System.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.