如何使用dbExpress执行SQL脚本?

cyt*_*nox 2 delphi dbexpress

我正在将一个旧的Delphi应用程序(使用ZeosDB)迁移到Delphi XE2.我想使用dbExpress作为ZeosDB替代Firebird 2.5或MS-SQL的数据库访问.有很多sql脚本用于创建我需要运行的表,视图和存储过程.Firebird脚本命令与带有"GO"的^,MS-SQL脚本命令分开.

如何使用dbexpress连接在数据库上运行这些脚本?ZeosDB提供了一个TZSqlProcessor,但我找不到dbExpress的任何等效组件.

Tom*_*gen 6

我不使用DBExpress,但据我所知,您可以一次只执行一个SQL命令(通过Execute或ExecuteDirect).换句话说,您不能将整个脚本放入Execute方法中.

这与FireBird和MS SQL使用的不同命令语法无关(^与GO).您必须了解'^'符号或'GO'命令不是"TSQL命令"!两者都是用于执行针对SQL引擎的命令的相应应用程序使用的特定命令分隔符.相反,它是"Firebird Manager"(或其称之为)和"SQL Query Profiler"(或"SQL Server Management Studio")之间的区别.

解决方案是使用某种解析器,将脚本拆分为单个命令列表,然后逐步执行TSQLConnection.Execute这些命令.

像这样的伪代码:

var
  DelimiterPos: Integer;
  S: String;
  Command: String;
begin
  S:= ScriptFile; // ScriptFile: String - your whole script
  While True Do
  begin
    DelimiterPos:= Pos('^', ScriptFile);
    if DelimiterPos = 0 then DelimiterPos:= Length(S);
    Command:= Copy(S, 1, DelimiterPos - 1);
    SQLConnection.Execute(Command);
    Delete(S, 1, DelimiterPos);
    if Lengh(S) = 0 Then Exit;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

请注意,只有在脚本中的任何位置都没有使用"^"符号而是使用命令分隔符的情况下,上面的示例才能正常工作.

作为旁注,我确信有一些已经构建的组件可以为您做这些(如TZSQLProcessor).我不知道有任何指向你.

旁注2:我很确定,您必须修改脚本才能与MS SQL完全兼容.虽然Firebird和MS SQL都是SQL服务器,但DML/DDL语法总是存在差异.

编辑:

  1. 如果您可以将SQL脚本"重写"到代码中,则可以使用Jedi VCL jvStringHolder组件.将每个单独的命令作为一个项目(TStrings类型)放在jvStringHolder中.

  2. 创建解析器相当复杂,但不可撤销.借助SynEdit的灵感,我根据您的需要制作了这些内容:使用TSQLScript.ParseScript加载脚本,然后迭代Command [index:integer]属性.SQLLexer不是完整的SQL Lexer,而是通过respec注释,括号,代码折叠等来实现关键字分离.我还在注释中添加了一个特殊的语法($ sign in comment block),这有助于我将标题放入脚本中.这是我的一个项目的完全复制粘贴.我不再给出任何解释,但我希望你能得到这个想法,让它在你的项目中运行.

单元SQLParser;

interface

type

  TTokenKind = (tkUknown, tkEOF, tkComment, tkKeyword, tkIdentifier,
                tkCommentParam, tkCommentParamValue, tkCommandEnd, tkCRLF);

  TBlockKind = (bkNone, bkLineComment, bkBlockComment);

  TSQLLexer = class
  private
    FBlockKind: TBlockKind;
    FParseString: String;
    FPosition: PChar;
    FTokenKind: TTokenKind;
    FTokenPosition: PChar;
    function GetToken: String;
    procedure Reset;
    procedure SetParseString(Value: String);
  protected
    procedure ReadComment;
    procedure ReadCommentParam;
    procedure ReadCommentParamValue;
    procedure ReadCRLF;
    procedure ReadIdentifier;
    procedure ReadSpace;
  public
    constructor Create(ParseString: String);
    function NextToken: TTokenKind;

    property Position: PChar read FPosition;
    property SQLText: String read FParseString write SetParseString;
    property Token: String read GetToken;
    property TokenKind: TTokenKind read FTokenKind;
    property TokenPosition: PChar read FTokenPosition;
  end;



implementation

uses SysUtils;

{ TSQLLexer }

constructor TSQLLexer.Create(ParseString: string);
begin
  inherited Create;
  FParseString:= ParseString;
  Reset;
end;

function TSQLLexer.GetToken;
begin
  SetString(Result, FTokenPosition, FPosition - FTokenPosition);
end;

function TSQLLexer.NextToken: TTokenKind;
begin
  case FBlockKind of
    bkLineComment, bkBlockComment: ReadComment;
    else
      case FPosition^ of
      #0: FTokenKind:= tkEOF;
      #1..#9, #11, #12, #14..#32:
        begin
          ReadSpace;
          NextToken;
        end;
      #10, #13: ReadCRLF;
      '-':
        if PChar(FPosition +1)^ = '-' then
          ReadComment
        else
          Inc(FPosition);
      '/':
        if PChar(FPosition +1)^ = '*' then
          ReadComment
        else
          Inc(FPosition);
      'a'..'z', 'A'..'Z': ReadIdentifier;
      ';':
        begin
          FTokenPosition:= FPosition;
          Inc(FPosition);
          FTokenKind:= tkCommandEnd;
        end
      else
        Inc(FPosition);
      end;
  end;
  Result:= FTokenKind;
end;


procedure TSQLLexer.ReadComment;
begin
  FTokenPosition:= FPosition;
  if not (FBlockKind in [bkLineComment, bkBlockComment])  then
  begin
    if FPosition^ = '/' then
      FBlockKind:= bkBlockComment
    else
      FBlockKind:= bkLineComment;
    Inc(FPosition, 2);
  end;
  case FPosition^ of
    '$': ReadCommentParam;
    ':': ReadCommentParamValue;
  else
    while not CharInSet(FPosition^, [#0, '$']) do
    begin
      if FBlockKind = bkBlockComment then
      begin
        if (FPosition^ = '*') And (PChar(FPosition + 1)^ = '/') then
        begin
          Inc(FPosition, 2);
          FBlockKind:= bkNone;
          Break;
        end;
      end
      else
      begin
        if CharInSet(Fposition^, [#10, #13]) then
        begin
          ReadCRLF;
          FBlockKind:= bkNone;
          Break;
        end;
      end;
      Inc(FPosition);
    end;
    FTokenKind:= tkComment;
  end;
end;

procedure TSQLLexer.ReadCommentParam;
begin
  Inc(FPosition);
  ReadIdentifier;
  FTokenKind:= tkCommentParam;
end;

procedure TSQLLexer.ReadCommentParamValue;
begin
  Inc(FPosition);
  ReadSpace;
  FTokenPosition:= FPosition;
  while not CharInSet(FPosition^, [#0, #10, #13]) do
    Inc(FPosition);
  FTokenKind:= tkCommentParamValue;
end;

procedure TSQLLexer.ReadCRLF;
begin
  while CharInSet(FPosition^, [#10, #13]) do
    Inc(FPosition);
  FTokenKind:= tkCRLF;
end;

procedure TSQLLexer.ReadIdentifier;
begin
  FTokenPosition:= FPosition;
  while CharInSet(FPosition^, ['a'..'z', 'A'..'Z', '_']) do
    Inc(FPosition);

  FTokenKind:= tkIdentifier;

  if Token = 'GO' then
    FTokenKind:= tkKeyword;
end;

procedure TSQLLexer.ReadSpace;
begin
  while CharInSet(FPosition^, [#1..#9, #11, #12, #14..#32]) do
  Inc(FPosition);
end;

procedure TSQLLexer.Reset;
begin
  FTokenPosition:= PChar(FParseString);
  FPosition:= FTokenPosition;
  FTokenKind:= tkUknown;
  FBlockKind:= bkNone;
end;

procedure TSQLLexer.SetParseString(Value: String);
begin
  FParseString:= Value;
  Reset;
end;

end.
Run Code Online (Sandbox Code Playgroud)

解析器:

type
  TScriptCommand = class
  private
    FCommandText: String;
  public
    constructor Create(ACommand: String);
    property CommandText: String read FCommandText write FCommandText;
  end;

  TSQLScript = class
  private
    FCommands: TStringList;
    function GetCount: Integer;
    function GetCommandList: TStrings;
    function GetCommand(index: Integer): TScriptCommand;
  protected
    procedure AddCommand(AName: String; ACommand: String);
  public
    Constructor Create;
    Destructor Destroy; override;
    procedure ParseScript(Script: TStrings);

    property Count: Integer read GetCount;
    property CommandList: TStrings read GetCommandList;
    property Command[index: integer]: TScriptCommand read GetCommand;
  end;

{ TSQLScriptCommand }

constructor TScriptCommand.Create(ACommand: string);
begin
  inherited Create;
  FCommandText:= ACommand;
end;

{ TSQLSCript }

constructor TSQLScript.Create;
begin
  inherited;
  FCommands:= TStringList.Create(True);
  FCommands.Duplicates:= dupIgnore;
  FCommands.Sorted:= False;
end;

destructor TSQLScript.Destroy;
begin
  FCommands.Free;
  inherited;
end;

procedure TSQLScript.AddCommand(AName, ACommand: String);
var
  ScriptCommand: TScriptCommand;
  S: String;
begin
  if AName = '' then
    S:= SUnnamedCommand
  else
    S:= AName;
  ScriptCommand:= TScriptCommand.Create(ACommand);
  FCommands.AddObject(S, ScriptCommand);
end;

function TSQLScript.GetCommand(index: Integer): TScriptCommand;
begin
  Result:= TScriptCommand(FCommands.Objects[index]);
end;

function TSQLScript.GetCommandList: TStrings;
begin
  Result:= FCommands;
end;

function TSQLScript.GetCount: Integer;
begin
  Result:= FCommands.Count;
end;

procedure TSQLScript.ParseScript(Script: TStrings);
var
  Title: String;
  Command: String;
  LastParam: String;
  LineParser: TSQLLexer;
  IsNewLine: Boolean;
  LastPos: PChar;

  procedure AppendCommand;
  var
    S: String;
  begin
    SetString(S, LastPos, LineParser.Position - LastPos);
    Command:= Command + S;
    LastPos:= LineParser.Position;
  end;

  procedure FinishCommand;
  begin
    if Command <> '' then
      AddCommand(Title, Command);
    Title:= '';
    Command:= '';
    LastPos:= LineParser.Position;
    if LastPos^ = ';' then Inc(LastPos);
  end;

begin
  LineParser:= TSQLLexer.Create(Script.Text);
  try
    LastPos:= LineParser.Position;
    IsNewLine:= True;
    repeat
      LineParser.NextToken;
      case LineParser.TokenKind of
        tkComment: LastPos:= LineParser.Position;
        tkCommentParam:
          begin
            LastParam:= UpperCase(LineParser.Token);
            LastPos:= LineParser.Position;
          end;
        tkCommentParamValue:
          if LastParam = 'TITLE' then
          begin
            Title:= LineParser.Token;
            LastParam:= '';
            LastPos:= LineParser.Position;
          end;
        tkKeyword:
            if (LineParser.Token = 'GO') and IsNewLine then FinishCommand
            else
              AppendCommand;
        tkEOF:
          FinishCommand;
        else
          AppendCommand;
      end;
      IsNewLine:= LineParser.TokenKind in [tkCRLF, tkCommandEnd];
    until LineParser.TokenKind = tkEOF;
  finally
    LineParser.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)