Postgres:即使尝试插入,也始终使用默认列值

时间:2017-07-27 17:41:27

标签: sql postgresql

我正在使用postgres创建一个用户表。我的表格如下......

create table if not exists "user" (
    user_id bigserial primary key,
    username varchar(20) not null,
    "password" varchar(100) not null,
    created_datetime timestamp default (now() at time zone 'utc')
);

即使插入脚本尝试将时间戳插入created_datetime,create_datetime是否也可以仅使用默认列?如果插入脚本试图将时间戳插入create_datetime列,postgres甚至会抛出错误。

3 个答案:

答案 0 :(得分:2)

创建一个触发器,填充该列,忽略INSERT中提供的任何值:

create or replace function t_user_created_datetime() returns trigger as $$
begin
  new.created_datetime := now() at time zone 'utc';
  return new;
end;
$$ language plpgsql;

create trigger t_user_created_datetime
  before insert on "user"
  for each row execute procedure t_user_created_datetime();

检查:

test=# insert into "user"(user_id, username, "password", created_datetime) values(1, 'test', 'test', '1900-01-01'::timestamp);
INSERT 0 1
test=# select * from "user";
 user_id | username | password |      created_datetime
---------+----------+----------+----------------------------
       1 | test     | test     | 2017-07-27 18:21:24.501701
(1 row)

使用此类触发器,您可以从表格的定义中移除default (now() at time zone 'utc'),因为它变得无用。

如果您希望在INSERT中明确设置列的值时看到错误,请将触发器功能更改为以下内容:

create or replace function t_user_created_datetime() returns trigger as $$
begin
  if new.created_datetime is not null then
    raise exception 'It is forbidden to explicitly set "created_datetime" during INSERT to "user" table.';
  end if;
  new.created_datetime := now() at time zone 'utc';
  return new;
end;
$$ language plpgsql;

在这种情况下,created_datetime 列不得 default  因为否则你总会看到这个错误。

P.S。我强烈建议考虑使用timestamptz - 它也是8个字节,如timestamp,但如果您需要(或将来需要)处理多个时区,则可以省去很多工作。< / p>

答案 1 :(得分:2)

Revoke来自所有角色的表的插入和更新权限:

revoke insert, update
on table "user"
from public, application_role
cascade

然后grant仅对其他列的应用程序角色执行插入和更新权限:

grant 
    insert (user_id, username, "password"),
    update (username, "password")
on table "user"
to application_role

只有表所有者才能在created_datetime列上插入和更新。

答案 2 :(得分:1)

完成答案集。使用rules

首先,我们将创建服务函数,以便能够从SQL语句中引发异常:

create or replace function public.fn_raise(msg text, cond boolean = true)
  returns bool
  immutable
  language plpgsql
as $$ 
begin
  if cond then
    raise exception '%', msg;
  end if;
  return false;
end $$;

接下来让我们创建测试表:

create table t(i int, d timestamptz not null default current_timestamp);

最后,规则:

create or replace rule rul_t_insert as on insert to t
where new.d <> current_timestamp
do also
  select fn_raise(format('Can not insert %s into table t', new.d), new.d <> current_timestamp);

让我们测试一下:

postgres=# insert into t(i) values(1) returning *;
┌───┬───────────────────────────────┐
│ i │               d               │
╞═══╪═══════════════════════════════╡
│ 1 │ 2017-07-28 12:31:37.255392+03 │
└───┴───────────────────────────────┘

postgres=# insert into t(i,d) values(1,null) returning *;
ERROR:  null value in column "d" violates not-null constraint
DETAIL:  Failing row contains (1, null).

postgres=# insert into t(i,d) values(2,'2000-10-10') returning *;
ERROR:  Can not insert 2000-10-10 00:00:00+03 into table t

我提到的问题只是insert,但如果您还想阻止此字段的更新,则可以创建另一个规则:

create or replace rule rul_t_update as on update to t
where new.d <> old.d
do also
  select fn_raise(format('Can not change t.d to %s', new.d), new.d <> old.d);

测试:

postgres=# update t set i = 3 where i = 1 returning *;
┌───┬───────────────────────────────┐
│ i │               d               │
╞═══╪═══════════════════════════════╡
│ 3 │ 2017-07-28 12:31:37.255392+03 │
└───┴───────────────────────────────┘

postgres=# update t set i = 4, d = current_timestamp where i = 3 returning *;
ERROR:  Can not change t.d to 2017-07-28 12:39:18.963852+03