2017-07-27 44 views
1

我正在使用postgres創建用戶表。我的表是以下...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') 
); 

是否有可能爲create_datetime只使用默認的列,即使插入腳本試圖插入時間戳到created_datetime?如果插入腳本嘗試在create_datetime列中插入時間戳,Postgres甚至會拋出一個錯誤。

回答

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,但如果您需要(或將來需要)處理多個時區,則可節省大量工作量。

+0

感謝timestamptz建議! –

2

Revoke表上的INSERT和UPDATE權限的所有角色:

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列上插入和更新。

+0

「_O另一方面,如果一個角色已被授予對某個表的特權,那麼從各個列中撤消相同的特權將不會產生任何效果._」From [there](https://www.postgresql.org/docs/電流/靜態/ SQL-revoke.html)。所以它應該是'撤銷插入,更新...;'然後'授予插入(...),更新(...)...;' – Abelisto

+0

@Abelisto好吧,我想我已經修復了它。 –

+0

除了不明確的錯誤消息(類似「關係用戶拒絕的權限」),它可能是最有效和最靈活的解決方案。 PS:爲什麼不只是'授予插入(user_id,user_name,密碼),更新(....'? – Abelisto

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