db timestamp guards (#70)

This commit is contained in:
Jim authored and GitHub committed 2020-05-27 17:35:04 -04:00
1 parent 6dd083fa42
commit aa64579a8c
11 files changed
+820 -150

No files matched your search

+1
View File
@@ -14,6 +14,7 @@ require (
github.com/go-openapi/validate v0.19.8 // indirect
github.com/go-swagger/go-swagger v0.23.0
github.com/golang-migrate/migrate/v4 v4.11.0
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
github.com/golang/protobuf v1.4.2
github.com/grpc-ecosystem/grpc-gateway v1.14.5
github.com/hashicorp/errwrap v1.0.0
+200
View File
@@ -2,8 +2,11 @@ package db
import (
"testing"
"time"
"github.com/golang-sql/civil"
_ "github.com/jackc/pgx/v4"
"github.com/stretchr/testify/assert"
)
func TestDomain_PublicId(t *testing.T) {
@@ -110,3 +113,200 @@ returning id;
t.Errorf("want error, got no error")
}
}
func TestDomain_immutable_create_time_func(t *testing.T) {
assert := assert.New(t)
const (
createTable = `
create table if not exists test_immutable_create_time (
id bigint generated always as identity primary key,
name text,
create_time wt_timestamp
);
`
addTriggers = `
create trigger
test_update_immutable_create_time
before
update on test_immutable_create_time
for each row execute procedure immutable_create_time_func();
`
query = `
select create_time from test_immutable_create_time where id = $1
`
countQuery = `
select count(*) from test_immutable_create_time
`
insert = `
insert into test_immutable_create_time (name)
values ('alice')
returning id;
`
// no where clause intentionally, since it's not needed for the test
update = `
update test_immutable_create_time
set name = 'bob';
`
// no where clause intentionally, since it's not needed for the test
bad_update = `
update test_immutable_create_time
set create_time = now();
`
// no where clause intentionally, since it's not needed for the test
bad_update_null = `
update test_immutable_create_time
set create_time = null;
`
)
cleanup, conn, _ := TestSetup(t, "postgres")
defer func() {
if err := conn.Close(); err != nil {
t.Error(err)
}
if err := cleanup(); err != nil {
t.Error(err)
}
}()
db := conn.DB()
_, err := db.Exec(createTable)
assert.NoError(err)
_, err = db.Exec(addTriggers)
assert.NoError(err)
var id int
err = db.QueryRow(insert).Scan(&id)
assert.NoError(err)
var origTime time.Time
err = db.QueryRow(query, id).Scan(&origTime)
assert.NoError(err)
var count int
err = db.QueryRow(countQuery).Scan(&count)
assert.NoError(err)
assert.Equal(1, count)
_, err = db.Exec(update)
assert.NoError(err)
_, err = db.Exec(bad_update)
assert.NoError(err)
var updateTime time.Time
err = db.QueryRow(query, id).Scan(&updateTime)
assert.NoError(err)
assert.Equal(origTime, updateTime)
_, err = db.Exec(bad_update_null)
assert.NoError(err)
err = db.QueryRow(query, id).Scan(&updateTime)
assert.NoError(err)
assert.Equal(origTime, updateTime)
}
func TestDomain_update_time_column(t *testing.T) {
assert := assert.New(t)
const (
createTable = `
create table if not exists test_update_time_column (
id bigint generated always as identity primary key,
name text,
update_time wt_timestamp
);
`
addTriggers = `
create trigger
test_update_time_column
before
update on test_update_time_column
for each row execute procedure update_time_column();
`
countQuery = `
select count(*) from test_update_time_column
`
query = `
select update_time from test_update_time_column where id = $1
`
insert = `
insert into test_update_time_column (name)
values ('bob')
returning id;
`
// no where clause intentionally, since it's not needed for the test
update = `
update test_update_time_column
set name = 'alice';
`
// no where clause intentionally, since it's not needed for the test
bad_update = `
update test_update_time_column
set update_time = now() - interval '3 days';
`
// no where clause intentionally, since it's not needed for the test
bad_update_null = `
update test_update_time_column
set update_time = null;
`
)
cleanup, conn, _ := TestSetup(t, "postgres")
defer func() {
if err := conn.Close(); err != nil {
t.Error(err)
}
if err := cleanup(); err != nil {
t.Error(err)
}
}()
db := conn.DB()
_, err := db.Exec(createTable)
assert.NoError(err)
_, err = db.Exec(addTriggers)
assert.NoError(err)
var id int
err = db.QueryRow(insert).Scan(&id)
assert.NoError(err)
var origTime time.Time
err = db.QueryRow(query, id).Scan(&origTime)
assert.NoError(err)
orig := civil.DateTimeOf(origTime)
var count int
err = db.QueryRow(countQuery).Scan(&count)
assert.NoError(err)
assert.Equal(1, count)
_, err = db.Exec(update)
assert.NoError(err)
_, err = db.Exec(bad_update)
assert.NoError(err)
var updateTime time.Time
err = db.QueryRow(query, id).Scan(&updateTime)
assert.NoError(err)
updated := civil.DateTimeOf(updateTime)
assert.True(updated.After(orig))
_, err = db.Exec(bad_update_null)
assert.NoError(err)
var nextUpdateTime time.Time
err = db.QueryRow(query, id).Scan(&nextUpdateTime)
assert.NoError(err)
nextUpdated := civil.DateTimeOf(nextUpdateTime)
assert.True(nextUpdated.After(updated))
}
+312 -75
View File
@@ -12,7 +12,8 @@ begin;
drop domain wt_timestamp;
drop domain wt_public_id;
drop function update_time_column() cascade;
drop function immutable_create_time_func() cascade;
commit;
`),
@@ -35,8 +36,87 @@ create domain wt_timestamp as
comment on domain wt_timestamp is
'Standard timestamp for all create_time and update_time columns';
create or replace function
update_time_column()
returns trigger
as $$
begin
if row(new.*) is distinct from row(old.*) then
new.update_time = now();
return new;
else
return old;
end if;
end;
$$ language plpgsql;
comment on function
update_time_column()
is
'function used in before update triggers to properly set update_time columns';
create or replace function
immutable_create_time_func()
returns trigger
as $$
begin
if new.create_time is distinct from old.create_time then
raise warning 'create_time cannot be set to %', new.create_time;
new.create_time = old.create_time;
end if;
return new;
end;
$$ language plpgsql;
comment on function
immutable_create_time_func()
is
'function used in before update triggers to make create_time column immutable';
create or replace function
default_create_time()
returns trigger
as $$
begin
if new.create_time is distinct from now() then
raise warning 'create_time cannot be set to %', new.create_time;
new.create_time = now();
end if;
return new;
end;
$$ language plpgsql;
comment on function
immutable_create_time_func()
is
'function used in before update triggers to make create_time column immutable';
commit;
`),
},
"migrations/02_oplog.down.sql": {
name: "02_oplog.down.sql",
bytes: []byte(`
begin;
drop table if exists oplog_entry cascade;
drop trigger if exists update_oplog_entry_update_time on oplog_entry;
drop trigger if exists update_oplog_entry_create_time on oplog_entry;
drop table if exists oplog_ticket cascade;
drop trigger if exists update_oplog_ticket_update_time on oplog_ticket;
drop trigger if exists update_oplog_ticket_create_time on oplog_ticket;
drop table if exists oplog_metadata cascade;
drop trigger if exists update_oplog_metadata_update_time on oplog_metadata;
drop trigger if exists update_oplog_metadata_create_time on oplog_metadata;
commit;
`),
},
"migrations/02_oplog.up.sql": {
@@ -44,31 +124,91 @@ commit;
bytes: []byte(`
begin;
CREATE TABLE if not exists oplog_entry (
create table if not exists oplog_entry (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
version text NOT NULL,
aggregate_name text NOT NULL,
"data" bytea NOT NULL
version text not null,
aggregate_name text not null,
"data" bytea not null
);
CREATE TABLE if not exists oplog_ticket (
create trigger
update_time_column
before
update on oplog_entry
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on oplog_entry
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on oplog_entry
for each row execute procedure default_create_time();
create table if not exists oplog_ticket (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
"name" text NOT NULL UNIQUE,
"version" bigint NOT NULL
"name" text not null unique,
"version" bigint not null
);
CREATE TABLE if not exists oplog_metadata (
create trigger
update_time_column
before
update on oplog_ticket
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on oplog_ticket
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on oplog_ticket
for each row execute procedure default_create_time();
create table if not exists oplog_metadata (
id bigint generated always as identity primary key,
create_time wt_timestamp,
entry_id bigint NOT NULL REFERENCES oplog_entry(id) ON DELETE CASCADE ON UPDATE CASCADE,
"key" text NOT NULL,
value text NULL
update_time wt_timestamp,
entry_id bigint not null references oplog_entry(id) on delete cascade on update cascade,
"key" text not null,
value text null
);
create trigger
update_time_column
before
update on oplog_metadata
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on oplog_metadata
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on oplog_metadata
for each row execute procedure default_create_time();
create index if not exists idx_oplog_metatadata_key on oplog_metadata(key);
create index if not exists idx_oplog_metatadata_value on oplog_metadata(value);
INSERT INTO oplog_ticket (name, version)
insert into oplog_ticket (name, version)
values
('default', 1),
('iam_scope', 1),
@@ -86,6 +226,7 @@ values
commit;
`),
},
"migrations/03_db.down.sql": {
@@ -97,6 +238,15 @@ drop table if exists db_test_user;
drop table if exists db_test_car;
drop table if exists db_test_rental;
drop trigger if exists update_db_test_user_update_time on db_test_user;
drop trigger if exists update_db_test_user_create_time on db_test_user;
drop trigger if exists update_db_test_car_update_time on db_test_car;
drop trigger if exists update_db_test_car_create_time on db_test_car;
drop trigger if exists update_db_test_rental_update_time on db_test_rental;
drop trigger if exists update_db_test_rental_create_time on db_test_rental;
commit;
`),
@@ -108,35 +258,92 @@ begin;
-- create test tables used in the unit tests for the internal/db package
-- these tables (db_test_user, db_test_car, db_test_rental) are not part
-- of the Watchtower domain model... they are simply used for testing the internal/db package
CREATE TABLE if not exists db_test_user (
-- of the watchtower domain model... they are simply used for testing the internal/db package
create table if not exists db_test_user (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
public_id text not null unique,
name text unique,
phone_number text,
email text
);
CREATE TABLE if not exists db_test_car (
create trigger
update_time_column
before
update on db_test_user
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on db_test_user
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on db_test_user
for each row execute procedure default_create_time();
create table if not exists db_test_car (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
public_id text not null unique,
name text unique,
model text,
mpg smallint
);
CREATE TABLE if not exists db_test_rental (
create trigger
update_time_column
before
update on db_test_car
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on db_test_car
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on db_test_car
for each row execute procedure default_create_time();
create table if not exists db_test_rental (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
user_id bigint not null REFERENCES db_test_user(id),
car_id bigint not null REFERENCES db_test_car(id)
public_id text not null unique,
name text unique,
user_id bigint not null references db_test_user(id),
car_id bigint not null references db_test_car(id)
);
create trigger
update_time_column
before
update on db_test_rental
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on db_test_rental
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on db_test_rental
for each row execute procedure default_create_time();
commit;
`),
@@ -150,98 +357,128 @@ drop table if exists iam_scope CASCADE;
drop trigger if exists iam_scope_insert;
drop function if exists iam_sub_scopes_func;
drop trigger if exists update_iam_scope_update_time on iam_scope;
drop trigger if exists update_iam_scope_create_time on iam_scope;
COMMIT;
`),
},
"migrations/04_iam.up.sql": {
name: "04_iam.up.sql",
bytes: []byte(`
BEGIN;
begin;
CREATE TABLE iam_scope_type_enm (
string text NOT NULL primary key CHECK(string IN ('unknown', 'organization', 'project'))
create table iam_scope_type_enm (
string text not null primary key check(string in ('unknown', 'organization', 'project'))
);
INSERT INTO iam_scope_type_enm (string)
insert into iam_scope_type_enm (string)
values
('unknown'),
('organization'),
('project');
CREATE TABLE iam_scope (
create table iam_scope (
public_id wt_public_id primary key,
create_time wt_timestamp,
update_time wt_timestamp,
name text,
type text NOT NULL REFERENCES iam_scope_type_enm(string) CHECK(
type text not null references iam_scope_type_enm(string) check(
(
type = 'organization'
and parent_id = NULL
and parent_id = null
)
or (
type = 'project'
and parent_id IS NOT NULL
and parent_id is not null
)
),
description text,
parent_id text REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE
parent_id text references iam_scope(public_id) on delete cascade on update cascade
);
create table iam_scope_organization (
scope_id wt_public_id NOT NULL UNIQUE REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
name text UNIQUE,
scope_id wt_public_id not null unique references iam_scope(public_id) on delete cascade on update cascade,
name text unique,
primary key(scope_id)
);
create table iam_scope_project (
scope_id wt_public_id NOT NULL REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
parent_id wt_public_id NOT NULL REFERENCES iam_scope_organization(scope_id) ON DELETE CASCADE ON UPDATE CASCADE,
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
parent_id wt_public_id not null references iam_scope_organization(scope_id) on delete cascade on update cascade,
name text,
unique(parent_id, name),
primary key(scope_id, parent_id)
);
CREATE
OR REPLACE FUNCTION iam_sub_scopes_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type = 'organization' THEN
insert into iam_scope_organization (scope_id, name)
values
(new.public_id, new.name);
return NEW;
END IF;
IF new.type = 'project' THEN
insert into iam_scope_project (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return NEW;
END IF;
RAISE EXCEPTION 'unknown scope type';
END;
$$;
create or replace function
iam_sub_scopes_func()
returns trigger
as $$
declare parent_type int;
begin
if new.type = 'organization' then
insert into iam_scope_organization (scope_id, name)
values
(new.public_id, new.name);
return new;
end if;
if new.type = 'project' then
insert into iam_scope_project (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return new;
end if;
raise exception 'unknown scope type';
end;
$$ language plpgsql;
CREATE TRIGGER iam_scope_insert
AFTER
insert ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_sub_scopes_func();
create trigger
iam_scope_insert
after
insert on iam_scope
for each row execute procedure iam_sub_scopes_func();
CREATE
OR REPLACE FUNCTION iam_immutable_scope_type_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type != old.type THEN
RAISE EXCEPTION 'scope type cannot be updated';
END IF;
return NEW;
END;
$$;
create or replace function
iam_immutable_scope_type_func()
returns trigger
as $$
declare parent_type int;
begin
if new.type != old.type then
raise exception 'scope type cannot be updated';
end if;
return new;
end;
$$ language plpgsql;
CREATE TRIGGER iam_scope_update
BEFORE
update ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_immutable_scope_type_func();
create trigger
iam_scope_update
before
update on iam_scope
for each row execute procedure iam_immutable_scope_type_func();
COMMIT;
create trigger
update_time_column
before update on iam_scope
for each row execute procedure update_time_column();
create trigger
immutable_create_time
before
update on iam_scope
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on iam_scope
for each row execute procedure default_create_time();
commit;
`),
},
@@ -2,5 +2,6 @@ begin;
drop domain wt_timestamp;
drop domain wt_public_id;
drop function update_time_column() cascade;
drop function immutable_create_time_func() cascade;
commit;
@@ -13,4 +13,60 @@ create domain wt_timestamp as
comment on domain wt_timestamp is
'Standard timestamp for all create_time and update_time columns';
create or replace function
update_time_column()
returns trigger
as $$
begin
if row(new.*) is distinct from row(old.*) then
new.update_time = now();
return new;
else
return old;
end if;
end;
$$ language plpgsql;
comment on function
update_time_column()
is
'function used in before update triggers to properly set update_time columns';
create or replace function
immutable_create_time_func()
returns trigger
as $$
begin
if new.create_time is distinct from old.create_time then
raise warning 'create_time cannot be set to %', new.create_time;
new.create_time = old.create_time;
end if;
return new;
end;
$$ language plpgsql;
comment on function
immutable_create_time_func()
is
'function used in before update triggers to make create_time column immutable';
create or replace function
default_create_time()
returns trigger
as $$
begin
if new.create_time is distinct from now() then
raise warning 'create_time cannot be set to %', new.create_time;
new.create_time = now();
end if;
return new;
end;
$$ language plpgsql;
comment on function
immutable_create_time_func()
is
'function used in before update triggers to make create_time column immutable';
commit;
@@ -0,0 +1,18 @@
begin;
drop table if exists oplog_entry cascade;
drop trigger if exists update_oplog_entry_update_time on oplog_entry;
drop trigger if exists update_oplog_entry_create_time on oplog_entry;
drop table if exists oplog_ticket cascade;
drop trigger if exists update_oplog_ticket_update_time on oplog_ticket;
drop trigger if exists update_oplog_ticket_create_time on oplog_ticket;
drop table if exists oplog_metadata cascade;
drop trigger if exists update_oplog_metadata_update_time on oplog_metadata;
drop trigger if exists update_oplog_metadata_create_time on oplog_metadata;
commit;
+73 -12
View File
@@ -1,30 +1,90 @@
begin;
CREATE TABLE if not exists oplog_entry (
create table if not exists oplog_entry (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
version text NOT NULL,
aggregate_name text NOT NULL,
"data" bytea NOT NULL
version text not null,
aggregate_name text not null,
"data" bytea not null
);
CREATE TABLE if not exists oplog_ticket (
create trigger
update_time_column
before
update on oplog_entry
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on oplog_entry
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on oplog_entry
for each row execute procedure default_create_time();
create table if not exists oplog_ticket (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
"name" text NOT NULL UNIQUE,
"version" bigint NOT NULL
"name" text not null unique,
"version" bigint not null
);
CREATE TABLE if not exists oplog_metadata (
create trigger
update_time_column
before
update on oplog_ticket
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on oplog_ticket
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on oplog_ticket
for each row execute procedure default_create_time();
create table if not exists oplog_metadata (
id bigint generated always as identity primary key,
create_time wt_timestamp,
entry_id bigint NOT NULL REFERENCES oplog_entry(id) ON DELETE CASCADE ON UPDATE CASCADE,
"key" text NOT NULL,
value text NULL
update_time wt_timestamp,
entry_id bigint not null references oplog_entry(id) on delete cascade on update cascade,
"key" text not null,
value text null
);
create trigger
update_time_column
before
update on oplog_metadata
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on oplog_metadata
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on oplog_metadata
for each row execute procedure default_create_time();
create index if not exists idx_oplog_metatadata_key on oplog_metadata(key);
create index if not exists idx_oplog_metatadata_value on oplog_metadata(value);
INSERT INTO oplog_ticket (name, version)
insert into oplog_ticket (name, version)
values
('default', 1),
('iam_scope', 1),
@@ -41,3 +101,4 @@ values
('db_test_rental', 1);
commit;
@@ -4,4 +4,13 @@ drop table if exists db_test_user;
drop table if exists db_test_car;
drop table if exists db_test_rental;
drop trigger if exists update_db_test_user_update_time on db_test_user;
drop trigger if exists update_db_test_user_create_time on db_test_user;
drop trigger if exists update_db_test_car_update_time on db_test_car;
drop trigger if exists update_db_test_car_create_time on db_test_car;
drop trigger if exists update_db_test_rental_update_time on db_test_rental;
drop trigger if exists update_db_test_rental_create_time on db_test_rental;
commit;
+69 -12
View File
@@ -2,33 +2,90 @@ begin;
-- create test tables used in the unit tests for the internal/db package
-- these tables (db_test_user, db_test_car, db_test_rental) are not part
-- of the Watchtower domain model... they are simply used for testing the internal/db package
CREATE TABLE if not exists db_test_user (
-- of the watchtower domain model... they are simply used for testing the internal/db package
create table if not exists db_test_user (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
public_id text not null unique,
name text unique,
phone_number text,
email text
);
CREATE TABLE if not exists db_test_car (
create trigger
update_time_column
before
update on db_test_user
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on db_test_user
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on db_test_user
for each row execute procedure default_create_time();
create table if not exists db_test_car (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
public_id text not null unique,
name text unique,
model text,
mpg smallint
);
CREATE TABLE if not exists db_test_rental (
create trigger
update_time_column
before
update on db_test_car
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on db_test_car
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on db_test_car
for each row execute procedure default_create_time();
create table if not exists db_test_rental (
id bigint generated always as identity primary key,
create_time wt_timestamp,
update_time wt_timestamp,
public_id text NOT NULL UNIQUE,
name text UNIQUE,
user_id bigint not null REFERENCES db_test_user(id),
car_id bigint not null REFERENCES db_test_car(id)
public_id text not null unique,
name text unique,
user_id bigint not null references db_test_user(id),
car_id bigint not null references db_test_car(id)
);
create trigger
update_time_column
before
update on db_test_rental
for each row execute procedure update_time_column();
create trigger
create_time_column
before
update on db_test_rental
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on db_test_rental
for each row execute procedure default_create_time();
commit;
@@ -4,4 +4,7 @@ drop table if exists iam_scope CASCADE;
drop trigger if exists iam_scope_insert;
drop function if exists iam_sub_scopes_func;
drop trigger if exists update_iam_scope_update_time on iam_scope;
drop trigger if exists update_iam_scope_create_time on iam_scope;
COMMIT;
+77 -50
View File
@@ -1,86 +1,113 @@
BEGIN;
begin;
CREATE TABLE iam_scope_type_enm (
string text NOT NULL primary key CHECK(string IN ('unknown', 'organization', 'project'))
create table iam_scope_type_enm (
string text not null primary key check(string in ('unknown', 'organization', 'project'))
);
INSERT INTO iam_scope_type_enm (string)
insert into iam_scope_type_enm (string)
values
('unknown'),
('organization'),
('project');
CREATE TABLE iam_scope (
create table iam_scope (
public_id wt_public_id primary key,
create_time wt_timestamp,
update_time wt_timestamp,
name text,
type text NOT NULL REFERENCES iam_scope_type_enm(string) CHECK(
type text not null references iam_scope_type_enm(string) check(
(
type = 'organization'
and parent_id = NULL
and parent_id = null
)
or (
type = 'project'
and parent_id IS NOT NULL
and parent_id is not null
)
),
description text,
parent_id text REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE
parent_id text references iam_scope(public_id) on delete cascade on update cascade
);
create table iam_scope_organization (
scope_id wt_public_id NOT NULL UNIQUE REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
name text UNIQUE,
scope_id wt_public_id not null unique references iam_scope(public_id) on delete cascade on update cascade,
name text unique,
primary key(scope_id)
);
create table iam_scope_project (
scope_id wt_public_id NOT NULL REFERENCES iam_scope(public_id) ON DELETE CASCADE ON UPDATE CASCADE,
parent_id wt_public_id NOT NULL REFERENCES iam_scope_organization(scope_id) ON DELETE CASCADE ON UPDATE CASCADE,
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
parent_id wt_public_id not null references iam_scope_organization(scope_id) on delete cascade on update cascade,
name text,
unique(parent_id, name),
primary key(scope_id, parent_id)
);
CREATE
OR REPLACE FUNCTION iam_sub_scopes_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type = 'organization' THEN
insert into iam_scope_organization (scope_id, name)
values
(new.public_id, new.name);
return NEW;
END IF;
IF new.type = 'project' THEN
insert into iam_scope_project (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return NEW;
END IF;
RAISE EXCEPTION 'unknown scope type';
END;
$$;
create or replace function
iam_sub_scopes_func()
returns trigger
as $$
declare parent_type int;
begin
if new.type = 'organization' then
insert into iam_scope_organization (scope_id, name)
values
(new.public_id, new.name);
return new;
end if;
if new.type = 'project' then
insert into iam_scope_project (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return new;
end if;
raise exception 'unknown scope type';
end;
$$ language plpgsql;
CREATE TRIGGER iam_scope_insert
AFTER
insert ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_sub_scopes_func();
create trigger
iam_scope_insert
after
insert on iam_scope
for each row execute procedure iam_sub_scopes_func();
CREATE
OR REPLACE FUNCTION iam_immutable_scope_type_func() RETURNS TRIGGER
SET SCHEMA
'public' LANGUAGE plpgsql AS $$ DECLARE parent_type INT;
BEGIN IF new.type != old.type THEN
RAISE EXCEPTION 'scope type cannot be updated';
END IF;
return NEW;
END;
$$;
create or replace function
iam_immutable_scope_type_func()
returns trigger
as $$
declare parent_type int;
begin
if new.type != old.type then
raise exception 'scope type cannot be updated';
end if;
return new;
end;
$$ language plpgsql;
CREATE TRIGGER iam_scope_update
BEFORE
update ON iam_scope FOR EACH ROW EXECUTE PROCEDURE iam_immutable_scope_type_func();
create trigger
iam_scope_update
before
update on iam_scope
for each row execute procedure iam_immutable_scope_type_func();
COMMIT;
create trigger
update_time_column
before update on iam_scope
for each row execute procedure update_time_column();
create trigger
immutable_create_time
before
update on iam_scope
for each row execute procedure immutable_create_time_func();
create trigger
default_create_time_column
before
insert on iam_scope
for each row execute procedure default_create_time();
commit;