فهرست منبع

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

Reinier Olislagers 11 سال پیش
والد
کامیت
e4c666421c
4فایلهای تغییر یافته به همراه515 افزوده شده و 6 حذف شده
  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,
   UserPermissions, TableManage, BackupRestore, CreateUser, ChangePass,
   PermissionManage, SQLHistory, CopyTable, dynlibs, ibase60dyn, dbInfo,
-  sysutils, Comparison, Update;
+  sysutils, Comparison, Update, topologicalsort;
 
 const
   Major = 1;

+ 2 - 4
scriptdb.pas

@@ -35,10 +35,6 @@ procedure RemoveParamClosing(var AParams: string);
 
 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;
 
 (********************  Script Roles  ***********************)
@@ -291,6 +287,8 @@ begin
   ProcedureScript:= TStringList.Create;
   try
     ProceduresList.CommaText:= dmSysTables.GetDBObjectNames(dbIndex, 5, Count);
+    // Get procedures in dependency order:
+    dmSysTables.SortDependencies(ProceduresList);
     List.Clear;
     for i:= 0 to ProceduresList.Count - 1 do
     begin

+ 52 - 1
systables.pas

@@ -34,6 +34,9 @@ type
       var ConstraintsArray: TConstraintCounts);
     procedure Init(dbIndex: Integer);
     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;
       var AfterBefor, OnTable, Event, Body: string; var TriggerEnabled: Boolean;
       var TriggerPosition: Integer): Boolean;
@@ -99,7 +102,7 @@ var
 
 implementation
 
-uses Main;
+uses Main,topologicalsort;
 
 function DecToBin(Dec, Len: Byte): string;
 var
@@ -191,6 +194,54 @@ begin
   sqQuery.Close;
 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  ***************)
 
 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.
+