postgresql PL/pgSQL函数中列引用“workspace_id”不明确

inb24sb2  于 2023-03-29  发布在  PostgreSQL
关注(0)|答案(2)|浏览(176)

在这段代码中,当我运行:

CREATE TABLE IF NOT EXISTS setup_workspace_result (
  path TEXT,
  workspace_id BIGINT
);

DROP FUNCTION IF EXISTS public.setup_workspace;

CREATE OR REPLACE FUNCTION setup_workspace(refresh_token TEXT)
RETURNS setup_workspace_result
AS $$
DECLARE
  user_email TEXT := ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'email');
  workspace_name TEXT := ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'hd');
  name TEXT :=  ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'name');
  is_admin BOOLEAN := ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'isAdmin');
  workspace_id BIGINT;
  user_id BIGINT;
  result setup_workspace_result;
BEGIN
  -- Check if user already exists
  SELECT users.id, users.workspace_id INTO user_id, workspace_id FROM users WHERE email = user_email;
  IF user_id IS NOT NULL THEN
    -- User already exists, return workspace_id
    UPDATE service_google 
    SET token = refresh_token
    WHERE service_google.workspace_id = workspace_id;

    result.workspace_id := workspace_id;
    result.path := 'login';
  ELSE
    -- Insert data into tables
    INSERT INTO users (email, name)
    VALUES (user_email, name) RETURNING id INTO user_id;

    INSERT INTO workspaces (domain_name, created_by)
    VALUES (workspace_name, user_id) RETURNING id INTO workspace_id;

    INSERT INTO super_admins (workspace_id, user_id)
    VALUES (workspace_id, user_id);

    result.workspace_id := workspace_id;
    result.path := 'signup';
  END IF;

  RETURN result;
END;
$$ LANGUAGE plpgsql;

出现以下错误:

{
  code: '42702',
  details: 'It could refer to either a PL/pgSQL variable or a table column.',
  hint: null,
  message: 'column reference "workspace_id" is ambiguous'
}

你知道为什么我会得到这个,我如何解决它?

hpxqektj

hpxqektj1#

您有一个名为workspace_id的列,并且您声明了一个同名的变量。这是命名冲突的秘诀。请不要这样做。
尽可能使用与所有相关列名不同的变量名。我喜欢在变量名前面加上下划线,例如:_workspace_id。这只是解决这个问题的许多方法之一。并且表限定所有可能冲突的列名。这绝对是个好主意。
相关:

  • 引用NEW的行的Postgres函数NULL值
  • 如何在PostgreSQL中返回函数内部的SELECT结果?

修复竞态

在修复了出现的错误之后,函数中还有更多不太明显的问题。
基本上,这是一个典型的 SELECTINSERT 的情况,当前的实现容易出现竞争条件。首先阅读:

  • 函数中的SELECT或INSERT是否容易出现争用情况?

然后考虑这个重写:

CREATE OR REPLACE FUNCTION setup_workspace(refresh_token text, OUT result setup_workspace_result)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _user_email       text;
   _workspace_name   text;
   _name             text;
-- _is_admin         boolean;  -- unused
   _workspace_id     bigint;
   _user_id          bigint;
BEGIN
   SELECT js ->> 'email', js ->> 'hd', js ->> 'name'  -- , js ->> 'isAdmin'
   INTO   _user_email, _workspace_name, _name  -- , _is_admin
   FROM  (SELECT current_setting('request.jwt.claims', true)::json) t(js);

   IF _user_email IS NULL THEN
      RAISE EXCEPTION 'Custom variable "request.jwt.claim" not set, or email missing!';  -- or similar
   END IF;

   LOOP
      -- Check if user already exists
      SELECT u.id    , u.workspace_id
      INTO   _user_id, _workspace_id
      FROM   users u
      WHERE  u.email = _user_email;

      IF FOUND THEN
         -- User already exists, return workspace_id
         UPDATE service_google  s
         SET    token = refresh_token
         WHERE  s.workspace_id = _workspace_id;

         result.workspace_id := workspace_id;
         result.path := 'login';

         EXIT;
      END IF;

      -- Insert data into tables
      INSERT INTO users (email, name)
      VALUES (_user_email, _name)
      ON     CONFLICT DO NOTHING   -- narrow down to specific unique violation?
      RETURNING id
      INTO _user_id;

      IF FOUND THEN
         -- Insert data into tables
         INSERT INTO users (email, name)
         VALUES (_user_email, _name)
         RETURNING id
         INTO _user_id;

         INSERT INTO workspaces (domain_name, created_by)
         VALUES (_workspace_name, _user_id)
         RETURNING id
         INTO   _workspace_id;

         INSERT INTO super_admins (workspace_id, user_id)
         VALUES (_workspace_id, _user_id);

         result.workspace_id := _workspace_id;
         result.path := 'signup';
         
         EXIT;
      END IF;
   END LOOP;
END
$func$;

还修复了命名冲突问题,并添加了一些其他改进。

xfb7svmp

xfb7svmp2#

很多数据库,包括postgres,在变量名和列名冲突时会感到困惑。为了避免这种情况,我在变量名前面加上下划线,这样它们就不会冲突,如下所示:

DECLARE
  _user_email TEXT := ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'email');
  _workspace_name TEXT := ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'hd');
  _name TEXT :=  ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'name');
  _is_admin BOOLEAN := ((current_setting('request.jwt.claims'::text, TRUE))::JSON ->> 'isAdmin');
  _workspace_id BIGINT;
  _user_id BIGINT;
  _result setup_workspace_result;

它还使代码更容易阅读,因为变量很突出。
您可以根据自己的喜好使用不同的样式,例如两个下划线__workspace_idv_(如v_workspace_id),以更好地适应您公司可能具有的命名标准。

相关问题