Firebird Documentation IndexFirebird 2.5 リリースノート手続き型SQL(PSQL) → EXECUTE文の新たな拡張
Firebird Home Firebird Home Prev: PSQLの変数にデータベースのカラムの型を借用Firebird Documentation IndexUp: 手続き型SQL(PSQL)Next: PSQLのその他の改善点

EXECUTE文の新たな拡張

コンテキスト・イシュー
PSQLからの外部クエリ
EXECUTE STATEMENTでの動的パラメータ
例外処理
EXECUTE STATEMENTの使用例

われわれのリリースノートでは珍しく、この章はPSQLのEXECUTE STATEMENT文の完全で新しく拡張された構文から始め、その後で、さまざまな新機能とその使い方の説明に移ります。

[FOR] EXECUTE STATEMENT <query_text> [(<input_parameters>)]
    [ON EXTERNAL [DATA SOURCE] <connection_string>]
    [WITH {AUTONOMOUS | COMMON} TRANSACTION]
    [AS USER <user_name>]
    [PASSWORD <password>]
    [ROLE <role_name>]
    [WITH CALLER PRIVILEGES]
    [INTO <variables>]
    

Note

オプションの句の順序は固定されておらず、例えば、次のモデルに基づく文も有効となります:

    [ON EXTERNAL [DATA SOURCE] <接続文字列>]
    [WITH {AUTONOMOUS | COMMON} TRANSACTION]
    [AS USER <ユーザー名>]
    [PASSWORD <パスワード>]
    [ROLE <ロール名>]
    [WITH CALLER PRIVILEGES]
      

句の重複はできません。

コンテキスト・イシュー

ON EXTERNAL DATA SOURCE句がない場合、EXECUTE STATEMENTは通常、CURRENT_CONNECTIONの文脈内で実行されます。AS USER句が省略されている場合、または、これがCURRENT_USERと同じ<ユーザー名>引数を持っている場合は、その通りです。

しかし、<ユーザー名>がCURRENT_USERと異なる場合、文は、同じエンジン・インスタンス内で、Y-Valveとリモートレイヤーなしに確立された別の接続で実行されます。

Note

AS USER <ユーザー名>句がない場合はCURRENT_USERがデフォルトとなります。

認証

CURRENT_CONNECTIONとは異なる接続にサーバー認証が必要な場合、例えば、外部のデータソースでEXECUTE STATEMENTコマンドを実行するには、AS USERやPASSWORD句が必要になります。しかし、いくつかの条件のもとで、PASSWORDが省略できることがあります。その効果は以下の通りです:

  1. Windowsでは、信頼された認証”がアクティブとなっており、AS USERパラメータが欠けているか無効、またはCURRENT_USERと同じ場合、CURRENT_CONNECTION(つまり、外部データソースではなく)に“信頼された認証”が実行されます。

  2. 外部データソース・パラメータが与えられ、その<接続文字列>がCURRENT_CONNECTIONと同じデータベースを参照している場合は、有効なユーザーアカウントはCURRENT_USERのものとなります。

  3. 外部データソース・パラメータが与えられ、その<接続文字列>が、CURRENT_CONNECTIONがアタッチしているものとは異なるデータベースを参照している場合、有効なユーザーアカウントは、Firebirdプロセスが現在稼働しているOSのアカウントとなります。

PASSWORD句が欠けている他の場合には、isc_dpb_user_nameだけはDPB(アタッチメントパラメータ)で与えられ、native認証が試行されます。

トランザクションの挙動

この新しい構文はトランザクションの挙動を設定するオプションの句を含んでいます:WITH AUTONOMOUS TRANSACTIONとWITH COMMON TRANSACTIONです。デフォルトはWITH COMMON TRANSACTIONであり、特に指定する必要はありません。トランザクションの有効期間はCURRENT_TRANSACTIONの有効期間によって決まり、CURRENT_TRANSACTIONに従ってコミットまたはロールバックされます。

WITH COMMON TRANSACTIONによる挙動は以下の通りです:

  1. 外部データソースでの任意のトランザクションを、CURRENT_TRANSACTIONと同じパラメータで開始します;あるいは、

  2. CURRENT_TRANSACTION内の文を実行します;または、

  3. CURRENT_CONNECTION内で開始される別のトランザクションを使用します。

WITH AUTONOMOUS TRANSACTION設定は、新しいトランザクションをCURRENT_TRANSACTIONと同じパラメータで開始します。文が例外を発生させずに実行されればトランザクションはコミットされ、エラーがあった場合はロールバックされます。

アクセス権限の継承

Vladyslav Khorsun

トラッカー・リファレンス CORE-1928

設計上、EXECUTE STATEMENTのもともとの実装では、実行可能なコードは実行されるストアドプロシージャまたはトリガのアクセス権限から分離され、CURRENT_USERが利用できる権限へと戻されます。この戦略は、任意の文を実行されてしまう脆弱性を低減できるため、概ね賢いものです。しかし、堅牢な環境やプライバシーが問題にならない状況では、制約と感じられることがあります。

オプションの句WITH CALLER PRIVILEGESの導入により、実行可能な文に、実行されるストアドプロシージャやトリガのアクセス権限を継承させられるようになりました。文は呼び出し先のストアドプロシージャやトリガに適用される任意の追加権限を使用して作成されます。その効果は、文がストアドプロシージャやトリガによって直接実行された場合と同様のものです。

Important

WITH CALLER PRIVILEGESオプションはON EXTERNAL DATA SOURCEオプションとは互換性がありません。

PSQLからの外部クエリ

Vladyslav Khorsun

トラッカー・リファレンス CORE-1853

EXECUTE STATEMENTに<接続文字列>引数を付けたON EXTERNAL DATA SOURCE句を含めることで、外部データベースに対するクエリがサポートされました。

<接続文字列>引数

<接続文字列>のフォーマットはごく普通のもので、API関数isc_attach_database()に渡されます。すなわち、

   [<ホスト名><プロトコル区切り文字>]データベースのパス
        

キャラクタ・セット

外部のデータソースへの接続には、CURRENT_CONNECTIONの文脈で使われているものと同じキャラクタ・セットが使われます。

アクセス権限

外部のデータソースが別のサーバー上にある場合、AS USER <ユーザー名>句とPASSWORD <パスワード>句が必要となります。

外部のデータソースが別のサーバ上にある場合、WITH CALLER PRIVILEGES句は無視されます。

情報が足りないよ!ロールはどうなってるの?

Note

外部接続での二相トランザクションはバージョン2.5では利用できません。

EXECUTE STATEMENTでの動的パラメータ

Vladyslav Khorsun

Alex Peshkov

トラッカー・リファレンス CORE-1221

新たな拡張により、パラメータ化されたDSQL文に似た方法で、動的な入力パラメータ(プレースホルダ)を使って文を作成することができるようになりました。クエリのテキストそのものをパラメータとして渡すことも可能です。

構文の表記規則

この仕組みには、実行時のパーシングを容易にし、また、DSQLパラメータを扱うポピュラーなクライアント・ラッパー層(Delphiなど)と互換性のあるスタイルでのパラメータ“命名”オプションを許可するいくつかの表記規則が採用されています。API自体が持つ表記規則もサポートされており、定義された順序で名前なしパラメータを渡すことができます。ただし、名前付き、名前なしパラメータを混在させることはできません。

新しい拘束演算子

この点について、動的パラメータ機能の実装の中で、同等性テストでのクラッシュを避けるために、実行時の値を名前付きパラメータに拘束する新しい代入演算子を導入する必要がありました。この新しい演算子はPascalの代入演算子を真似たもの“:=”です。

パラメータを定義する構文
    <input_parameters> ::=
      <named_parameter> | <input_parameters>, <named_parameter>

    <named_parameter> ::=
      <parameter name> := <expression>
          

名前付き入力パラメータの例

例えば、次のPSQL文は<query_text>と<input_parameters>(<named_parameter>)の両方を定義します:

EXECUTE BLOCK AS
  DECLARE S VARCHAR(255);
  DECLARE N INT = 100000;
  BEGIN
  /* 通常のPSQL文字列としての<query_text>の割り当て */
    S = 'INSERT INTO TTT VALUES (:a, :b, :a)';

    WHILE (N > 0) DO
    BEGIN
    /* 各ループの実行は文字列の値と入力パラメータに
       拘束される値の両方に適用されます */

      EXECUTE STATEMENT (:S) (a := CURRENT_TRANSACTION, b := CURRENT_CONNECTION)
      WITH COMMON TRANSACTION;
      N = N - 1;
    END
  END
          

名前なし入力パラメータの例

同様の文で、今度は名前なし入力パラメータを使用し、定数の引数を直接渡します。

EXECUTE BLOCK AS
  DECLARE S VARCHAR(255);
  DECLARE N INT = 100000;
  BEGIN
    S = 'INSERT INTO TTT VALUES (?, ?, ?)';

    WHILE (N > 0) DO
    BEGIN
      EXECUTE STATEMENT (:S) (CURRENT_TRANSACTION, CURRENT_CONNECTION, CURRENT_TRANSACTION);
      N = N - 1;
    END
  END
          

Note

次の点に注意して下さい。<query_text>と<input_parameters>の両方を使用する場合、必ず<query_text>を括弧で囲みます。すなわち、

    EXECUTE STATEMENT (:sql) (p1 := 'abc', p2 := :second_param) ...
            

例外処理

例外の取り扱いは、ON EXTERNAL DATA SOURCE句が与えられているかどうかで異なります。

ON EXTERNAL DATA SOURCE句がある場合

ON EXTERNAL DATA SOURCE句があると、Firebirdは未知のデータソースから与えられるエラーコードを解釈することができず、エラー情報そのものを解釈し、文字列にラップして自身のエラー・ラッパー(isc_eds_connectionまたはisc_eds_statement)へと渡します。

リモートエラーを解釈したテキストには、エラーコードと対応するメッセージの両方が含まれます。

  1. isc_eds_connectionエラーのフォーマット

    テンプレート文字列
      Execute statement error at @1 :\n@2Data source : @3
    ステータスベクター・タグ
      isc_eds_connection,
      isc_arg_string, <失敗したAPI関数名>,
      isc_arg_string, <外部エラーを解釈したテキスト>,
      isc_arg_string, <データソース名>
                
  2. isc_eds_statementエラーのフォーマット

    テンプレート文字列
      Execute statement error at @1 :\n@2Statement : @3\nData source : @4
    ステータスベクター・タグ
      isc_eds_statement,
      isc_arg_string, <失敗したAPI関数名>,
      isc_arg_string, <外部エラーを解釈したテキスト>,
      isc_arg_string, <クエリ>,
      isc_arg_string, <データソース名>
                

PSQLレベルでは、これらのエラーのシンボルは、それらを他のgdscodeと同様に扱うことで処理することができます。例えば、

  WHEN GDSCODE eds_statement
        

Note

現状では、エラーコード生成元にWHEN文ではアクセスできません。この状況は将来改善されるかもしれません。

ON EXTERNAL DATA SOURCE句がない場合

ON EXTERNAL DATA SOURCE句がない場合は、エラーの元のステータスベクターが呼び出し元であるPSQLコードにそのまま渡されます。

例えば、動的な文がisc_lock_conflictの例外を引き起こした場合、例外は呼び出し元に渡され、通常のハンドラで取り扱われます:

  WHEN GDSCODE lock_conflict
        

EXECUTE STATEMENTの使用例

以下の例は、EXECUTE STATEMENTの拡張をアプリケーションに適用する方法のサンプルを提供しています。

テスト接続とトランザクション

設定のバリエーションを比較できるように、テストを二つ挙げておきます:

テスト a):同じトランザクションでこの文を数回実行する─現在のデータベースへの新しい接続を三つ作成し、呼び出しがあるたびにそれを再利用する。トランザクションは再利用される。

EXECUTE BLOCK
  RETURNS (CONN INT, TRAN INT, DB VARCHAR(255))
AS
  DECLARE I INT = 0;
  DECLARE N INT = 3;
  DECLARE S VARCHAR(255);
BEGIN
  SELECT A.MON$ATTACHMENT_NAME FROM MON$ATTACHMENTS A
   WHERE A.MON$ATTACHMENT_ID = CURRENT_CONNECTION
    INTO :S;

  WHILE (i < N) DO
  BEGIN
    DB = TRIM(CASE i - 3 * (I / 3)
      WHEN 0 THEN '\\.\' WHEN 1 THEN 'localhost:' ELSE '' END) || :S;

    FOR EXECUTE STATEMENT
      'SELECT CURRENT_CONNECTION, CURRENT_TRANSACTION
      FROM RDB$DATABASE'
      ON EXTERNAL :DB
      AS USER CURRENT_USER PASSWORD 'masterkey' -- ただの例です
      WITH COMMON TRANSACTION
      INTO :CONN, :TRAN
    DO SUSPEND;

    i = i + 1;
  END
END
          

テスト b):同じトランザクションでこの文を数回実行する─呼び出しがあるたびに、現在のデータベースへの新しい接続を三つ作成する。

EXECUTE BLOCK
  RETURNS (CONN INT, TRAN INT, DB VARCHAR(255))
AS
  DECLARE I INT = 0;
  DECLARE N INT = 3;
  DECLARE S VARCHAR(255);
BEGIN
  SELECT A.MON$ATTACHMENT_NAME
    FROM MON$ATTACHMENTS A
  WHERE A.MON$ATTACHMENT_ID = CURRENT_CONNECTION
   INTO :S;

  WHILE (i < N) DO
  BEGIN
    DB = TRIM(CASE i - 3 * (I / 3)
      WHEN 0 THEN '\\.\'
      WHEN 1 THEN 'localhost:'
      ELSE '' END) || :S;

    FOR EXECUTE STATEMENT
    'SELECT CURRENT_CONNECTION, CURRENT_TRANSACTION FROM RDB$DATABASE'
      ON EXTERNAL :DB
      WITH AUTONOMOUS TRANSACTION -- 自律型トランザクションに注意
      INTO :CONN, :TRAN
    DO SUSPEND;

    i = i + 1;
  END
END
          

入力の評価のデモ

一度だけ評価される入力式のデモ:

EXECUTE BLOCK
  RETURNS (A INT, B INT, C INT)
AS
BEGIN
  EXECUTE STATEMENT (
    'SELECT CAST(:X AS INT),
            CAST(:X AS INT),
            CAST(:X AS INT)
       FROM RDB$DATABASE')
      (x := GEN_ID(G, 1))
    INTO :A, :B, :C;

  SUSPEND;
END
        

書き込み速度テスト

パラメータ化していない形式のEXECUTE STATEMENTと比較するため、われわれが以前に挙げていた入力パラメータの使用例を再掲します:

RECREATE TABLE TTT (
  TRAN INT,
  CONN INT,
  ID INT);

-- 直接の書き込み:

EXECUTE BLOCK AS
  DECLARE N INT = 100000;
BEGIN
  WHILE (N > 0) DO
  BEGIN
    INSERT INTO TTT VALUES (CURRENT_TRANSACTION, CURRENT_CONNECTION, CURRENT_TRANSACTION);
    N = N - 1;
  END
END

-- プリペアされた動的な文を介した書き込み
-- 名前付き入力パラメータを使用:

EXECUTE BLOCK AS
  DECLARE S VARCHAR(255);
  DECLARE N INT = 100000;
BEGIN
  S = 'INSERT INTO TTT VALUES (:a, :b, :a)';

  WHILE (N > 0) DO
  BEGIN
    EXECUTE STATEMENT (:S)
      (a := CURRENT_TRANSACTION, b := CURRENT_CONNECTION)
    WITH COMMON TRANSACTION;
    N = N - 1;
  END
END

-- プリペアされた動的な文を介した書き込み
-- 名前なし入力パラメータを使用:

EXECUTE BLOCK AS
DECLARE S VARCHAR(255);
DECLARE N INT = 100000;
BEGIN
  S = 'INSERT INTO TTT VALUES (?, ?, ?)';

  WHILE (N > 0) DO
  BEGIN
    EXECUTE STATEMENT (:S) (CURRENT_TRANSACTION, CURRENT_CONNECTION, CURRENT_TRANSACTION);
    N = N - 1;
  END
END
        
Prev: PSQLの変数にデータベースのカラムの型を借用Firebird Documentation IndexUp: 手続き型SQL(PSQL)Next: PSQLのその他の改善点
Firebird Documentation IndexFirebird 2.5 リリースノート手続き型SQL(PSQL) → EXECUTE文の新たな拡張