db timestamp guards (#70)
This commit is contained in:
11 files changed
+820
-150
No files matched your search
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user