|
|
|
|
@@ -50,11 +50,12 @@ BEGIN
|
|
|
|
|
IF NEW.scope='PUBLIC' THEN
|
|
|
|
|
IF NEW.publication_status<>'ACTIVE' OR NOT NEW.is_active OR NEW.is_hidden OR NEW.authoring_version<>0 THEN
|
|
|
|
|
RAISE EXCEPTION 'PUBLIC collection writes are not tenant authoring' USING ERRCODE='23514'; END IF;
|
|
|
|
|
ELSIF NEW.scope='TENANT_OWNED' AND (NEW.publication_status<>'DRAFT' OR NEW.is_active OR NOT NEW.is_hidden OR NEW.authoring_version<>0 OR NEW.question_count<>0) THEN
|
|
|
|
|
ELSIF NEW.scope='TENANT_OWNED' AND (NEW.collection_type<>'MANUAL' OR NEW.publication_status<>'DRAFT' OR NEW.is_active OR NOT NEW.is_hidden OR NEW.authoring_version<>0 OR NEW.question_count<>0) THEN
|
|
|
|
|
RAISE EXCEPTION 'tenant collection must start empty DRAFT' USING ERRCODE='23514'; END IF;
|
|
|
|
|
RETURN NEW;
|
|
|
|
|
END IF;
|
|
|
|
|
IF NEW.scope='PUBLIC' AND NEW IS DISTINCT FROM OLD THEN RAISE EXCEPTION 'PUBLIC collection writes are not tenant authoring' USING ERRCODE='23514'; END IF;
|
|
|
|
|
IF OLD.scope='TENANT_OWNED' AND NEW.collection_type IS DISTINCT FROM OLD.collection_type THEN RAISE EXCEPTION 'tenant collection type is immutable MANUAL' USING ERRCODE='23514'; END IF;
|
|
|
|
|
IF OLD.scope='TENANT_OWNED' AND NEW.deleted IS DISTINCT FROM OLD.deleted THEN RAISE EXCEPTION 'collection cannot be logically deleted' USING ERRCODE='23514'; END IF;
|
|
|
|
|
IF OLD.publication_status='ARCHIVED' AND NEW IS DISTINCT FROM OLD THEN RAISE EXCEPTION 'archived collection is immutable' USING ERRCODE='23514'; END IF;
|
|
|
|
|
IF NEW.authoring_version<>OLD.authoring_version+1 THEN RAISE EXCEPTION 'collection authoring version must advance exactly once' USING ERRCODE='23514'; END IF;
|
|
|
|
|
@@ -78,7 +79,7 @@ END $$;
|
|
|
|
|
|
|
|
|
|
CREATE FUNCTION education_enforce_collection_membership_mutation() RETURNS TRIGGER
|
|
|
|
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,pg_temp AS $$
|
|
|
|
|
DECLARE c_status VARCHAR(16); c_version INTEGER; c_tenant BIGINT; c_scope VARCHAR(20); q_tenant BIGINT; q_scope VARCHAR(20); q_status VARCHAR(20); q_published BOOLEAN; token_count INTEGER;
|
|
|
|
|
DECLARE c_status VARCHAR(16); c_version INTEGER; c_tenant BIGINT; c_scope VARCHAR(20); q_tenant BIGINT; q_scope VARCHAR(20); q_status VARCHAR(20); q_published BOOLEAN; q_deleted BOOLEAN; token_count INTEGER;
|
|
|
|
|
BEGIN
|
|
|
|
|
IF TG_OP='UPDATE' THEN RAISE EXCEPTION 'collection membership replacement requires physical delete and insert' USING ERRCODE='23514'; END IF;
|
|
|
|
|
EXECUTE format('SELECT tenant_id,scope,publication_status,authoring_version FROM %I.education_question_collection WHERE id=$1 FOR UPDATE',TG_TABLE_SCHEMA)
|
|
|
|
|
@@ -89,9 +90,9 @@ BEGIN
|
|
|
|
|
IF token_count<>1 THEN RAISE EXCEPTION 'collection membership mutation requires transaction token' USING ERRCODE='23514'; END IF;
|
|
|
|
|
IF TG_OP='DELETE' THEN RETURN OLD; END IF;
|
|
|
|
|
IF NEW.deleted OR NEW.tenant_id IS DISTINCT FROM c_tenant OR NEW.scope IS DISTINCT FROM c_scope THEN RAISE EXCEPTION 'membership ownership must equal collection' USING ERRCODE='23514'; END IF;
|
|
|
|
|
EXECUTE format('SELECT tenant_id,scope,status,is_published FROM %I.education_question WHERE id=$1 FOR SHARE',TG_TABLE_SCHEMA)
|
|
|
|
|
INTO q_tenant,q_scope,q_status,q_published USING NEW.question_id;
|
|
|
|
|
IF q_tenant IS DISTINCT FROM c_tenant OR q_scope IS DISTINCT FROM 'TENANT_OWNED' OR q_status IS DISTINCT FROM 'PUBLISHED' OR NOT q_published THEN
|
|
|
|
|
EXECUTE format('SELECT tenant_id,scope,status,is_published,deleted FROM %I.education_question WHERE id=$1 FOR SHARE',TG_TABLE_SCHEMA)
|
|
|
|
|
INTO q_tenant,q_scope,q_status,q_published,q_deleted USING NEW.question_id;
|
|
|
|
|
IF q_tenant IS DISTINCT FROM c_tenant OR q_scope IS DISTINCT FROM 'TENANT_OWNED' OR q_status IS DISTINCT FROM 'PUBLISHED' OR NOT q_published OR q_deleted THEN
|
|
|
|
|
RAISE EXCEPTION 'membership question must be current-tenant published' USING ERRCODE='23514'; END IF;
|
|
|
|
|
RETURN NEW;
|
|
|
|
|
END $$;
|
|
|
|
|
@@ -111,6 +112,20 @@ BEGIN
|
|
|
|
|
RETURN NEW;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
CREATE FUNCTION education_require_question_collection_membership_count() RETURNS TRIGGER
|
|
|
|
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,pg_temp AS $$
|
|
|
|
|
DECLARE actual_count INTEGER; consumed INTEGER;
|
|
|
|
|
BEGIN
|
|
|
|
|
IF OLD.publication_status IS DISTINCT FROM 'DRAFT' OR NEW.publication_status IS DISTINCT FROM 'DRAFT' THEN RETURN NULL; END IF;
|
|
|
|
|
EXECUTE format('SELECT count(*) FROM %I.education_question_collection_question WHERE tenant_id=$1 AND collection_id=$2 AND deleted=false',TG_TABLE_SCHEMA)
|
|
|
|
|
INTO actual_count USING NEW.tenant_id,NEW.id;
|
|
|
|
|
IF actual_count IS DISTINCT FROM NEW.question_count THEN RAISE EXCEPTION 'collection question_count must equal physical membership' USING ERRCODE='23514'; END IF;
|
|
|
|
|
EXECUTE format('DELETE FROM %I.education_question_collection_membership_token WHERE transaction_id=$1 AND tenant_id=$2 AND collection_id=$3 AND authoring_version=$4',TG_TABLE_SCHEMA)
|
|
|
|
|
USING pg_current_xact_id()::text::BIGINT,NEW.tenant_id,NEW.id,NEW.authoring_version;
|
|
|
|
|
GET DIAGNOSTICS consumed=ROW_COUNT; IF consumed<>1 THEN RAISE EXCEPTION 'collection membership transaction token required' USING ERRCODE='23514'; END IF;
|
|
|
|
|
RETURN NULL;
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
CREATE FUNCTION education_require_question_collection_audit() RETURNS TRIGGER
|
|
|
|
|
LANGUAGE plpgsql SET search_path=pg_catalog,pg_temp AS $$ DECLARE n INTEGER; BEGIN
|
|
|
|
|
IF NEW.publication_status IS NOT DISTINCT FROM OLD.publication_status THEN RETURN NULL; END IF;
|
|
|
|
|
@@ -122,12 +137,14 @@ CREATE FUNCTION education_prevent_question_collection_audit_mutation() RETURNS T
|
|
|
|
|
REVOKE ALL ON FUNCTION education_enforce_question_collection_authoring() FROM PUBLIC;
|
|
|
|
|
REVOKE ALL ON FUNCTION education_enforce_collection_membership_mutation() FROM PUBLIC;
|
|
|
|
|
REVOKE ALL ON FUNCTION education_validate_question_collection_audit() FROM PUBLIC;
|
|
|
|
|
REVOKE ALL ON FUNCTION education_require_question_collection_membership_count() FROM PUBLIC;
|
|
|
|
|
REVOKE ALL ON FUNCTION education_require_question_collection_audit() FROM PUBLIC;
|
|
|
|
|
REVOKE ALL ON FUNCTION education_prevent_question_collection_audit_mutation() FROM PUBLIC;
|
|
|
|
|
CREATE TRIGGER trg_education_question_collection_authoring BEFORE INSERT OR UPDATE ON education_question_collection FOR EACH ROW EXECUTE FUNCTION education_enforce_question_collection_authoring();
|
|
|
|
|
CREATE TRIGGER trg_education_collection_membership_mutation BEFORE INSERT OR UPDATE OR DELETE ON education_question_collection_question FOR EACH ROW EXECUTE FUNCTION education_enforce_collection_membership_mutation();
|
|
|
|
|
CREATE TRIGGER trg_education_question_collection_audit_validate BEFORE INSERT ON education_question_collection_lifecycle_audit FOR EACH ROW EXECUTE FUNCTION education_validate_question_collection_audit();
|
|
|
|
|
CREATE TRIGGER trg_education_question_collection_audit_immutable BEFORE UPDATE OR DELETE ON education_question_collection_lifecycle_audit FOR EACH ROW EXECUTE FUNCTION education_prevent_question_collection_audit_mutation();
|
|
|
|
|
CREATE CONSTRAINT TRIGGER trg_education_question_collection_membership_count AFTER UPDATE OF question_count ON education_question_collection DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION education_require_question_collection_membership_count();
|
|
|
|
|
CREATE CONSTRAINT TRIGGER trg_education_question_collection_audit_required AFTER UPDATE OF publication_status ON education_question_collection DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION education_require_question_collection_audit();
|
|
|
|
|
|
|
|
|
|
DO $$ DECLARE installed INTEGER; BEGIN
|
|
|
|
|
|