Firebird Documentation Index → Firebird 2.5 リリースノート → 手続き型SQL(PSQL) → EXECUTE文の新たな拡張 |
われわれのリリースノートでは珍しく、この章は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>]
オプションの句の順序は固定されておらず、例えば、次のモデルに基づく文も有効となります:
[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とリモートレイヤーなしに確立された別の接続で実行されます。
AS USER <ユーザー名>句がない場合はCURRENT_USERがデフォルトとなります。
CURRENT_CONNECTIONとは異なる接続にサーバー認証が必要な場合、例えば、外部のデータソースでEXECUTE STATEMENTコマンドを実行するには、AS USERやPASSWORD句が必要になります。しかし、いくつかの条件のもとで、PASSWORDが省略できることがあります。その効果は以下の通りです:
Windowsでは、“信頼された認証”がアクティブとなっており、AS USERパラメータが欠けているか無効、またはCURRENT_USERと同じ場合、CURRENT_CONNECTION(つまり、外部データソースではなく)に“信頼された認証”が実行されます。
外部データソース・パラメータが与えられ、その<接続文字列>がCURRENT_CONNECTIONと同じデータベースを参照している場合は、有効なユーザーアカウントはCURRENT_USERのものとなります。
外部データソース・パラメータが与えられ、その<接続文字列>が、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による挙動は以下の通りです:
外部データソースでの任意のトランザクションを、CURRENT_TRANSACTIONと同じパラメータで開始します;あるいは、
CURRENT_TRANSACTION内の文を実行します;または、
CURRENT_CONNECTION内で開始される別のトランザクションを使用します。
WITH AUTONOMOUS TRANSACTION設定は、新しいトランザクションをCURRENT_TRANSACTIONと同じパラメータで開始します。文が例外を発生させずに実行されればトランザクションはコミットされ、エラーがあった場合はロールバックされます。
トラッカー・リファレンス CORE-1928
設計上、EXECUTE STATEMENTのもともとの実装では、実行可能なコードは実行されるストアドプロシージャまたはトリガのアクセス権限から分離され、CURRENT_USERが利用できる権限へと戻されます。この戦略は、任意の文を実行されてしまう脆弱性を低減できるため、概ね賢いものです。しかし、堅牢な環境やプライバシーが問題にならない状況では、制約と感じられることがあります。
オプションの句WITH CALLER PRIVILEGESの導入により、実行可能な文に、実行されるストアドプロシージャやトリガのアクセス権限を継承させられるようになりました。文は呼び出し先のストアドプロシージャやトリガに適用される任意の追加権限を使用して作成されます。その効果は、文がストアドプロシージャやトリガによって直接実行された場合と同様のものです。
WITH CALLER PRIVILEGESオプションはON EXTERNAL DATA SOURCEオプションとは互換性がありません。
トラッカー・リファレンス CORE-1853
EXECUTE STATEMENTに<接続文字列>引数を付けたON EXTERNAL DATA SOURCE句を含めることで、外部データベースに対するクエリがサポートされました。
<接続文字列>のフォーマットはごく普通のもので、API関数isc_attach_database()に渡されます。すなわち、
[<ホスト名><プロトコル区切り文字>]データベースのパス
外部のデータソースが別のサーバー上にある場合、AS USER <ユーザー名>句とPASSWORD <パスワード>句が必要となります。
外部のデータソースが別のサーバ上にある場合、WITH CALLER PRIVILEGES句は無視されます。
情報が足りないよ!ロールはどうなってるの?
外部接続での二相トランザクションはバージョン2.5では利用できません。
トラッカー・リファレンス 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
次の点に注意して下さい。<query_text>と<input_parameters>の両方を使用する場合、必ず<query_text>を括弧で囲みます。すなわち、
EXECUTE STATEMENT (:sql) (p1 := 'abc', p2 := :second_param) ...
例外の取り扱いは、ON EXTERNAL DATA SOURCE句が与えられているかどうかで異なります。
ON EXTERNAL DATA SOURCE句があると、Firebirdは未知のデータソースから与えられるエラーコードを解釈することができず、エラー情報そのものを解釈し、文字列にラップして自身のエラー・ラッパー(isc_eds_connectionまたはisc_eds_statement)へと渡します。
リモートエラーを解釈したテキストには、エラーコードと対応するメッセージの両方が含まれます。
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, <データソース名>
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
現状では、エラーコード生成元にWHEN文ではアクセスできません。この状況は将来改善されるかもしれません。
以下の例は、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
Firebird Documentation Index → Firebird 2.5 リリースノート → 手続き型SQL(PSQL) → EXECUTE文の新たな拡張 |