web-dev-qa-db-ja.com

キーワードのような名前のテーブルのPL / pgSQL regclass引用

既存のテーブル名にサフィックスを追加して、新しいテーブル名を作成したいと考えています。 Postgres 9.5 PL/pgSQL関数は、regclassタイプとして渡された既存のテーブル名を取得し、新しい名前を文字列として返します。私はformat()を使用して新しい名前を作成しており、通常は_%I_プレースホルダーを使用します。これは、渡されたテーブル名がどのPL/pgSQLキーワードとも一致しない限り機能します。この場合、どのタイプのコンポーネントを選択しても(_%I_または_%s_)、引用は正しくありません。

次の関数を考えます。

_CREATE OR REPLACE FUNCTION make_name(old_name regclass)
RETURNS text                                           
LANGUAGE plpgsql AS                                    
$$                                                     
BEGIN                                                  
    RETURN format('%I_new', old_name);                 
END;                                                   
$$;                                                    
_

さらに、treenodeoverlayの2つのテーブルがあると仮定します。両方に対してこの関数を呼び出すと、次の新しい名前になります。

_SELECT make_name('overlay'::regclass);
     make_name     
-------------------
 """overlay"""_new
(1 row)

SELECT make_name('treenode'::regclass);
  make_name   
--------------
 treenode_new
(1 row)
_

結局のところ、overlayはPL/pgSQL関数でもあり(formatと同じですが、treenodeはそうではありません)、引用動作を変更しているようです。 _%s_をformat()と共に使用すると、結果は_"overlay"_new_になります。 _||_演算子を使用しても同じことが起こります。入力パラメーターの型としてtextを使用すると、すべてが期待どおりに機能しますが、regclassを使用することをお勧めします。

キーワードに一致するregclassテーブル名(例:overlay)の場合と同様に、キーワードに一致するregclassテーブル名(例:treenode)を引用符なしでフォーマットする方法はありますか?

5
tomka

説明

誤解の複数の層を1つずつ確認していきます-シンプルで安全なソリューションに到達します。

overlayが引用符で囲まれている理由は、ではない関数名だからですが、 予約語 だからです。
また、 overlay()ではありません「PL/pgSQL関数」(またはPL/pgSQLキーワードではありません)、これはPostgresコアに組み込まれた標準SQL関数です(_LANGUAGE internal_、Cはカーテンの後ろにあります)。実際、PL/pgSQLはこれとは何の関係もありません。

単純な論理エラー。

format('%I_new', old_name)

これは、 '_ new'が追加される前に二重引用符で囲まれた非標準の識別子をエスケープし、textを入力タイプとして使用しても失敗します。全体を引用する前に '_ new'を追加する必要があります:

_format('%I', old_name || '_new')
_

しかし、それはregclassではまだ機能しません。読み続けます。

_%I_を使用してregclass変数に引用符を追加することは意味がありません。そのテキスト表現は、必要に応じてすでに自動的に引用されているためです。 (quote_ident()に似ていますが、NULLにすることはできません。入力がregclassであるため、最初はNULLにすることはできません。)これを適用しますtwiceregclass入力(常に文字列)には常に_%s_を使用します。

format()はこのためにやりすぎです。 quote_ident() を使用するだけです:

_quote_ident(old_name || '_new')
_

最も重要なこと、前述のように、text表現のregclass変数は自動的にエスケープされます。それはキャストに組み込まれています。生のテキストを取得するには、それを システムカタログ_pg_class_ から直接取得します。 regclass value は、このテーブルの内部ではoidにすぎません。

_SELECT relname FROM pg_class WHERE oid = $1;
_

解決

これはあなたが探していることを行います:

_CREATE OR REPLACE FUNCTION make_name(old_name regclass)
  RETURNS text AS
$func$
SELECT quote_ident(relname || '_new') FROM pg_class WHERE oid = $1;
$func$  LANGUAGE sql STABLE;
_

テスト

_CREATE TEMP TABLE "sUICiDAL' namE"();
CREATE TEMP TABLE overlay();

SELECT make_name('overlay') AS n1
     , make_name('"sUICiDAL'' namE"')  AS n2;

     n1      |          n2
-------------+----------------------
 overlay_new | "sUICiDAL' namE_new"
_

_overlay_new_は完全に正当な識別子であるため、引用されていないことに注意してください。

名前をエスケープしない(二重引用符なし)にしたい場合は、次のように使用します。

_SELECT relname || '_new' FROM pg_class WHERE oid = $1;
_
7

format関数への呼び出しを次のようにtranslateでラップすることが、私が考えた唯一の方法です。

RETURN translate(format('%I_new',old_name),'"','');

このポストは、フォーマットの出力を処理し、 "文字を取り除きます。

誰かがよりエレガントな解決策を持っているかどうかはわかりません。私はbtrimやrtrimなどを考えましたが、それぞれを呼び出す必要があります。

1
smbennett1974