web-dev-qa-db-ja.com

テーブルスペースが存在しない場合に作成する方法

psqlでスクリプトを実行してPostgresで新しいスキーマを作成するとき、それをテーブルスペースに配置したいと思います。このテーブルスペースが存在しない場合は、最初に作成します。デフォルトのSQLにはこのオプションがないため、関数を作成しました。

_CREATE OR REPLACE FUNCTION make_tablespace(tablespace CHARACTER,
                                           directory CHARACTER,
                                           owner CHARACTER)
  RETURNS void
AS
$$
BEGIN
  IF tablespace = '' THEN
    RAISE EXCEPTION 'No tablespace.';
  END IF;
  PERFORM SPCNAME FROM PG_TABLESPACE WHERE SPCNAME=tablespace;
  IF NOT FOUND THEN
    IF directory = '' THEN
      RAISE EXCEPTION 'No directory.';
    END IF;
    IF owner = '' THEN
      RAISE EXCEPTION 'No owner.';
    END IF;
    EXECUTE 'CREATE TABLESPACE '||tablespace||' OWNER '||owner||' LOCATION '''||directory||''';';
    RAISE NOTICE 'Tablespace % created.', tablespace;
  ELSE
    RAISE NOTICE 'Tablespace % already exists.', tablespace;
  END IF;
END $$ LANGUAGE plpgsql;
_

残念ながら、それを実行すると(select make_tablespace('marco', '/opt/marco', 'marco');)、エラーが発生します。

エラー:CREATE TABLESPACEは関数またはマルチコマンド文字列から実行できません

私はインターネットを検索しましたが、dblinkパッケージを使用することで回避策(数年前)があるようです。これをインストールしたくない。今日は別の方法がありますか? SQLステートメントを文字列として返すことができますが、それを実行するにはどうすればよいですか?

1
Marco

ここでのポイントは、Postgresの関数は、真のストアドプロシージャとほとんど同じではありません。 (ストアドプロシージャとは異なり)Postgres関数は、外部トランザクションのコンテキストで実行されます。したがって、VACUUM、_CREATE DATABASE_、...、_CREATE TABLESPACE_など、トランザクションブロックで実行できないコマンドは実行できません。マニュアルはそれについて明確です:

_CREATE TABLESPACE_は、トランザクションブロック内では実行できません。

これらのコマンドは、単一のSQLコマンドとして実行する必要があります。現在、Postgres(バージョン9.6まで)にも自律トランザクションがありません。これは回避策となる可能性があります。

したがって、コマンドを関数またはトランザクションに含める唯一の回避策は、自律型トランザクションを dblink で偽造することです、あなたがすでに見つけたように。

コードには他にもいくつかの小さな問題があります。私は提案します:

_CREATE OR REPLACE FUNCTION make_tablespace(tablespace text,
                                           directory text,
                                           owner text)
  RETURNS void AS
$func$
BEGIN
   IF tablespace <> '' THEN  -- catches '' *and* NULL
      -- do nothing
   ELSE
      RAISE EXCEPTION 'No tablespace.';
   END IF;

   IF EXISTS (SELECT 1 FROM pg_tablespace WHERE spcname = tablespace) THEN
      RAISE NOTICE 'Tablespace % already exists.', tablespace;
      EXIT;
   END IF;

   IF directory <> '' THEN
   ELSE
      RAISE EXCEPTION 'No directory.';
   END IF;

   IF owner <> '' THEN
   ELSE
      RAISE EXCEPTION 'No owner.';
   END IF;

   PERFORM dblink_connect('myserver');  -- name of foreign server
   PERFORM dblink_exec(format('CREATE TABLESPACE %I OWNER %I LOCATION %L', tablespace, owner, directory));
   RAISE NOTICE 'Tablespace % created.', tablespace;
   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;
_

主なポイント:

コード例と詳細な説明を含む関連する回答:

2