Sfoglia il codice sorgente

Streamline code; detect some more DDL commands

Reinier Olislagers 11 anni fa
parent
commit
843a4a010a
3 ha cambiato i file con 81 aggiunte e 58 eliminazioni
  1. 27 1
      modsqlscript.pas
  2. 49 57
      querywindow.pas
  3. 5 0
      scriptdb.pas

+ 27 - 1
modsqlscript.pas

@@ -18,6 +18,24 @@ Modified from FPC  sqldb.pp and sqlscript.pp to solve bugs with
 parameters. See 
 parameters. See 
 http://wiki.lazarus.freepascal.org/User_Changes_Trunk#TSQLScript_supports_:.2C_backtick_quotes_and_explicit_COMMIT.2FCOMMIT_RETAIN
 http://wiki.lazarus.freepascal.org/User_Changes_Trunk#TSQLScript_supports_:.2C_backtick_quotes_and_explicit_COMMIT.2FCOMMIT_RETAIN
 }
 }
+{ Note regarding SQLScript bug in FPC <= 2.7.1:
+If parameters are used in the script e.g. as in the sample EMPLOYEE.FDB
+CREATE Procedure DELETE_EMPLOYEE
+...
+SELECT count(po_number)
+FROM sales
+WHERE sales_rep = :emp_num
+INTO :any_sales;
+you may get this error
+: PrepareStatement :
+-Dynamic SQL Error
+-SQL error code = -104
+-Token unknown - line 19, column 7
+-?
+because the TSQLScript tries to process parameters as if they were sqldb
+parameters
+}
+
 unit modsqlscript;
 unit modsqlscript;
 
 
 {$mode objfpc}{$H+}
 {$mode objfpc}{$H+}
@@ -27,6 +45,7 @@ interface
 uses
 uses
   Classes, SysUtils, sqlscript, db, dbconst, sqldb;
   Classes, SysUtils, sqlscript, db, dbconst, sqldb;
 
 
+{$IF FPC_FULLVERSION<20701}
 type
 type
   { TModCustomSQLQuery }
   { TModCustomSQLQuery }
     TModCustomSQLQuery = class(TCustomSQLQuery)
     TModCustomSQLQuery = class(TCustomSQLQuery)
@@ -79,6 +98,8 @@ implementation
 
 
 procedure TModSQLScript.ExecuteStatement(SQLStatement: TStrings;
 procedure TModSQLScript.ExecuteStatement(SQLStatement: TStrings;
   var StopExecution: Boolean);
   var StopExecution: Boolean);
+var
+  statementtext: string;
 begin
 begin
   fquery.SQL.assign(SQLStatement);
   fquery.SQL.assign(SQLStatement);
   fquery.ExecSQL;
   fquery.ExecSQL;
@@ -144,5 +165,10 @@ procedure TModSQLScript.ExecuteScript;
 begin
 begin
   Execute;
   Execute;
 end;
 end;
-
+{$ELSE}
+// In FPC trunk, we can just use existing code
+type
+  TModSQLScript = TSQLscript;
+implementation
+{$ENDIF}
 end.
 end.

+ 49 - 57
querywindow.pas

@@ -8,28 +8,7 @@ uses
   Classes, SysUtils, IBConnection, db, sqldb, FileUtil, LResources, Forms,
   Classes, SysUtils, IBConnection, db, sqldb, FileUtil, LResources, Forms,
   Controls, Graphics, Dialogs, ExtCtrls, PairSplitter, StdCtrls, Buttons,
   Controls, Graphics, Dialogs, ExtCtrls, PairSplitter, StdCtrls, Buttons,
   DBGrids, Menus, ComCtrls, SynEdit, SynHighlighterSQL, Reg,
   DBGrids, Menus, ComCtrls, SynEdit, SynHighlighterSQL, Reg,
-  SynEditTypes, SynCompletion, Clipbrd, grids, DbCtrls, types, LCLType
-  {$IF FPC_FULLVERSION < 20701}
-{ Note regarding SQLScript bug in FPC <= 2.7.1:
-If parameters are used in the script e.g. as in the sample EMPLOYEE.FDB
-CREATE Procedure DELETE_EMPLOYEE
-...
-SELECT count(po_number)
-FROM sales
-WHERE sales_rep = :emp_num
-INTO :any_sales;
-you may get this error
-: PrepareStatement :
--Dynamic SQL Error
--SQL error code = -104
--Token unknown - line 19, column 7
--?
-because the TSQLScript tries to process parameters as if they were sqldb
-parameters
-}
-  ,modsqlscript
-  {$ENDIF}
-  ;
+  SynEditTypes, SynCompletion, Clipbrd, grids, DbCtrls, types, LCLType, modsqlscript;
 
 
 type
 type
 
 
@@ -213,7 +192,9 @@ type
     function GetNewTabNum: string;
     function GetNewTabNum: string;
     procedure FinishCellEditing(DataSet: TDataSet);
     procedure FinishCellEditing(DataSet: TDataSet);
     function GetRecordSet(TabIndex: Integer): TSQLQuery;
     function GetRecordSet(TabIndex: Integer): TSQLQuery;
-    function GetQuerySQLType(QueryList: TStringList; var SecondRealStart: Integer;
+    // Gets both querytype and whether SQL is DML or DDL
+    // Investigates QueryList[LookAtIndex] to find out
+    function GetQuerySQLType(QueryList: TStringList; var LookAtIndex: Integer;
       var IsDDL: Boolean): TQueryTypes;
       var IsDDL: Boolean): TQueryTypes;
     procedure NewCommitButton(const Pan: TPanel; var ATab: TTabSheet);
     procedure NewCommitButton(const Pan: TPanel; var ATab: TTabSheet);
     procedure RemoveComments(QueryList: TStringList; StartLine: Integer;
     procedure RemoveComments(QueryList: TStringList; StartLine: Integer;
@@ -244,7 +225,9 @@ type
     // Free up memory for controls
     // Free up memory for controls
     procedure RemoveControls;
     procedure RemoveControls;
     function FindSqlQuery: TSqlQuery;
     function FindSqlQuery: TSqlQuery;
+    // Returns whether query is DDL or DML
     function GetSQLType(Query: string; var Command: string): string;
     function GetSQLType(Query: string; var Command: string): string;
+    // Tries to split up text into separate queries
     function GetSQLSegment(QueryList: TStringList; StartLine: Integer;
     function GetSQLSegment(QueryList: TStringList; StartLine: Integer;
       var QueryType: TQueryTypes; var EndLine: Integer;
       var QueryType: TQueryTypes; var EndLine: Integer;
       var SQLSegment: string; var IsDDL: Boolean): Boolean;
       var SQLSegment: string; var IsDDL: Boolean): Boolean;
@@ -345,7 +328,7 @@ begin
         QueryList.Delete(i);
         QueryList.Delete(i);
       {
       {
       else
       else
-        //todo: this will also pick up -- within string literals which is wrong
+        // this will also pick up -- within string literals which is wrong
         QueryList[i]:= Copy(QueryList[i], 1, Pos('--', QueryList[i]) - 1);
         QueryList[i]:= Copy(QueryList[i], 1, Pos('--', QueryList[i]) - 1);
       }
       }
     end;
     end;
@@ -641,26 +624,37 @@ end;
 
 
 { GetQuerySQLType: get query type: select, script, execute from current string list }
 { GetQuerySQLType: get query type: select, script, execute from current string list }
 
 
-function TfmQueryWindow.GetQuerySQLType(QueryList: TStringList; var SecondRealStart: Integer; var IsDDL: Boolean): TQueryTypes;
+function TfmQueryWindow.GetQuerySQLType(QueryList: TStringList; var LookAtIndex: Integer; var IsDDL: Boolean): TQueryTypes;
 var
 var
-  SQLSegment: string;
+  MassagedSQL: string;
 begin
 begin
-  IsDDL:= False;
-  if SecondRealStart < QueryList.Count then
+  Result:= qtUnknown;
+  IsDDL:= False; //default
+  if LookAtIndex < QueryList.Count then
   begin
   begin
-    SQLSegment:= SQLSegment + QueryList[SecondRealStart];
+    MassagedSQL:= LowerCase(Trim(QueryList[LookAtIndex]));
 
 
-    if (Pos('select', LowerCase(Trim(SQLSegment))) = 1) then
-      Result:= qtSelectable // Selectable
-    else
-    if Pos('setterm', LowerCase(StringReplace(SQLSegment, ' ', '', [rfReplaceAll]))) = 1 then
-      Result:= qtScript // Script
+    // Script overrides rest
+    if Pos('set term', MassagedSQL) = 1 then
+    begin
+      // Using set term does not mean the SQL you're running has to be
+      // DDL (could be an execute block or something) but it most probably is
+      IsDDL:= true;
+      exit(qtScript);
+    end;
+
+    if (Pos('select', MassagedSQL) = 1) then
+      // todo: low priority misses insert...returning,
+      // update...returning, merge.. returning...
+      Result:= qtSelectable
     else
     else
     begin
     begin
-      Result:= qtExecute; // Executable
-      IsDDL:= (Pos('create', lowerCase(Trim(SQLSegment))) = 1) or (Pos('alter',
-        lowerCase(Trim(SQLSegment))) = 1) or
-         (Pos('modify', lowerCase(Trim(SQLSegment))) = 1);
+      Result:= qtExecute;
+      IsDDL:= (Pos('alter', MassagedSQL) = 1) or
+        (Pos('create', MassagedSQL) = 1) or
+        (Pos('drop', MassagedSQL) = 1) or
+        (Pos('grant', MassagedSQL) = 1) {actually DCL} or
+        (Pos('revoke', MassagedSQL) = 1) {actually DCL};
     end;
     end;
   end;
   end;
 end;
 end;
@@ -694,16 +688,14 @@ begin
 
 
     Error:= False;
     Error:= False;
     fTerminated:= True;
     fTerminated:= True;
-
   except
   except
-  on e: exception do
-  begin
-    Error:= True;
-    ErrorMsg:= e.Message;
-    fTerminated:= True;
-  end;
+    on e: exception do
+    begin
+      Error:= True;
+      ErrorMsg:= e.Message;
+      fTerminated:= True;
+    end;
   end;
   end;
-
 end;
 end;
 
 
 
 
@@ -1047,8 +1039,7 @@ begin
         Break;
         Break;
       end
       end
       else
       else
-      // todo: better test for set term in trim(lowercase at position 1?
-      if Pos('setterm', LowerCase(StringReplace(Line, ' ', '', [rfReplaceAll]))) = 1 then
+      if Pos('set term', LowerCase(Trim(Line))) = 1 then
       begin
       begin
         Result:= qtScript;
         Result:= qtScript;
         Break;
         Break;
@@ -1566,16 +1557,19 @@ end;
 
 
 function TfmQueryWindow.GetSQLType(Query: string; var Command: string): string;
 function TfmQueryWindow.GetSQLType(Query: string; var Command: string): string;
 begin
 begin
+  Result:= 'DML'; //default
   Query:= Trim(Query);
   Query:= Trim(Query);
   if (Query <> '') and (Pos(' ', Query) > 0) then
   if (Query <> '') and (Pos(' ', Query) > 0) then
   begin
   begin
+    // to do: this does not take comments into account...
     Command:= Copy(Query, 1, Pos(' ', Query) - 1);
     Command:= Copy(Query, 1, Pos(' ', Query) - 1);
     Command:= LowerCase(Command);
     Command:= LowerCase(Command);
-    if (Command = 'alter') or (Command = 'create') or (Command = 'drop') or (Command = 'grant') or
-       (Command = 'revoke') then
-      Result:= 'DDL'
-    else
-      Result:= 'DML';
+    if (Command = 'alter') or
+       (Command = 'create') or
+       (Command = 'drop') or
+       (Command = 'grant') {actually DCL} or
+       (Command = 'revoke') {actually DCL} then
+      Result:= 'DDL';
   end;
   end;
 end;
 end;
 
 
@@ -1641,11 +1635,9 @@ begin
       Break;
       Break;
     end
     end
     else
     else
-    //todo: perhaps better test for set term in trim(querylist[i] instead
     if (QueryType = qtScript) and
     if (QueryType = qtScript) and
-      ((i > SecondRealStart) and (Pos('setterm', LowerCase(StringReplace(QueryList[i],
-      ' ', '', [rfReplaceAll]))) > 0))
-      or (i = QueryList.Count - 1) then
+      ((i > SecondRealStart) and (Pos('set term', LowerCase(Trim(QueryList[i]))) = 1)) or
+      (i = QueryList.Count - 1) then
     begin
     begin
       Result:= True;
       Result:= True;
       EndLine:= i;
       EndLine:= i;

+ 5 - 0
scriptdb.pas

@@ -62,6 +62,11 @@ var
   i: Integer;
   i: Integer;
 begin
 begin
   List.CommaText:= dmSysTables.GetDBObjectNames(dbIndex, 9, Count);
   List.CommaText:= dmSysTables.GetDBObjectNames(dbIndex, 9, Count);
+  { todo: wrap create role RDB$Admin statement - in FB 2.5+ this role is present
+  by default, in lower dbs it isn't. No way to find out in advance when writing
+  a script. No support in FB yet for CREATE OR UPDATE ROLE so probably best
+  to do it in execute block with error handling or
+  first check system tables for role existence, again with execute block }
   for i:= 0 to List.Count - 1 do
   for i:= 0 to List.Count - 1 do
     List[i]:= 'Create Role ' + List[i] + ';';
     List[i]:= 'Create Role ' + List[i] + ';';
   Result:= List.Count > 0;
   Result:= List.Count > 0;