Browse Source

+ fcl-db: sql parser:
- add support for Firebird SET TERM statements (changing SQL statement terminator)
- fix sigsegv (e.g. running TTestCheckParser.TestNotBetween)

git-svn-id: trunk@27907 -

reiniero 11 years ago
parent
commit
75169f7a2b

+ 147 - 56
packages/fcl-db/src/sql/fpsqlparser.pas

@@ -59,8 +59,10 @@ Type
     procedure Expect(aToken: TSQLToken);
     // Verify that current token is one of the expected tokens; raise error if not
     procedure Expect(aTokens: TSQLTokens);
-    // Expects aToken as current token and eats it
+    // Expects aToken as current token and eats it by calling GetNextToken
     procedure Consume(aToken: TSQLToken);
+    // Expects aTokens tokens and eats the token by calling GetNextToken
+    procedure Consume(aTokens: TSQLTokens);
     procedure Error(Msg : String);
     procedure Error(Fmt : String; Args : Array of const);
     // Expression support
@@ -99,6 +101,7 @@ Type
     function ParseCreateViewStatement(AParent: TSQLElement; IsAlter: Boolean): TSQLCreateOrAlterStatement;
     function ParseCreateTriggerStatement(AParent: TSQLElement; IsAlter: Boolean): TSQLCreateOrAlterStatement;
     function ParseSetGeneratorStatement(AParent: TSQLElement) : TSQLSetGeneratorStatement;
+    function ParseSetTermStatement(AParent: TSQLElement) : TSQLSetTermStatement;
     function ParseCreateDatabaseStatement(AParent: TSQLElement; IsAlter: Boolean ): TSQLCreateDatabaseStatement;
     function ParseCreateShadowStatement(AParent: TSQLElement; IsAlter: Boolean ): TSQLCreateShadowStatement;
     function ParseAlterDatabaseStatement(AParent: TSQLElement; IsAlter: Boolean ): TSQLAlterDatabaseStatement;
@@ -146,6 +149,7 @@ Type
     Function ParseUpdateStatement(AParent : TSQLElement) : TSQLUpdateStatement;
     Function ParseInsertStatement(AParent : TSQLElement) : TSQLInsertStatement;
     Function ParseDeleteStatement(AParent : TSQLElement) : TSQLDeleteStatement;
+    // Parses both create and alter statements
     Function ParseCreateStatement(AParent : TSQLElement; IsAlter : Boolean = False) : TSQLCreateOrAlterStatement;
     Function ParseDropStatement(AParent : TSQLElement) : TSQLDropStatement;
     Function ParseRollbackStatement(AParent : TSQLElement) : TSQLRollbackStatement;
@@ -156,6 +160,12 @@ Type
     Function ParseRevokeStatement(AParent: TSQLElement): TSQLGrantStatement;
     Function Parse : TSQLElement;
     Function ParseScript(AllowPartial : Boolean = False) : TSQLElementList;
+    // Gets statement terminator (as e.g. used in SET TERM) so statements like
+    // EXECUTE BLOCK or CREATE PROCEDURE that contain semicolons can be parsed
+    function GetStatementTerminator: string;
+    // Sets statement terminator (as e.g. used in SET TERM) so statements like
+    // EXECUTE BLOCK or CREATE PROCEDURE that contain semicolons can be parsed
+    procedure SetStatementTerminator(AValue: string);
     // Auxiliary stuff
     Function CurrentToken : TSQLToken;
     Function CurrentTokenString : String;
@@ -253,6 +263,12 @@ begin
   GetNextToken;
 end;
 
+procedure TSQLParser.Consume(aTokens: TSQLTokens);
+begin
+  Expect(aTokens);
+  GetNextToken;
+end;
+
 function TSQLParser.CurSource: String;
 begin
   Result:=FScanner.CurFilename;
@@ -304,7 +320,8 @@ begin
   Result.SourcePos:=CurPos;
 end;
 
-Function TSQLParser.ParseTableRef(AParent : TSQLSelectStatement) : TSQLTableReference;
+function TSQLParser.ParseTableRef(AParent: TSQLSelectStatement
+  ): TSQLTableReference;
 Var
   T : TSQLSimpleTablereference;
   J  : TSQLJoinTableReference;
@@ -362,7 +379,8 @@ begin
   until Not (CurrentToken in [tsqlInner,tsqlJoin,tsqlOuter,tsqlLeft,tsqlRight]);
 end;
 
-Procedure TSQLParser.ParseFromClause(AParent : TSQLSelectStatement; AList : TSQLElementList);
+procedure TSQLParser.ParseFromClause(AParent: TSQLSelectStatement;
+  AList: TSQLElementList);
 
 Var
   T : TSQLTableReference;
@@ -380,7 +398,8 @@ begin
   until Done;
 end;
 
-Procedure TSQLParser.ParseSelectFieldList(AParent : TSQLSelectStatement; AList : TSQLElementList; Singleton : Boolean);
+procedure TSQLParser.ParseSelectFieldList(AParent: TSQLSelectStatement;
+  AList: TSQLElementList; Singleton: Boolean);
 Var
   F : TSQLSelectField;
   B : Boolean;
@@ -429,7 +448,8 @@ begin
   until (CurrentToken=tsqlFROM);
 end;
 
-Procedure TSQLParser.ParseGroupBy(AParent : TSQLSelectStatement; AList : TSQLElementList);
+procedure TSQLParser.ParseGroupBy(AParent: TSQLSelectStatement;
+  AList: TSQLElementList);
 
 Var
   N : TSQLStringType;
@@ -455,7 +475,8 @@ begin
   until (CurrentToken<>tsqlComma);
 end;
 
-Function TSQLParser.ParseForUpdate(AParent : TSQLSelectStatement) : TSQLElementList;
+function TSQLParser.ParseForUpdate(AParent: TSQLSelectStatement
+  ): TSQLElementList;
 
 begin
   // On entry we're on the FOR token.
@@ -474,7 +495,8 @@ begin
   end;
 end;
 
-Procedure TSQLParser.ParseOrderBy(AParent : TSQLSelectStatement; AList : TSQLElementList);
+procedure TSQLParser.ParseOrderBy(AParent: TSQLSelectStatement;
+  AList: TSQLElementList);
 
 Var
   O : TSQLOrderByElement;
@@ -525,7 +547,7 @@ begin
   until (CurrentToken<>tsqlComma);
 end;
 
-Function TSQLParser.ParseSelectPlan(AParent : TSQLElement) : TSQLSelectPlan;
+function TSQLParser.ParseSelectPlan(AParent: TSQLElement): TSQLSelectPlan;
 
 Var
   E : TSQLSelectPlanExpr;
@@ -789,7 +811,7 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseTableFieldDef(AParent : TSQLElement) : TSQLTableFieldDef;
+function TSQLParser.ParseTableFieldDef(AParent: TSQLElement): TSQLTableFieldDef;
 begin
   // on entry, we're on the field name
   Result:=TSQLTableFieldDef(CreateElement(TSQLTableFieldDef,AParent));
@@ -1163,7 +1185,8 @@ begin
   end;
 end;
 
-Procedure TSQLParser.ParseProcedureParamList(AParent: TSQLElement; AList : TSQLElementList);
+procedure TSQLParser.ParseProcedureParamList(AParent: TSQLElement;
+  AList: TSQLElementList);
 
 Var
   P : TSQLProcedureParamDef;
@@ -1187,7 +1210,8 @@ begin
   Consume(tsqlBraceClose);
 end;
 
-Procedure TSQLParser.ParseCreateProcedureVariableList(AParent: TSQLElement; AList : TSQLElementList);
+procedure TSQLParser.ParseCreateProcedureVariableList(AParent: TSQLElement;
+  AList: TSQLElementList);
 
 Var
   P : TSQLProcedureParamDef;
@@ -1212,7 +1236,7 @@ begin
   Until (CurrentToken<>tsqlDeclare);
 end;
 
-Function TSQLParser.ParseIfStatement(AParent : TSQLElement) : TSQLIFStatement;
+function TSQLParser.ParseIfStatement(AParent: TSQLElement): TSQLIFStatement;
 
 Var
   Pt : TSQLToken;
@@ -1261,7 +1285,7 @@ begin
   Until (CurrentToken<>tsqlComma);
 end;
 
-Function TSQLParser.ParseForStatement(AParent : TSQLElement) : TSQLForStatement;
+function TSQLParser.ParseForStatement(AParent: TSQLElement): TSQLForStatement;
 
 begin
   // On Entry, we're on the FOR token
@@ -1280,7 +1304,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExceptionStatement(AParent : TSQLElement) : TSQLExceptionStatement;
+function TSQLParser.ParseExceptionStatement(AParent: TSQLElement
+  ): TSQLExceptionStatement;
 
 begin
   // On Entry, we're on the EXCEPTION token
@@ -1296,7 +1321,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseAssignStatement(AParent : TSQLElement) : TSQLAssignStatement;
+function TSQLParser.ParseAssignStatement(AParent: TSQLElement
+  ): TSQLAssignStatement;
 
 Var
   N  : TSQLStringType;
@@ -1324,7 +1350,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParsePostEventStatement(AParent : TSQLElement) : TSQLPostEventStatement;
+function TSQLParser.ParsePostEventStatement(AParent: TSQLElement
+  ): TSQLPostEventStatement;
 
 begin
   // On Entry, we're on the POST_EVENT token
@@ -1344,7 +1371,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseWhileStatement(AParent : TSQLElement) : TSQLWhileStatement;
+function TSQLParser.ParseWhileStatement(AParent: TSQLElement
+  ): TSQLWhileStatement;
 
 begin
   // On entry, we're on the WHILE Token
@@ -1362,7 +1390,7 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseWhenStatement(AParent : TSQLElement) : TSQLWhenStatement;
+function TSQLParser.ParseWhenStatement(AParent: TSQLElement): TSQLWhenStatement;
 
 Var
   E : TSQLWhenException;
@@ -1420,7 +1448,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseProcedureStatement(AParent : TSQLElement) : TSQLStatement;
+function TSQLParser.ParseProcedureStatement(AParent: TSQLElement
+  ): TSQLStatement;
 
 begin
   Result:=Nil;
@@ -1455,7 +1484,8 @@ begin
   end;
 end;
 
-Procedure TSQLParser.ParseStatementBlock(AParent: TSQLElement; Statements : TSQLElementList);
+procedure TSQLParser.ParseStatementBlock(AParent: TSQLElement;
+  Statements: TSQLElementList);
 
 Var
   S: TSQLStatement;
@@ -1467,7 +1497,7 @@ begin
     S:=ParseProcedureStatement(AParent);
     Statements.Add(S);
     if not (PreviousToken=tsqlEnd) then
-      Consume(tsqlSemicolon);
+      Consume([tsqlSemicolon,tsqlStatementTerminator]);
     end;
   Consume(tsqlEnd);
 end;
@@ -1538,7 +1568,8 @@ begin
   GetNextToken; // Comma;
 end;
 
-Procedure TSQLParser.ParseCharTypeDefinition(Out DT : TSQLDataType; Out Len : Integer; Out ACharset : TSQLStringType);
+procedure TSQLParser.ParseCharTypeDefinition(out DT: TSQLDataType; out
+  Len: Integer; out ACharset: TSQLStringType);
 
 begin
   Len:=0;
@@ -1592,7 +1623,8 @@ begin
     end;
 end;
 
-Procedure TSQLParser.ParseBlobDefinition(Var ASegmentSize,ABlobType : Integer; Var ACharset : TSQLStringType);
+procedure TSQLParser.ParseBlobDefinition(var ASegmentSize, ABlobType: Integer;
+  var ACharset: TSQLStringType);
 
 begin
   // On entry, we are on the blob token.
@@ -1647,7 +1679,8 @@ begin
     end;
 end;
 
-Function TSQLParser.ParseForeignKeyDefinition(AParent : TSQLElement) : TSQLForeignKeyDefinition;
+function TSQLParser.ParseForeignKeyDefinition(AParent: TSQLElement
+  ): TSQLForeignKeyDefinition;
 
   // On entry, we're on ON Return true if On delete
   Function ParseForeignKeyAction (Out Res : TForeignKeyAction) : Boolean;
@@ -1723,7 +1756,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseFieldConstraint(AParent : TSQLElement) : TSQLFieldConstraint;
+function TSQLParser.ParseFieldConstraint(AParent: TSQLElement
+  ): TSQLFieldConstraint;
 
 Var
   N : TSQLStringType;
@@ -1800,7 +1834,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseTypeDefinition(AParent : TSQLElement; Flags : TParseTypeFlags) : TSQLTypeDefinition;
+function TSQLParser.ParseTypeDefinition(AParent: TSQLElement;
+  Flags: TParseTypeFlags): TSQLTypeDefinition;
 
 Var
   TN : String;
@@ -2047,7 +2082,8 @@ begin
     Error('Unexpected end of command');
 end;
 
-Function TSQLParser.ParseExprLevel1(AParent : TSQLElement; EO : TExpressionOptions) : TSQLExpression;
+function TSQLParser.ParseExprLevel1(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLExpression;
 
 var
   tt: TSQLToken;
@@ -2098,7 +2134,7 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseInoperand(AParent : TSQLElement) : TSQLExpression;
+function TSQLParser.ParseInoperand(AParent: TSQLElement): TSQLExpression;
 
 Var
   S : TSQLSelectExpression;
@@ -2133,7 +2169,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExprLevel2(AParent : TSQLElement; EO : TExpressionOptions) : TSQLExpression;
+function TSQLParser.ParseExprLevel2(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLExpression;
 var
   tt: TSQLToken;
   Right : TSQLExpression;
@@ -2141,7 +2178,7 @@ var
   T : TSQLTernaryExpression;
   O : TSQLBinaryOperation;
   U : TSQLUnaryExpression;
-  I,bw,doin : Boolean;
+  Inverted,bw,doin : Boolean;
 
 begin
   {$ifdef debugexpr}  Writeln('Level 2 ',TokenInfos[CurrentToken],': ',CurrentTokenString);{$endif debugexpr}
@@ -2150,11 +2187,11 @@ begin
     if (CurrentToken in sqlComparisons) then
       begin
       tt:=CurrentToken;
-      I:=CurrentToken=tsqlnot;
+      Inverted:=CurrentToken=tsqlnot;
       CheckEOF;
       GetNextToken;
       CheckEOF;
-      if I then
+      if Inverted then
         begin
         tt:=CurrentToken;
         if Not (tt in sqlInvertableComparisons) then
@@ -2174,12 +2211,13 @@ begin
           GetNextToken;
           if not (tt=tsqlis) then
             UnexpectedToken;
-          I:=true;
+          Inverted:=true;
           end;
         end;
 
       bw:=False;
       doin:=false;
+      B:=nil; //needed for test later
       Case tt of
         tsqlLT : O:=boLT;
         tsqlLE : O:=boLE;
@@ -2237,8 +2275,8 @@ begin
           B.Right:=Right;
           end;
         end;
-      If I then
-        if B.Operation=boIs then
+      If Inverted then
+        if (Assigned(B)) and (B.Operation=boIs) then
           B.Operation:=boIsNot
         else
           begin
@@ -2254,7 +2292,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExprLevel3(AParent : TSQLElement; EO : TExpressionOptions) : TSQLExpression;
+function TSQLParser.ParseExprLevel3(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLExpression;
 
   Function NegativeNumber : Boolean; inline;
 
@@ -2302,7 +2341,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExprLevel4(AParent : TSQLElement; EO : TExpressionOptions) : TSQLExpression;
+function TSQLParser.ParseExprLevel4(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLExpression;
 
 var
   tt : TSQLToken;
@@ -2333,7 +2373,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExprLevel5(AParent : TSQLElement; EO : TExpressionOptions) : TSQLExpression;
+function TSQLParser.ParseExprLevel5(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLExpression;
 
 Var
   tt : tsqltoken;
@@ -2365,7 +2406,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExprLevel6(AParent : TSQLElement ; EO : TExpressionOptions ) : TSQLExpression;
+function TSQLParser.ParseExprLevel6(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLExpression;
 
 
 
@@ -2399,7 +2441,8 @@ begin
     Result:=ParseExprPrimitive(AParent,EO);
 end;
 
-Function TSQLParser.ParseIdentifierList(AParent : TSQLElement; AList : TSQLelementList) : integer;
+function TSQLParser.ParseIdentifierList(AParent: TSQLElement;
+  AList: TSQLelementList): integer;
 
 Var
   Done : Boolean;
@@ -2420,7 +2463,8 @@ begin
   GetNextToken;
 end;
 
-Function TSQLParser.ParseValueList(AParent : TSQLElement; EO : TExpressionOptions) : TSQLElementList;
+function TSQLParser.ParseValueList(AParent: TSQLElement; EO: TExpressionOptions
+  ): TSQLElementList;
 
 Var
     E : TSQLExpression;
@@ -2467,15 +2511,16 @@ begin
   Error(SErrUnexpectedTokenOf,[CurrentTokenString,S]);
 end;
 
-function TSQLParser.CreateIdentifier(AParent : TSQLElement; Const AName: TSQLStringType
-  ): TSQLIdentifierName;
+function TSQLParser.CreateIdentifier(AParent: TSQLElement;
+  const AName: TSQLStringType): TSQLIdentifierName;
 begin
   Result:=TSQLIdentifierName(CreateElement(TSQLIdentifierName,AParent));
   Result.Name:=AName;
 end;
 
 
-Function TSQLParser.ParseExprAggregate(AParent : TSQLElement; EO : TExpressionOptions) : TSQLAggregateFunctionExpression;
+function TSQLParser.ParseExprAggregate(AParent: TSQLElement;
+  EO: TExpressionOptions): TSQLAggregateFunctionExpression;
 begin
   Result:=TSQLAggregateFunctionExpression(CreateElement(TSQLAggregateFunctionExpression,AParent));
   try
@@ -2514,7 +2559,8 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseExprPrimitive(AParent : TSQLElement; EO : TExpressionOptions) : TSQLExpression;
+function TSQLParser.ParseExprPrimitive(AParent: TSQLElement;
+  EO: TExpressionOptions): TSQLExpression;
 
 Var
   L : TSQLElementList;
@@ -2900,6 +2946,38 @@ begin
   end;
 end;
 
+function TSQLParser.ParseSetTermStatement(AParent: TSQLElement
+  ): TSQLSetTermStatement;
+var
+  ExistingStatTerm: string;
+begin
+  // On entry, we're on the 'TERM' token
+  Consume(tsqlTerm) ;
+  try
+    Result:=TSQLSetTermStatement(CreateElement(TSQLSetTermStatement,AParent));
+    expect([tsqlSemiColon,tsqlStatementTerminator,tsqlSymbolLiteral,tsqlString]);
+    // Already set the expression's new value to the new terminator, but do not
+    // change tSQLStatementTerminator as GetNextToken etc need the old one to
+    // detect the closing terminator
+    case CurrentToken of
+      tsqlSemiColon, tsqlStatementTerminator: Result.NewValue:=TokenInfos[CurrentToken];
+      tsqlSymbolLiteral, tsqlString: Result.NewValue:=CurrentTokenString;
+    end;
+    // Expect the old terminator...
+    GetNextToken;
+    // Parser will give tsqlSemicolon rather than tsqlStatementTerminator:
+    if TokenInfos[tsqlStatementTerminator]=TokenInfos[tsqlSEMICOLON] then
+      Expect(tsqlSEMICOLON)
+    else
+      Expect(tsqlStatementTerminator);
+    //... and now set the new terminator:
+    TokenInfos[tsqlStatementTerminator]:=Result.NewValue; //process new terminator value
+  except
+    FreeAndNil(Result);
+    Raise;
+  end;
+end;
+
 function TSQLParser.ParseSecondaryFile(AParent: TSQLElement) : TSQLDatabaseFileInfo;
 
 Var
@@ -3163,7 +3241,7 @@ begin
 end;
 
 function TSQLParser.ParseRollbackStatement(AParent: TSQLElement
-  ): TSQLRollBackStatement;
+  ): TSQLRollbackStatement;
 
 
 begin
@@ -3302,7 +3380,8 @@ begin
   // On Entry, we're on the SET statement
   Consume(tsqlSet);
   Case CurrentToken of
-    tsqlGenerator : Result:=ParseSetGeneratorStatement(AParent)
+    tsqlGenerator : Result:=ParseSetGeneratorStatement(AParent);
+    tsqlTerm : Result:=ParseSetTermStatement(AParent);
   else
     // For the time being
     UnexpectedToken;
@@ -3375,7 +3454,8 @@ begin
   inherited Destroy;
 end;
 
-Function TSQLParser.ParseDeclareFunctionStatement(AParent : TSQLElement) : TSQLDeclareExternalFunctionStatement;
+function TSQLParser.ParseDeclareFunctionStatement(AParent: TSQLElement
+  ): TSQLDeclareExternalFunctionStatement;
 
 begin
   // On entry, we're on the EXTERNAL token
@@ -3410,7 +3490,7 @@ begin
   end;
 end;
 
-Function TSQLParser.ParseDeclareStatement(AParent : TSQLElement) : TSQLStatement;
+function TSQLParser.ParseDeclareStatement(AParent: TSQLElement): TSQLStatement;
 
 
 begin
@@ -3818,7 +3898,7 @@ begin
   else
     UnexpectedToken;
   end;
-  if Not (CurrentToken in [tsqlEOF,tsqlSemicolon]) then
+  if Not (CurrentToken in [tsqlEOF,tsqlSemicolon,tsqlStatementTerminator]) then
     begin
     FreeAndNil(Result);
     if (CurrentToken=tsqlBraceClose) then
@@ -3850,6 +3930,16 @@ begin
   end;
 end;
 
+function TSQLParser.GetStatementTerminator: string;
+begin
+  result:=TokenInfos[tsqlStatementTerminator];
+end;
+
+procedure TSQLParser.SetStatementTerminator(AValue: string);
+begin
+  TokenInfos[tsqlStatementTerminator]:=AValue;;
+end;
+
 function TSQLParser.CurrentToken: TSQLToken;
 begin
   Result:=FCurrent;
@@ -3863,13 +3953,14 @@ end;
 function TSQLParser.GetNextToken: TSQLToken;
 begin
   FPrevious:=FCurrent;
+  // Set if not already peeked; otherwise fetch and look
   If (FPeekToken<>tsqlUnknown) then
-     begin
-     FCurrent:=FPeekToken;
-     FCurrentString:=FPeekTokenString;
-     FPeekToken:=tsqlUnknown;
-     FPeekTokenString:='';
-     end
+    begin
+    FCurrent:=FPeekToken;
+    FCurrentString:=FPeekTokenString;
+    FPeekToken:=tsqlUnknown;
+    FPeekTokenString:='';
+    end
   else
     begin
     FCurrent:=FScanner.FetchToken;

+ 73 - 15
packages/fcl-db/src/sql/fpsqlscanner.pp

@@ -37,10 +37,16 @@ type
 
    TSQLToken = (tsqlUnknown,
    // Specials
-   tsqlEOF,tsqlWhiteSpace,tsqlString,tsqlIdentifier,tsqlIntegerNumber,tsqlFloatNumber,tsqlComment,
+   tsqlEOF,tsqlWhiteSpace,
+   tsqlString {string literal},
+   tsqlIdentifier {a table etc name},
+   tsqlSymbolLiteral {a literal containing symbols/punctuation marks},
+   tsqlIntegerNumber,tsqlFloatNumber,tsqlComment,
+   tsqlStatementTerminator {statement separator, usually semicolon but may be changed by code. For now, limited to semicolon and symbol literals not already defined like tsqlCOMMA},
    tsqlBraceOpen,tsqlBraceClose,tsqlSquareBraceOpen,tsqlSquareBraceClose,
-   tsqlPlaceHolder,tsqlCOMMA,tsqlCOLON,tsqlDOT,tsqlSEMICOLON,tsqlGT,tsqlLT,
-   tsqlPLUS,tsqlMINUS,tsqlMUL,tsqlDIV,tsqlConcatenate,
+   tsqlPlaceHolder {question mark},
+   tsqlCOMMA,tsqlCOLON,tsqlDOT,tsqlSEMICOLON,
+   tsqlGT,tsqlLT,tsqlPLUS,tsqlMINUS,tsqlMUL,tsqlDIV,tsqlConcatenate,
    tsqlEQ,tsqlGE,tsqlLE,tsqlNE,
    { Reserved words/keywords start here. They must be last }
    { Note: if adding before tsqlALL or after tsqlWHEN please update FirstKeyword/LastKeyword }
@@ -62,7 +68,7 @@ type
    tsqlPrecision, tsqlPRIMARY,  tsqlProcedure, tsqlPosition, tsqlPlan, tsqlPassword, tsqlPage,tsqlPages,tsqlPageSize,tsqlPostEvent,tsqlPrivileges,tsqlPublic,
    tsqlRIGHT, tsqlROLE, tsqlReferences, tsqlRollBack, tsqlRelease,  tsqlretain,  tsqlReturningValues,tsqlReturns, tsqlrevoke,
    tsqlSELECT, tsqlSET, tsqlSINGULAR, tsqlSOME, tsqlSTARTING, tsqlSUM, tsqlSKIP,tsqlSUBTYPE,tsqlSize,tsqlSegment, tsqlSORT, tsqlSnapShot,tsqlSchema,tsqlShadow,tsqlSuspend,tsqlSQLCode,tsqlSmallint,
-   tSQLTABLE, tsqlText, tsqlTrigger, tsqlTime, tsqlTimeStamp, tsqlType, tsqlTo, tsqlTransaction, tsqlThen,
+   tSQLTABLE, tsqlText, tsqlTerm, tsqlTrigger, tsqlTime, tsqlTimeStamp, tsqlType, tsqlTo, tsqlTransaction, tsqlThen,
    tsqlUNION, tsqlUPDATE, tsqlUPPER,  tsqlUNIQUE, tsqlUSER,
    tsqlValue, tsqlVALUES, tsqlVARIABLE,  tsqlVIEW, tsqlVARCHAR,TSQLVARYING,
    tsqlWHERE, tsqlWITH, tsqlWHILE, tsqlWork, tsqlWhen
@@ -79,7 +85,12 @@ const
   // Strings that represent tokens in TSQLToken
   TokenInfos: array[TSQLToken] of string = ('unknown',
        // Specials
-       'EOF','whitespace','String', 'identifier','integer number','float number', 'comment',
+       'EOF','whitespace',
+       'String',
+       'identifier',
+       'symbol literal',
+       'integer number','float number', 'comment',
+       ';' {value may be changed at run time to any symbol or set of symbols},
        '(',')', '[',']',
        '?',',',':','.',';','>','<',
        '+','-','*','/','||',
@@ -103,7 +114,7 @@ const
        'PRECISION', 'PRIMARY', 'PROCEDURE','POSITION','PLAN', 'PASSWORD','PAGE','PAGES','PAGE_SIZE','POST_EVENT','PRIVILEGES','PUBLIC',
        'RIGHT', 'ROLE', 'REFERENCES', 'ROLLBACK','RELEASE', 'RETAIN', 'RETURNING_VALUES', 'RETURNS','REVOKE',
        'SELECT', 'SET', 'SINGULAR', 'SOME', 'STARTING', 'SUM', 'SKIP','SUB_TYPE', 'SIZE', 'SEGMENT', 'SORT', 'SNAPSHOT','SCHEMA','SHADOW','SUSPEND','SQLCODE','SMALLINT',
-       'TABLE', 'TEXT', 'TRIGGER', 'TIME', 'TIMESTAMP', 'TYPE', 'TO', 'TRANSACTION', 'THEN',
+       'TABLE', 'TEXT', 'TERM', 'TRIGGER', 'TIME', 'TIMESTAMP', 'TYPE', 'TO', 'TRANSACTION', 'THEN',
        'UNION', 'UPDATE', 'UPPER', 'UNIQUE', 'USER',
        'VALUE','VALUES','VARIABLE', 'VIEW','VARCHAR','VARYING',
        'WHERE', 'WITH', 'WHILE','WORK','WHEN'
@@ -149,6 +160,7 @@ Type
                        soReturnWhiteSpace,
                        soBackslashEscapes,
                        soNoDoubleDelimIsChar,
+                       soNoSetTerm {if set, do not allow changing terminator with SET TERM commands as used in Firebird},
                        soDoubleQuoteStringLiteral,  // Default: single quote is string literal
                        soSingleQuoteIdentifier,     // Default: double quote is identifier. Ignored if soDoubleQuoteStringLiteral is not specified
                        soBackQuoteIdentifier        // Default: double quote is identifier
@@ -172,12 +184,18 @@ Type
     FKeyWords : TFPHashList;
     FExclude : TStringList;
     function CommentDiv: TSQLToken;
+    // Used to parse out an identifier/name and store it in the list of identifiers
     function DoIdentifier : TSQLToken;
+    // Used to parse out a literal containing symbols
+    // Also looks for tsqlStatementTerminator
+    function DoSymbolLiteral : TSQLToken;
     function DoMultiLineComment: TSQLToken;
     function DoNumericLiteral: TSQLToken;
     function DoSingleLineComment: TSQLToken;
     function DoStringLiteral: TSQLToken;
     function DoWhiteSpace: TSQLToken;
+    // Reads a new line into TokenStr and returns true
+    // If no new lines, returns false
     function FetchLine: Boolean;
     function GetCurColumn: Integer;
     function GetExcludeKeywords: TStrings;
@@ -211,11 +229,12 @@ Type
 implementation
 
 Var
+  // Keeps track of identifiers used
   IdentifierTokens : array[FirstKeyword..LastKeyWord] of TSQLToken;
   IdentifierTokensOK : Boolean;
 
 Resourcestring
-  SErrUNknownToken = 'Unknown token: %s';
+  SErrUnknownToken = 'Unknown token: %s';
 
 Procedure BuildIdentifierTokens;
 
@@ -316,10 +335,10 @@ begin
     Inc(TokenStr);
     if TokenStr[0] = #0 then
       if not FetchLine then
-       begin
-       FCurToken := Result;
-       exit;
-       end;
+        begin
+        FCurToken := Result;
+        exit;
+        end;
   until not (TokenStr[0] in [#9, ' ']);
 end;
 
@@ -454,7 +473,7 @@ begin
   If FKeywords.Count>0 then
     FKeywords.Clear;
   for I:=FirstKeyword to LastKeyword do
-    if (not Assigned(FExclude)) or (FExclude.INdexOf(TokenInfos[I])=-1) then
+    if (not Assigned(FExclude)) or (FExclude.IndexOf(TokenInfos[I])=-1) then
       FKeywords.Add(TokenInfos[I],@IdentifierTokens[i]);
 end;
 
@@ -616,7 +635,7 @@ function TSQLScanner.DoIdentifier : TSQLToken;
 Var
   TokenStart:PChar;
   Len : Integer;
-  I : TSQLToken;
+  {I : TSQLToken;}
   S : ShortString;
   P : ^TSQLToken;
 
@@ -652,6 +671,43 @@ begin
     end;}
 end;
 
+function TSQLScanner.DoSymbolLiteral : TSQLToken;
+
+Var
+  Len : Integer;
+  P : ^TSQLToken;
+  TokenStart : PChar;
+begin
+  Result:=tsqlUnknown;
+
+  // Get "word" finalized by end of string, space/tab.
+  TokenStart:=TokenStr;
+  repeat
+    Inc(TokenStr);
+  until (TokenStr[0] in [#0, #9, #10, #13, ' ']);
+  Len:=(TokenStr-TokenStart);
+  if Len > 0 then
+    begin
+    result:=tsqlSymbolLiteral;
+    SetLength(FCurTokenString,Len);
+    Move(TokenStart^,FCurTokenString[1],Len);
+
+    // Check if it is the statement terminator
+    if FCurTokenString=TokenInfos[tsqlStatementTerminator] then
+      begin
+      exit(tsqlStatementTerminator);
+      end;
+
+    // Check if this is a keyword or identifier/literal
+    // Probably not (due to naming rules) but it doesn't hurt
+    If FKeyWords.Count=0 then
+      BuildKeyWords;
+    P:=FKeyWords.Find(FCurTokenString); //case-sensitive search
+    If (P<>Nil) then
+      Result:=P^; //keyword found, just in case
+    end;
+end;
+
 function TSQLScanner.FetchToken: TSQLToken;
 
 begin
@@ -819,7 +875,7 @@ begin
       begin
       Inc(TokenStr);
       if TokenStr[0] = '=' then
-	begin
+	      begin
         Inc(TokenStr);
         Result:=tsqlGE;
         end
@@ -830,7 +886,9 @@ begin
    'A'..'Z':
      Result:=DoIdentifier;
    else
-     Error(SErrUnknownToken,[TokenStr[0]]);
+     // Symbol of some sort
+     Result:=DoSymbolLiteral;
+     //Error(SErrUnknownToken,[TokenStr[0]]);
    end; // Case
   Until (Not (Result in [tsqlComment,tsqlWhitespace])) or
         ((Result=tsqlComment) and (soReturnComments in options)) or

+ 10 - 0
packages/fcl-db/src/sql/fpsqltree.pp

@@ -889,6 +889,7 @@ Type
   public
     Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
   end;
+
   { TSQLSetGeneratorStatement }
 
   TSQLSetGeneratorStatement = Class(TSQLCreateOrAlterGenerator)
@@ -898,6 +899,15 @@ Type
     Property NewValue : Integer Read FNewValue Write FNewValue;
   end;
 
+  { TSQLSetTermStatement }
+
+  TSQLSetTermStatement = Class(TSQLStatement)
+  private
+    FNewValue: string;
+  Public
+    Property NewValue : string Read FNewValue Write FNewValue;
+  end;
+
   { TSQLCreateRoleStatement }
 
   TSQLCreateRoleStatement = Class(TSQLCreateOrAlterStatement)