PostgreSQL『ERROR: cannot change name of input parameter in FUNCTION』の原因と対処

  • 作成日 2025.09.22
  • 更新日 2025.10.06
  • その他
PostgreSQL『ERROR: cannot change name of input parameter in FUNCTION』の原因と対処

CREATE OR REPLACE FUNCTIONで既存関数の「入力(IN)パラメータ名」を変更しようとすると、このエラーが出る。呼び出し側の名前付き引数(named notation)互換性を壊すのを防ぐため、PostgreSQLはIN引数名の置き換えを禁止している。最短復旧、恒久対策、互換維持のワークアラウンド、依存関係の洗い出しまでまとめた。

エラーの意味と代表メッセージ

ERROR:  cannot change name of input parameter in function

・既存の関数に対して、IN引数の「名前」だけを変えてCREATE OR REPLACEしたときに発生
・引数の「型や順序」は同一でも、IN引数の“名前”変更は不可

発生条件(まずここを疑う)

□ 既存: CREATE FUNCTION f(a int, b text) RETURNS int ...
   置換: CREATE OR REPLACE FUNCTION f(x int, y text) RETURNS int ...  -- ←NG

□ ALTER FUNCTIONで引数名を直接変えようとした  -- PostgreSQLは未対応

□ OUT/INOUTではなく、IN引数名を変えようとした

□ 呼び出し側で named notation(f(b => 'txt', a => 1))を使う可能性がある関数

なぜ禁止されているのか(背景)

・PostgreSQLは「名前付き引数呼び出し」をサポート
  例: SELECT f(b => 'text', a => 1);
・IN引数名を置き換えると、既存アプリが実行時に誤解釈/失敗する
・そのため、CREATE OR REPLACEでIN引数名だけ変えることを禁止

最短復旧:元のIN引数名をそのまま使う

-- 既存の引数名を戻して置き換える(本体の修正だけ行う)
CREATE OR REPLACE FUNCTION f(a int, b text)
RETURNS int
LANGUAGE plpgsql AS $$
BEGIN
  -- 本体を必要に応じて更新
  RETURN a + length(b);
END
$$;

・パラメータ名の変更を取りやめれば即復旧できる

ワークアラウンド1:関数内部で“別名”に割り当てる(PL/pgSQL)

-- 見通しの良い内部変数名にしたいだけなら、ALIASで内部別名を付ける
CREATE OR REPLACE FUNCTION f(a int, b text)
RETURNS int
LANGUAGE plpgsql AS $$
DECLARE
  user_id ALIAS FOR $1;   -- a の別名
  comment ALIAS FOR $2;   -- b の別名
BEGIN
  RETURN user_id + length(comment);
END
$$;

・外向きのIN引数名(a,b)は維持しつつ、内部では意味のある名前で扱える

ワークアラウンド2:新関数(v2)を作り、旧関数をラッパ化

-- 新API(望む引数名)
CREATE OR REPLACE FUNCTION f_v2(user_id int, comment text)
RETURNS int
LANGUAGE plpgsql AS $$
BEGIN
  RETURN user_id + length(comment);
END
$$;

-- 後方互換:旧APIは内部で新APIへ委譲
CREATE OR REPLACE FUNCTION f(a int, b text)
RETURNS int
LANGUAGE sql AS $$
  SELECT f_v2(a, b);
$$;

・既存の呼び出しを壊さずに“新しい名前のAPI”を併存できる
・十分な移行期間を設け、旧APIは段階的に廃止

恒久対策:本当にIN引数名を変える必要がある場合の手順

-- 1) 影響調査:呼び出し側で named notation を使っていないか確認
--   (DB内の依存はpg_depend、アプリコードはgrep/検索等)

-- 2) ドロップ&再作成(※すべての呼び出し箇所を書き換えた上で)
DROP FUNCTION IF EXISTS public.f(int, text);
CREATE FUNCTION public.f(user_id int, comment text)
RETURNS int
LANGUAGE plpgsql AS $$ ... $$;

・CREATE OR REPLACEでは変えられないため、DROP → CREATE が唯一の方法
・当然ながら、呼び出し側(特にnamed notation使用箇所)を全面更新する

依存関係の洗い出し(DB内)

-- 対象関数のOIDを確認
SELECT 'public.f(int, text)'::regprocedure AS proc, oid FROM pg_proc
WHERE oid = 'public.f(int, text)'::regprocedure;

-- その関数に依存しているDBオブジェクトを列挙
SELECT pg_describe_object(d.classid, d.objid, d.objsubid) AS dependent
FROM pg_depend d
WHERE d.refobjid = 'public.f(int, text)'::regprocedure
ORDER BY 1;

・ビュー/関数/トリガ/拡張など、DB内の依存を事前に把握できる

OUT/INOUTパラメータと戻り値に関する注意

・OUT/INOUTは“戻り値の列名”の意味も持つ
・名前変更は依存ビュー/SELECT * に影響し得るため慎重に
・型や戻り値の変更は CREATE OR REPLACE の範囲外(原則はDROP→CREATE)

デフォルト引数・順序の変更での落とし穴

-- デフォルト値の追加/変更はCREATE OR REPLACEで可能だが、
-- named/position両方式の互換性に配慮すること
CREATE OR REPLACE FUNCTION f(a int, b text DEFAULT '')
RETURNS int AS $$ ... $$ LANGUAGE plpgsql;

-- 順序変更は推奨しない(position呼び出しが破綻する)

最小再現→エラー→解消の通し例

-- 1) 準備:最初の定義
CREATE OR REPLACE FUNCTION f(a int, b text)
RETURNS int LANGUAGE plpgsql AS $$
BEGIN
  RETURN a + length(b);
END $$;

-- 2) NG:IN引数名を変えて置換(エラー)
CREATE OR REPLACE FUNCTION f(x int, y text)
RETURNS int LANGUAGE plpgsql AS $$
BEGIN
  RETURN x + length(y);
END $$;
-- ERROR: cannot change name of input parameter in function

-- 3) OK案A:引数名を戻し、内部で別名を付与
CREATE OR REPLACE FUNCTION f(a int, b text)
RETURNS int LANGUAGE plpgsql AS $$
DECLARE
  user_id ALIAS FOR $1;
  comment ALIAS FOR $2;
BEGIN
  RETURN user_id + length(comment);
END $$;

-- 4) OK案B:新APIを作って旧APIから委譲
CREATE OR REPLACE FUNCTION f_v2(user_id int, comment text)
RETURNS int LANGUAGE plpgsql AS $$
BEGIN
  RETURN user_id + length(comment);
END $$;

CREATE OR REPLACE FUNCTION f(a int, b text)
RETURNS int LANGUAGE sql AS $$
  SELECT f_v2(a, b);
$$;
[/code]

運用チェックリスト

□ named notation(argname => value)で呼ばれていないか全箇所を確認
□ DB内の依存(pg_depend)とアプリコードの両方を洗い出し
□ 互換維持が必要なら「内部ALIAS」または「v2 + ラッパ」方式を採用
□ どうしても改名するなら、DROP→CREATEと呼び出し側の一括修正を同一リリースで実施
□ OUT/INOUTや戻り値列名がビュー等に波及しないか点検
□ デフォルト引数や順序変更は慎重に(position/named両対応を考慮)

まとめ:安全な改名の原則

・CREATE OR REPLACEでIN引数名は変えられない → 仕様
・見通しだけ改善したいなら内部ALIASで十分
・API改名は「新関数 + 旧ラッパ」で段階移行
・本当に改名が必要なら DROP→CREATE と呼び出し側修正をセットで