Browse Source

Sort stored procedures (only internally - within stored procedures - right now) when scripting

Reinier Olislagers 11 years ago
parent
commit
e4c666421c
4 changed files with 515 additions and 6 deletions
  1. 1 1
      TurboBird.lpr
  2. 2 4
      scriptdb.pas
  3. 52 1
      systables.pas
  4. 460 0
      topologicalsort.pas

+ 1 - 1
TurboBird.lpr

@@ -22,7 +22,7 @@ uses
   NewDomain, SysTables, NewConstraint, NewEditField, Calen, Scriptdb,
   NewDomain, SysTables, NewConstraint, NewEditField, Calen, Scriptdb,
   UserPermissions, TableManage, BackupRestore, CreateUser, ChangePass,
   UserPermissions, TableManage, BackupRestore, CreateUser, ChangePass,
   PermissionManage, SQLHistory, CopyTable, dynlibs, ibase60dyn, dbInfo,
   PermissionManage, SQLHistory, CopyTable, dynlibs, ibase60dyn, dbInfo,
-  sysutils, Comparison, Update;
+  sysutils, Comparison, Update, topologicalsort;
 
 
 const
 const
   Major = 1;
   Major = 1;

+ 2 - 4
scriptdb.pas

@@ -35,10 +35,6 @@ procedure RemoveParamClosing(var AParams: string);
 
 
 implementation
 implementation
 
 
-{ to do: add support for dependencies when extracting script; otherwise running the script may fail
-evaluating dependency order:
-http://rosettacode.org/wiki/Topological_sort#Object_Pascal
-}
 uses SysTables, Main;
 uses SysTables, Main;
 
 
 (********************  Script Roles  ***********************)
 (********************  Script Roles  ***********************)
@@ -291,6 +287,8 @@ begin
   ProcedureScript:= TStringList.Create;
   ProcedureScript:= TStringList.Create;
   try
   try
     ProceduresList.CommaText:= dmSysTables.GetDBObjectNames(dbIndex, 5, Count);
     ProceduresList.CommaText:= dmSysTables.GetDBObjectNames(dbIndex, 5, Count);
+    // Get procedures in dependency order:
+    dmSysTables.SortDependencies(ProceduresList);
     List.Clear;
     List.Clear;
     for i:= 0 to ProceduresList.Count - 1 do
     for i:= 0 to ProceduresList.Count - 1 do
     begin
     begin

+ 52 - 1
systables.pas

@@ -34,6 +34,9 @@ type
       var ConstraintsArray: TConstraintCounts);
       var ConstraintsArray: TConstraintCounts);
     procedure Init(dbIndex: Integer);
     procedure Init(dbIndex: Integer);
     function GetDBObjectNames(DatabaseIndex, TVIndex: Integer; var Count: Integer): string;
     function GetDBObjectNames(DatabaseIndex, TVIndex: Integer; var Count: Integer): string;
+    // Returns object list (list of object names, i.e. tables, views) sorted by dependency
+    // Limits sorting within one category (e.g. views)
+    procedure SortDependencies(var ObjectList: TStringList);
     function GetTriggerInfo(DatabaseIndex: Integer; ATriggername: string;
     function GetTriggerInfo(DatabaseIndex: Integer; ATriggername: string;
       var AfterBefor, OnTable, Event, Body: string; var TriggerEnabled: Boolean;
       var AfterBefor, OnTable, Event, Body: string; var TriggerEnabled: Boolean;
       var TriggerPosition: Integer): Boolean;
       var TriggerPosition: Integer): Boolean;
@@ -99,7 +102,7 @@ var
 
 
 implementation
 implementation
 
 
-uses Main;
+uses Main,topologicalsort;
 
 
 function DecToBin(Dec, Len: Byte): string;
 function DecToBin(Dec, Len: Byte): string;
 var
 var
@@ -191,6 +194,54 @@ begin
   sqQuery.Close;
   sqQuery.Close;
 end;
 end;
 
 
+procedure TdmSysTables.SortDependencies(var ObjectList: TStringList);
+const
+  QueryTemplate=
+    'select rdb$dependent_name, ' +
+    ' rdb$depended_on_name, ' +
+    ' rdb$dependent_type, '+
+    ' rdb$depended_on_type '+
+    ' from rdb$dependencies ';
+var
+  i: integer;
+  TopSort: TTopologicalSort;
+begin
+  TopSort:= TTopologicalSort.Create;
+  try
+    sqQuery.SQL.Text:= QueryTemplate;
+    sqQuery.Open;
+    // First add all objects that should end up as results without any depdencies
+    for i:=0 to ObjectList.Count-1 do
+    begin
+      TopSort.AddNode(ObjectList[i]);
+    end;
+    // Now add dependencies:
+    while not sqQuery.EOF do
+    begin
+      for i:=0 to ObjectList.Count-1 do
+      begin
+        if trim(sqQuery.FieldByName('rdb$dependent_name').AsString)=uppercase(ObjectList[i]) then
+          begin
+            // Get kind of object using dependency type in query;
+            // limit to same category (i.e. all views or all stored procedures)
+            if sqQuery. FieldByName('rdb$dependent_type').AsInteger=
+              sqQuery. FieldByName('rdb$depended_on_type').AsInteger then
+              TopSort.AddDependency(
+                trim(sqQuery.FieldByName('rdb$dependent_name').AsString),
+                trim(sqQuery.FieldByName('rdb$depended_on_name').AsString));
+          end;
+      end;
+      sqQuery.Next;
+    end;
+    sqQuery.Close;
+    // Resulting sorted list should end up in ObjectList:
+    ObjectList.Clear;
+    TopSort.Sort(ObjectList);
+  finally
+    TopSort.Free;
+  end;
+end;
+
 (***********  Get Trigger Info  ***************)
 (***********  Get Trigger Info  ***************)
 
 
 function TdmSysTables.GetTriggerInfo(DatabaseIndex: Integer; ATriggername: string;
 function TdmSysTables.GetTriggerInfo(DatabaseIndex: Integer; ATriggername: string;

+ 460 - 0
topologicalsort.pas

@@ -0,0 +1,460 @@
+unit topologicalsort;
+{ Dependency checker/topological sort
+
+  Copyright (C) 2010-2014 Reinier Olislagers
+
+  This library is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Library General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or (at your
+  option) any later version.
+
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
+  for more details.
+
+  You should have received a copy of the GNU Library General Public License
+  along with this library; if not, write to the Free Software Foundation,
+  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+}
+
+{$mode objfpc}{$H+}
+//{$DEFINE TOPODEBUG} //if you want to see loads of diagnostic info
+
+interface
+
+uses
+  Classes, SysUtils;
+
+type
+  RNodeIndex = record
+    NodeName: String; //Name of the node
+    //Index: integer; //Index number used in DepGraph. For now, we can distill the index from the array index. If we want to use a TList or similar, we'd need an index property
+    Order: integer;  //Order when sorted
+  end;
+
+  RDepGraph = record
+    Node: integer;  //Refers to Index in NodeIndex
+    DependsOn: integer; //The Node depends on this other Node.
+  end;
+
+  { TTopologicalSort }
+  TTopologicalSort = class(TObject)
+  private
+    Nodes: array of RNodeIndex;
+    DependencyGraph: array of RDepGraph;
+    FCanBeSorted: boolean;
+    function SearchNode(NodeName: String): integer;
+    function SearchIndex(NodeID: integer): String;
+    function DepFromNodeID(NodeID: integer): integer;
+    function DepFromDepID(DepID: integer): integer;
+    function DepFromNodeIDDepID(NodeID, DepID: integer): integer;
+    procedure DelDependency(const Index: integer);
+  public
+    constructor Create;
+    destructor Destroy; override;
+    procedure Sort(const UnorderedInput: TStringList; var SortedOutput: TStringList); //Directly sort input into output
+    procedure Sort(var SortedOutput: TStringList); //Sort nodes into output
+    procedure AddNode(NodeName: String); // Add an item to the input
+    procedure AddDependency(NodeName, DependsOn: String); //Add a dependency to another item on the input
+    procedure AddNodeDependencies(NodeAndDependencies: TStringList); // Each string has node, and the nodes it depends on. This allows insertion of an entire dependency graph at once
+    //procedure DelNode(NodeName: String);
+    procedure DelDependency(NodeName, DependsOn: String);
+
+    property CanBeSorted: boolean read FCanBeSorted;
+
+  end;
+
+implementation
+
+const
+  INVALID = -1;
+// index not found for index search functions, no sort order defined, or record invalid/deleted
+
+function TTopologicalSort.SearchNode(NodeName: String): integer;
+var
+  Counter: integer;
+begin
+  // Return -1 if node not found. If node found, return index in array
+  Result := INVALID;
+  for Counter := 0 to High(Nodes) do
+  begin
+    if Nodes[Counter].NodeName = NodeName then
+    begin
+      Result := Counter;
+      break;
+    end;
+  end;
+end;
+
+function TTopologicalSort.SearchIndex(NodeID: integer): String;
+  //Look up name for the index
+begin
+  if (NodeID > 0) and (NodeID <= High(Nodes)) then
+  begin
+    Result := Nodes[NodeID].NodeName;
+  end
+  else
+  begin
+    Result := 'ERROR'; //something's fishy, this shouldn't happen
+  end;
+end;
+
+function TTopologicalSort.DepFromNodeID(NodeID: integer): integer;
+  // Look for Node index number in the dependency graph
+  // and return the first node found. If nothing found, return -1
+var
+  Counter: integer;
+begin
+  Result := INVALID;
+  for Counter := 0 to High(DependencyGraph) do
+  begin
+    if DependencyGraph[Counter].Node = NodeID then
+    begin
+      Result := Counter;
+      break;
+    end;
+  end;
+end;
+
+function TTopologicalSort.DepFromDepID(DepID: integer): integer;
+  // Look for dependency index number in the dependency graph
+  // and return the index for the first one found. If nothing found, return -1
+var
+  Counter: integer;
+begin
+  Result := INVALID;
+  for Counter := 0 to High(DependencyGraph) do
+  begin
+    if DependencyGraph[Counter].DependsOn = DepID then
+    begin
+      Result := Counter;
+      break;
+    end;
+  end;
+end;
+
+function TTopologicalSort.DepFromNodeIDDepID(NodeID, DepID: integer): integer;
+  // Shows index for the dependency from NodeID on DepID, or INVALID if not found
+var
+  Counter: integer;
+begin
+  Result := INVALID;
+  for Counter := 0 to High(DependencyGraph) do
+  begin
+    if DependencyGraph[Counter].Node = NodeID then
+      if DependencyGraph[Counter].DependsOn = DepID then
+      begin
+        Result := Counter;
+        break;
+      end;
+  end;
+end;
+
+procedure TTopologicalSort.DelDependency(const Index: integer);
+// Removes dependency from array.
+// Is fastest when the dependency is near the top of the array
+// as we're copying the remaining elements.
+var
+  Counter: integer;
+  OriginalLength: integer;
+begin
+  OriginalLength := Length(DependencyGraph);
+  if Index = OriginalLength - 1 then
+  begin
+    SetLength(DependencyGraph, OriginalLength - 1);
+  end;
+  if Index < OriginalLength - 1 then
+  begin
+    for Counter := Index to OriginalLength - 2 do
+    begin
+      DependencyGraph[Counter] := DependencyGraph[Counter + 1];
+    end;
+    SetLength(DependencyGraph, OriginalLength - 1);
+  end;
+  if Index > OriginalLength - 1 then
+  begin
+    // This could happen when deleting on an empty array:
+    raise Exception.Create('Tried to delete index ' + IntToStr(Index) +
+      ' while the maximum index was ' + IntToStr(OriginalLength - 1));
+  end;
+end;
+
+constructor TTopologicalSort.Create;
+begin
+  inherited Create;
+end;
+
+destructor TTopologicalSort.Destroy;
+begin
+  // Clear up data just to make sure:
+  Finalize(DependencyGraph);
+  Finalize(Nodes);
+  inherited;
+end;
+
+procedure TTopologicalSort.Sort(const UnorderedInput: TStringList; var SortedOutput: TStringList);
+begin
+  AddNodeDependencies(UnorderedInput);
+  Sort(SortedOutput);
+end;
+
+procedure TTopologicalSort.Sort(var SortedOutput: TStringList);
+var
+  {$IFDEF TOPODEBUG}
+  DebugCounter: integer;
+  {$ENDIF}
+  Counter: integer;
+  NodeCounter: integer;
+  OutputSortOrder: integer;
+  DidSomething: boolean; //used to detect cycles (circular references)
+  Node: integer;
+begin
+  OutputSortOrder := 0;
+  DidSomething := True; // prime the loop below
+  FCanBeSorted := True; //hope for the best.
+  {$IFDEF TOPODEBUG}
+  Sortedoutput.Add('debug: nodes before processing: ');
+  for DebugCounter := 0 to High(Nodes) do
+  begin
+    SortedOutput.Add('debug: node ' + IntToStr(DebugCounter) + ' has name:' +
+      Nodes[DebugCounter].Nodename + ' - sort order: ' +
+      IntToStr(Nodes[DebugCounter].Order));
+  end;
+  SortedOutput.add('debug: dependencies before processing: ');
+  for DebugCounter := 0 to High(DependencyGraph) do
+  begin
+    SortedOutput.Add(
+      'debug: dep #' + IntToStr(DebugCounter) + ': ' +
+      Nodes[DependencyGraph[DebugCounter].Node].NodeName + '(' +
+      IntToStr(DependencyGraph[DebugCounter].Node) + ') depends on: ' +
+      Nodes[DependencyGraph[DebugCounter].DependsOn].NodeName + '(' +
+      IntToStr(DependencyGraph[DebugCounter].DependsOn) + ')'
+      );
+  end;
+  {$ENDIF}
+  while (DidSomething = True) do
+  begin
+    // 1. Find all nodes (now) without dependencies, output them first and remove the dependencies:
+    // 1.1 Nodes that are not present in the dependency graph at all:
+    {$IFDEF TOPODEBUG}
+    SortedOutput.Add('debug: 1.1 only in node list. begin. outputsortorder = ' +
+      IntToStr(OutputSortOrder));
+    {$ENDIF}
+    for Counter := 0 to High(Nodes) do
+    begin
+      if DepFromNodeID(Counter) = INVALID then
+      begin
+        if DepFromDepID(Counter) = INVALID then
+        begin
+          // Node doesn't occur in either side of the dependency graph, so it has sort order 0:
+          DidSomething := True;
+          {$IFDEF TOPODEBUG}
+          SortedOutput.Add('debug: 1.1 found counter: ' + IntToStr(Counter) +
+            ', nodename:' + Nodes[Counter].NodeName);
+          {$ENDIF}
+          if (Nodes[Counter].Order = INVALID) or
+            (Nodes[Counter].Order > OutputSortOrder) then
+          begin
+            // Enter sort order if the node doesn't have a lower valid order already.
+            Nodes[Counter].Order := OutputSortOrder;
+          end;
+        end; //Invalid Dep
+      end; //Invalid Node
+    end; //Count
+    // Done with the first batch, so we can increase the sort order:
+    OutputSortOrder := OutputSortOrder + 1;
+    {$IFDEF TOPODEBUG}
+    SortedOutput.Add('debug: 1.1 only in node list. end. outputsortorder = ' +
+      IntToStr(OutputSortOrder));
+    {$ENDIF}
+    // 1.2 Nodes that are only present on the right hand side of the dep graph:
+    {$IFDEF TOPODEBUG}
+    SortedOutput.Add('debug: 1.2 only on right hand side. begin for SortOrder: ' +
+      IntToStr(OutputSortOrder) + ' - high(DepGraph):' +
+      IntToStr(High(DependencyGraph)));
+    {$ENDIF}
+    DidSomething := False;
+    // reverse order so we can delete dependencies without passing upper array
+    for Counter := High(DependencyGraph) downto 0 do
+    begin
+      Node := DependencyGraph[Counter].DependsOn; //the depended node
+      if (DepFromNodeID(Node) = INVALID) then
+      begin
+        DidSomething := True;
+        //Delete dependency so we don't hit it again:
+        {$IFDEF TOPODEBUG}
+        SortedOutput.Add('debug: going to delete dependency ' + IntToStr(Counter) +
+          ' from: *' + Nodes[DependencyGraph[Counter].Node].NodeName +
+          '* on: *' + Nodes[DependencyGraph[Counter].DependsOn].NodeName + '*.');
+        {$ENDIF}
+        DelDependency(Counter);
+        if (Nodes[Node].Order = INVALID) or (Nodes[Node].Order > OutputSortOrder) then
+        begin
+          // Enter sort order if the node doesn't have a lower valid order already.
+          Nodes[Node].Order := OutputSortOrder;
+          {$IFDEF TOPODEBUG}
+          SortedOutput.add('debug: current order for node ' + Nodes[Node].NodeName +
+            ' changed to: ' + IntToStr(Nodes[Node].Order));
+          {$ENDIF}
+        end;
+      end;
+      OutputSortOrder := OutputSortOrder + 1; //next iteration
+    end;
+    // 2. Go back to 1 until we can't do more work, and do some bookkeeping:
+    OutputSortOrder := OutputSortOrder + 1;
+  end; //outer loop for 1 to 2
+  OutputSortOrder := OutputSortOrder - 1; //fix unused last loop.
+
+  {$IFDEF TOPODEBUG}
+  SortedOutput.add('debug: nodes after end of processing: ');
+  for DebugCounter := 0 to High(Nodes) do
+  begin
+    SortedOutput.Add('debug: node ' + IntToStr(DebugCounter) + ' has name:' +
+      Nodes[DebugCounter].Nodename + ' - sort order: ' +
+      IntToStr(Nodes[DebugCounter].Order));
+  end;
+  // Output all dependencies:
+  SortedOutput.add('debug: dependencies after processing: ');
+  for DebugCounter := 0 to High(DependencyGraph) do
+  begin
+    SortedOutput.Add(
+      'debug: dep #' + IntToStr(DebugCounter) + ': ' +
+      Nodes[DependencyGraph[DebugCounter].Node].NodeName + '(' +
+      IntToStr(DependencyGraph[DebugCounter].Node) + ') depends on: ' +
+      Nodes[DependencyGraph[DebugCounter].DependsOn].NodeName + '(' +
+      IntToStr(DependencyGraph[DebugCounter].DependsOn) + ')'
+      );
+  end;
+  {$ENDIF}
+  // 2. If we have dependencies left, we have a cycle; exit.
+  if (High(DependencyGraph) > 0) then
+  begin
+    FCanBeSorted := False; //indicate we have a cycle
+    SortedOutput.Add('Cycle (circular dependency) detected, cannot sort further. Dependencies left:');
+    for Counter := 0 to High(DependencyGraph) do
+    begin
+      SortedOutput.Add(SearchIndex(DependencyGraph[Counter].Node) +
+        ' depends on: ' + SearchIndex(DependencyGraph[Counter].DependsOn));
+    end;
+  end
+  else
+  begin
+    // No cycle:
+    // Now parse results, if we have them
+    for Counter := 0 to OutputSortOrder do
+    begin
+      for NodeCounter := 0 to High(Nodes) do
+      begin
+        if Nodes[NodeCounter].Order = Counter then
+        begin
+          SortedOutput.Add(Nodes[NodeCounter].NodeName
+          {$IFDEF TOPODEBUG}
+            + ' (' + IntToStr(Nodes[NodeCounter].Order) + ')'
+          {$ENDIF}
+            );
+        end;
+      end; //output each result
+    end; //order iteration
+  end; //cycle detection
+end;
+
+procedure TTopologicalSort.AddNode(NodeName: String);
+var
+  NodesNewLength: integer;
+begin
+  // Adds node; make sure we don't add duplicate entries
+  if SearchNode(NodeName) = INVALID then
+  begin
+    NodesNewLength := Length(Nodes) + 1;
+    SetLength(Nodes, NodesNewLength);
+    Nodes[NodesNewLength - 1].NodeName := NodeName; //Arrays are 0 based
+    //Nodes[NodesNewLength -1].Index :=  //If we change the object to a tlist or something, we already have an index property
+    Nodes[NodesNewLength - 1].Order := INVALID; //default value
+  end;
+end;
+
+procedure TTopologicalSort.AddDependency(NodeName, DependsOn: String);
+begin
+  // Make sure both nodes in the dependency exist as a node
+  if SearchNode(NodeName) = INVALID then
+  begin
+    Self.AddNode(NodeName);
+  end;
+  if SearchNode(DependsOn) = INVALID then
+  begin
+    Self.AddNode(DependsOn);
+  end;
+  // Add the dependency, only if we don't depend on ourselves:
+  if NodeName <> DependsOn then
+  begin
+    SetLength(DependencyGraph, Length(DependencyGraph) + 1);
+    DependencyGraph[High(DependencyGraph)].Node := SearchNode(NodeName);
+    DependencyGraph[High(DependencyGraph)].DependsOn := SearchNode(DependsOn);
+  end;
+end;
+
+procedure TTopologicalSort.AddNodeDependencies(NodeAndDependencies: TStringList);
+// Takes a stringlist containing a list of strings. Each string contains node names
+// separated by spaces. The first node depends on the others. It is permissible to have
+// only one node name, which doesn't depend on anything.
+// This procedure will add the dependencies and the nodes in one go.
+var
+  Deplist: TStringList;
+  StringCounter: integer;
+  NodeCounter: integer;
+begin
+  if Assigned(NodeAndDependencies) then
+  begin
+    DepList := TStringList.Create;
+    try
+      for StringCounter := 0 to NodeAndDependencies.Count - 1 do
+      begin
+        // For each string in the argument: split into names, and process:
+        DepList.Delimiter := ' '; //use space to separate the entries
+        DepList.StrictDelimiter := False; //allows us to ignore double spaces in input.
+        DepList.DelimitedText := NodeAndDependencies[StringCounter];
+        for NodeCounter := 0 to DepList.Count - 1 do
+        begin
+          if NodeCounter = 0 then
+          begin
+            // Add the first node, which might be the only one.
+            Self.AddNode(Deplist[0]);
+          end;
+
+          if NodeCounter > 0 then
+          begin
+            // Only add dependency from the second item onwards
+            // The AddDependency code will automatically add Deplist[0] to the Nodes, if required
+            Self.AddDependency(DepList[0], DepList[NodeCounter]);
+          end;
+        end;
+      end;
+    finally
+      DepList.Free;
+    end;
+  end;
+end;
+
+procedure TTopologicalSort.DelDependency(NodeName, DependsOn: String);
+// Delete the record.
+var
+  NodeID: integer;
+  DependsID: integer;
+  Dependency: integer;
+begin
+  NodeID := Self.SearchNode(NodeName);
+  DependsID := Self.SearchNode(DependsOn);
+  if (NodeID <> INVALID) and (DependsID <> INVALID) then
+  begin
+    // Look up dependency and delete it.
+    Dependency := Self.DepFromNodeIDDepID(NodeID, DependsID);
+    if (Dependency <> INVALID) then
+    begin
+      Self.DelDependency(Dependency);
+    end;
+  end;
+end;
+
+end.
+