Как выполнить SQL-скрипт с использованием dbExpress? - PullRequest
2 голосов
/ 01 марта 2012

Я перевожу старое приложение Delphi (используя ZeosDB) в Delphi XE2. Я хочу использовать dbExpress в качестве замены ZeosDB для доступа к базе данных Firebird 2.5 или MS-SQL. Есть много SQL-скриптов для создания таблиц, просмотра и хранимых процедур, которые мне нужно запустить. Команды сценария Firebird разделяются с помощью команд сценариев ^, MS-SQL с "GO".

Как я могу запустить эти сценарии в базе данных, используя соединение dbexpress? ZeosDB предоставляет TZSqlProcessor, но я не могу найти эквивалентный компонент для dbExpress.

Ответы [ 5 ]

6 голосов
/ 05 марта 2012

Я не использую DBExpress, но, насколько мне известно, вы можете выполнять (либо через Execute, либо через ExecuteDirect) только одну команду SQL за раз.Другими словами, вы не можете поместить весь скрипт в метод Execute.

Это не связано с разным синтаксисом команд, используемым FireBird и MS SQL (^ против GO).Вы должны понимать, что знак «^» или команда «GO» не является «командой TSQL»!Оба являются определенными разделителями команд, используемыми соответствующим приложением, используемым для выполнения команд для механизмов SQL.Это разница между «Firebird Manager» (или как он называется) и «SQL Query Profiler» (или «SQL Server Management Studio»).

Решение состоит в том, чтобы использовать какой-то синтаксический анализатор, разбить скриптв список отдельных команд и TSQLConnection. Выполните эти команды одну за другой.

Примерно такой псевдокод:

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;

Обратите внимание, что приведенный выше пример будет работать правильно только вслучаи, когда знак '^' используется не где-либо в скрипте, а как разделитель команд.

В качестве идентификатора, я уверен, что есть некоторые уже созданные компоненты, которые сделают это для вас (например, TZSQLProcessor).Я не знаю никого, на кого можно было бы указать.

Sidenote 2: Я почти уверен, что вам придется изменить свои сценарии, чтобы они были полностью совместимы с MS SQL.Несмотря на то, что Firebird и MS SQL оба являются серверами SQL, всегда существует разница в синтаксисе DML / DDL.

Редактировать:

  1. Если вы можете "переписать"SQL-скрипт в коде, вы можете использовать компонент Jvidi VCL jvStringHolder.Поместите каждую отдельную команду как один элемент (типа TStrings) в jvStringHolder .

  2. Создание синтаксического анализатора довольно сложно, но его нельзя отменить.По вдохновению SynEdit я сделал эти предложения именно для того, что вам нужно: загрузить скрипт с TSQLScript.ParseScript, а затем выполнить итерацию через свойство Command [index: integer].SQLLexer не является полноценным SQL Lexer, но реализует разделение ключевых слов с учетом комментариев, скобок, свертывания кода и т. Д. Я также добавил специальный синтаксис в комментарии ($ знак в блоке комментариев), который помогает мне добавлять заголовки в сценарий.Это полная копия-вставка из одного из моих проектов.Я не даю больше объяснений, но я надеюсь, что вы поймете эту идею и запустите ее в своем проекте.

unit 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.

парсер:

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;
3 голосов
/ 01 марта 2012

Вам необходимо использовать TSQLConnection.Это компонент имеет два метода, Execute и ExecuteDirect.Первый не принимает параметры, но второй метод принимает.

Используя первый метод:

procedure TForm1.Button1Click(Sender: TObject);
var
   MeuSQL: String;
begin
   MeuSQL := 'INSERT INTO YOUR_TABLE ('FIELD1', 'FIELD2') VALUES ('VALUE1', 'VALUE2')';
   SQLConnection.ExecuteDirect(MeuSQL);
end;

Если хотите, вы можете использовать транзакцию.второй метод:

procedure TForm1.Button1Click(Sender: TObject);
var
  MySQL: string;
  MyParams: TParams;
begin
  MySQL := 'INSERT INTO TABLE ("FIELD1", "FIELD2") VALUE (:PARAM1, :PARAM2)';
  MyParams.Create;
  MyParams.CreateParam(ftString, 'PARAM1', ptInput).Value := 'Seu valor1';
  MyParams.CreateParam(ftString, 'PARAM2', ptInput).Value := 'Seu valor2';

  SQLConnection1.Execute(MySQL,MyParams, Nil);
end;
1 голос
/ 06 марта 2012

Это не ограничение dbExpress, а ограничение языка SQL.Я не уверен насчет T-SQL, но похоже, что GO похож на анонимный блок в Oracle PL / SQL.Вы можете поместить следующий код PL / SQL в TSqlDataSet.CommandText и вызвать ExecSQL для создания нескольких таблиц.Может быть, T-SQL имеет аналогичный способ сделать это:

begin
execute immediate 'CREATE TABLE suppliers 
( supplier_id number(10) not null, 
  supplier_name varchar2(50) not null, 
  contact_name varchar2(50)  
)'; 
execute immediate 'CREATE TABLE customers 
( customer_id number(10) not null, 
  customer_name varchar2(50) not null, 
  address varchar2(50),  
  city varchar2(50),  
  state varchar2(25),  
  zip_code varchar2(10),  
  CONSTRAINT customers_pk PRIMARY KEY (customer_id) 
)';
end;
1 голос
/ 06 марта 2012

Я почти на 90% уверен, что вы не сможете, по крайней мере, не проанализировать отдельные команды между GO, а затем последовательно выполнить каждую из них, что, как вы уже указали, проблематично.

(Я был бы рад опровергнуть вышесказанное и весьма заинтересован в том, чтобы увидеть решение ...)

Если вы просто используете сценарий в качестве логики инициализации (например, для создания таблиц и т. Д.), Другое решение, которое вы могли бы рассмотреть, - это запустить сценарии в командном файле и выполнить их с помощью 'Sqlcmd', который может быть выполнен через ваше приложение delphi (используя ShellExecute), которое затем ожидает его завершения, прежде чем продолжить.

Не так элегантно, как использование компонента, но если это просто для логики инициализации, это может быть быстрый, приемлемый компромисс. Я, конечно, не учел бы вышеизложенное для какой-либо обработки после инициализации.

0 голосов
/ 06 марта 2012

Я не знаю, как часто вам нужно создавать эти таблицы, но как насчет размещения всех отдельных сценариев создания SQL в таблице с последовательной нумерацией / версией версии?Чем вы можете пройти через эту таблицу и выполнить один за другим.Вам нужно будет разделить ваши сценарии один раз, но после этого он станет более понятным.

...