db.changeLog.xml Maven / Gradle / Ivy
The newest version!
<?xml version="1.1" encoding="UTF-8" standalone="no"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> <!-- This should be overridden in production by a system property --> <property name="horreum.db.secret" value="secret"/> <property name="quarkus.datasource.username" value="appuser" /> <!-- Primary model --> <changeSet id="0" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="dbsecret"> <column name="passphrase" type="text" /> </createTable> <insert tableName="dbsecret"> <column name="passphrase" value="${horreum.db.secret}" /> </insert> <createSequence sequenceName="hibernate_sequence" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="hook"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="active" type="boolean"> <constraints nullable="false" /> </column> <column name="target" type="integer"> <constraints nullable="false" /> </column> <column name="type" type="text"> <constraints nullable="false" /> </column> <column name="url" type="text"> <constraints nullable="false" /> </column> </createTable> <addUniqueConstraint tableName="hook" columnNames="url, type, target" /> <createSequence sequenceName="hook_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="run"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="data" type="jsonb"> <constraints nullable="false" /> </column> <column name="start" type="timestamp without time zone"> <constraints nullable="false" /> </column> <column name="stop" type="timestamp without time zone"> <constraints nullable="false" /> </column> <column name="description" type="text" /> <column name="testid" type="integer"> <constraints nullable="false" /> </column> <column name="owner" type="text"> <constraints nullable="false" /> </column> <column name="access" type="integer"> <constraints nullable="false" /> </column> <column name="token" type="text" /> <column name="trashed" type="boolean" defaultValue="false" /> </createTable> <createSequence sequenceName="run_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="schema"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="uri" type="text"> <constraints nullable="false" unique="true"/> </column> <column name="description" type="text" /> <column name="name" type="text"> <constraints nullable="false" unique="true" /> </column> <column name="schema" type="jsonb" /> <column name="testpath" type="text" /> <column name="startpath" type="text" /> <column name="stoppath" type="text" /> <column name="descriptionpath" type="text" /> <column name="owner" type="text"> <constraints nullable="false" /> </column> <column name="token" type="text" /> <column name="access" type="integer"> <constraints nullable="false" /> </column> </createTable> <createSequence sequenceName="schema_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="schemaextractor"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="accessor" type="text"> <constraints nullable="false" /> </column> <column name="jsonpath" type="text"> <constraints nullable="false" /> </column> <column name="schema_id" type="integer"> <constraints nullable="false" foreignKeyName="fk_extractor_schema_id" references="schema(id)"/> </column> </createTable> <createTable tableName="test"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="description" type="text"/> <column name="name" type="text"> <constraints nullable="false" unique="true"/> </column> <column name="owner" type="text"> <constraints nullable="false" /> </column> <column name="token" type="text" /> <column name="access" type="integer"> <constraints nullable="false" /> </column> <column name="defaultview_id" type="integer" /> <column name="compareurl" type="text" /> </createTable> <createSequence sequenceName="test_id_seq" startValue="10" incrementBy="1" cacheSize="1" /> <createTable tableName="view"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="test_id" type="integer"> <constraints nullable="false" foreignKeyName="fk_view_test_id" references="test(id)"/> </column> </createTable> <addForeignKeyConstraint constraintName="fk_test_view_id" baseTableName="test" baseColumnNames="defaultview_id" referencedTableName="view" referencedColumnNames="id" /> <createTable tableName="viewcomponent"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="accessors" type="text"> <constraints nullable="false"/> </column> <column name="headername" type="text"> <constraints nullable="false"/> </column> <column name="headerorder" type="integer"> <constraints nullable="false"/> </column> <column name="render" type="text" /> <column name="view_id" type="integer"> <constraints nullable="false" foreignKeyName="fk_component_view_id" references="view(id)"/> </column> </createTable> <addUniqueConstraint tableName="viewcomponent" columnNames="view_id, headername" /> <sql> GRANT USAGE ON SCHEMA public TO "${quarkus.datasource.username}"; GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE hook, run, schema, schemaextractor, test, view, viewcomponent TO "${quarkus.datasource.username}"; GRANT ALL ON SEQUENCE hibernate_sequence, hook_id_seq, run_id_seq, schema_id_seq, test_id_seq TO "${quarkus.datasource.username}"; </sql> </changeSet> <!-- Auto-updated read-only tables --> <changeSet id="1" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="run_schemas"> <column name="runid" type="integer" /> <column name="testid" type="integer" /> <column name="uri" type="text" /> <column name="schemaid" type="integer" /> <column name="prefix" type="text" /> </createTable> <createProcedure> CREATE OR REPLACE FUNCTION before_run_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE runid = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION before_run_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE runid = OLD.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_func() RETURNS TRIGGER AS $$ DECLARE v_schema text; v_schemaid integer; BEGIN FOR v_schema IN (SELECT jsonb_path_query(NEW.data, '$.\$schema'::jsonpath)#>>'{}') LOOP v_schemaid := (SELECT id FROM schema WHERE uri = v_schema); IF v_schemaid IS NOT NULL THEN INSERT INTO run_schemas (runid, testid, prefix, uri, schemaid) VALUES (NEW.id, NEW.testid, '$', v_schema, v_schemaid); END IF; END LOOP; FOR v_schema IN (SELECT jsonb_path_query(NEW.data, '$.*.\$schema'::jsonpath)#>>'{}') LOOP v_schemaid := (SELECT id FROM schema WHERE uri = v_schema); IF v_schemaid IS NOT NULL THEN INSERT INTO run_schemas (runid, testid, prefix, uri, schemaid) VALUES (NEW.id, NEW.testid, '$.*', v_schema, v_schemaid); END IF; END LOOP; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION before_schema_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE schemaid = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION before_schema_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE schemaid = OLD.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION after_schema_update_func() RETURNS TRIGGER AS $$ BEGIN WITH rs AS ( SELECT id, owner, access, token, testid, '$' as prefix, jsonb_path_query(RUN.data, '$.\$schema'::jsonpath)#>>'{}' as uri FROM run UNION SELECT id, owner, access, token, testid, '$.*' as prefix, jsonb_path_query(RUN.data, '$.*.\$schema'::jsonpath)#>>'{}' as uri FROM run ) INSERT INTO run_schemas SELECT rs.id as runid, rs.testid, rs.uri, NEW.id as schemaid, prefix FROM rs WHERE rs.uri = NEW.uri; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createTable tableName="view_data"> <column name="vcid" type="integer" /> <column name="runid" type="integer" /> <column name="extractor_ids" type="int[]" /> <column name="object" type="jsonb" /> </createTable> <createProcedure> CREATE OR REPLACE FUNCTION vd_before_delete_run_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM view_data WHERE runid = OLD.runid; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_after_insert_run_func() RETURNS TRIGGER AS $$ BEGIN WITH vcs AS ( SELECT id, unnest(regexp_split_to_array(accessors, ';')) as aa FROM viewcomponent ) INSERT INTO view_data SELECT vcs.id as vcid, rs.runid, array_agg(se.id) as extractor_ids, jsonb_object_agg(se.accessor, (CASE WHEN aa like '%[]' THEN jsonb_path_query_array(run.data, (rs.prefix || se.jsonpath)::jsonpath) ELSE jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) END)) as object FROM vcs JOIN schemaextractor se ON se.accessor = replace(aa, '[]', '') JOIN run_schemas rs ON rs.schemaid = se.schema_id JOIN run on run.id = rs.runid WHERE run.id = NEW.runid GROUP BY runid, vcid; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_before_delete_extractor_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM view_data WHERE OLD.id = ANY(extractor_ids); RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_before_update_extractor_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM view_data WHERE OLD.id = ANY(extractor_ids); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_after_update_extractor_func() RETURNS TRIGGER AS $$ BEGIN WITH vcs AS ( SELECT id, unnest(regexp_split_to_array(accessors, ';')) as aa FROM viewcomponent ) INSERT INTO view_data SELECT vcs.id as vcid, rs.runid, array_agg(se.id) as extractor_ids, jsonb_object_agg(se.accessor, (CASE WHEN aa like '%[]' THEN jsonb_path_query_array(run.data, (rs.prefix || se.jsonpath)::jsonpath) ELSE jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) END)) as object FROM vcs JOIN schemaextractor se ON se.accessor = replace(aa, '[]', '') JOIN run_schemas rs ON rs.schemaid = se.schema_id JOIN run on run.id = rs.runid WHERE se.id = NEW.id GROUP BY runid, vcid; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_before_delete_vc_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM view_data WHERE vcid = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_before_update_vc_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM view_data WHERE vcid = OLD.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION vd_after_update_vc_func() RETURNS TRIGGER AS $$ BEGIN WITH vcs AS ( SELECT id, unnest(regexp_split_to_array(accessors, ';')) as aa FROM viewcomponent WHERE id = NEW.id ) INSERT INTO view_data SELECT vcs.id as vcid, rs.runid, array_agg(se.id) as extractor_ids, jsonb_object_agg(se.accessor, (CASE WHEN aa like '%[]' THEN jsonb_path_query_array(run.data, (rs.prefix || se.jsonpath)::jsonpath) ELSE jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) END)) as object FROM vcs JOIN schemaextractor se ON se.accessor = replace(aa, '[]', '') JOIN run_schemas rs ON rs.schemaid = se.schema_id JOIN run on run.id = rs.runid GROUP BY runid, vcid; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <!-- Triggers run with privileges of the user running the update = appuser --> <sql> GRANT select, insert, delete, update ON TABLE run_schemas, view_data TO "${quarkus.datasource.username}"; </sql> <sql> CREATE TRIGGER before_run_delete BEFORE DELETE ON run FOR EACH ROW EXECUTE FUNCTION before_run_delete_func(); CREATE TRIGGER before_run_update BEFORE UPDATE ON run FOR EACH ROW EXECUTE FUNCTION before_run_update_func(); CREATE TRIGGER after_run_update AFTER INSERT OR UPDATE ON run FOR EACH ROW EXECUTE FUNCTION after_run_update_func(); CREATE TRIGGER before_schema_delete BEFORE DELETE ON schema FOR EACH ROW EXECUTE FUNCTION before_schema_delete_func(); CREATE TRIGGER before_schema_update BEFORE UPDATE OF uri ON schema FOR EACH ROW EXECUTE FUNCTION before_schema_update_func(); CREATE TRIGGER after_schema_update AFTER INSERT OR UPDATE OF uri ON schema FOR EACH ROW EXECUTE FUNCTION after_schema_update_func(); CREATE TRIGGER vd_before_delete BEFORE DELETE ON run_schemas FOR EACH ROW EXECUTE FUNCTION vd_before_delete_run_func(); CREATE TRIGGER vd_after_insert AFTER INSERT ON run_schemas FOR EACH ROW EXECUTE FUNCTION vd_after_insert_run_func(); CREATE TRIGGER vd_before_delete BEFORE DELETE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION vd_before_delete_extractor_func(); CREATE TRIGGER vd_before_update BEFORE UPDATE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION vd_before_update_extractor_func(); CREATE TRIGGER vd_after_update AFTER INSERT OR UPDATE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION vd_after_update_extractor_func(); CREATE TRIGGER vd_before_delete BEFORE DELETE ON viewcomponent FOR EACH ROW EXECUTE FUNCTION vd_before_delete_vc_func(); CREATE TRIGGER vd_before_update BEFORE UPDATE OF id, accessors ON viewcomponent FOR EACH ROW EXECUTE FUNCTION vd_before_update_vc_func(); CREATE TRIGGER vd_after_update AFTER INSERT OR UPDATE OF id, accessors ON viewcomponent FOR EACH ROW EXECUTE FUNCTION vd_after_update_vc_func(); </sql> <rollback> DROP TRIGGER IF EXISTS before_run_delete ON run; DROP TRIGGER IF EXISTS before_run_update ON run; DROP TRIGGER IF EXISTS after_run_update ON run; DROP TRIGGER IF EXISTS before_schema_delete ON run; DROP TRIGGER IF EXISTS before_schema_update ON run; DROP TRIGGER IF EXISTS after_schema_update ON run; DROP TRIGGER IF EXISTS vd_before_delete ON run_schemas; DROP TRIGGER IF EXISTS vd_after_insert ON run_schemas; DROP TRIGGER IF EXISTS vd_before_delete ON schemaextractor; DROP TRIGGER IF EXISTS vd_before_update ON schemaextractor; DROP TRIGGER IF EXISTS vd_after_update ON schemaextractor; DROP TRIGGER IF EXISTS vd_before_delete ON viewcomponent; DROP TRIGGER IF EXISTS vd_before_update ON viewcomponent; DROP TRIGGER IF EXISTS vd_after_update ON viewcomponent; </rollback> </changeSet> <!-- Security constraints --> <changeSet id="2" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> <!-- Install pgcrypto plugin --> CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; </sql> <createProcedure> <!-- Verify that what user has in horreum.userroles is correctly signed --> CREATE OR REPLACE FUNCTION has_role(owner TEXT) RETURNS boolean AS $$ DECLARE v_passphrase TEXT; v_userroles TEXT; v_role_salt_sign TEXT; v_parts TEXT[]; v_role TEXT; v_salt TEXT; v_signature TEXT; v_computed TEXT; BEGIN SELECT passphrase INTO v_passphrase FROM dbsecret; v_userroles := current_setting('horreum.userroles', true); IF v_userroles = '' OR v_userroles IS NULL THEN RETURN 0; END IF; FOREACH v_role_salt_sign IN ARRAY regexp_split_to_array(v_userroles, ',') LOOP v_parts := regexp_split_to_array(v_role_salt_sign, ':'); v_role := v_parts[1]; IF v_role = owner THEN v_salt := v_parts[2]; v_signature := v_parts[3]; v_computed := encode(digest(v_role || v_salt || v_passphrase, 'sha256'), 'base64'); IF v_computed = v_signature THEN RETURN 1; ELSE RAISE EXCEPTION 'invalid role + salt + signature'; END IF; END IF; END LOOP; RETURN 0; END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION can_view(access INTEGER, owner TEXT, token TEXT) RETURNS boolean AS $$ BEGIN RETURN ( access = 0 OR (access = 1 AND has_role('viewer')) OR (access = 2 AND has_role(owner) AND has_role('viewer')) OR token = current_setting('horreum.token', true) ); END; $$ LANGUAGE plpgsql STABLE; </createProcedure> <sqlFile path="policies.sql" relativeToChangelogFile="true" /> </changeSet> <changeSet id="3" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="variable"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="name" type="text"> <constraints nullable="false" /> </column> <column name="testid" type="integer"> <!-- No foregin key constraint --> <constraints nullable="false" /> </column> <column name="accessors" type="text"> <constraints nullable="false" /> </column> <column name="calculation" type="text"/> <column name="maxwindow" type="integer"> <constraints nullable="false"/> </column> <column name="deviationfactor" type="double precision"> <constraints nullable="false"/> </column> <column name="confidence" type="double precision"> <constraints nullable="false"/> </column> </createTable> <createTable tableName="datapoint"> <column name="id" type="integer" autoIncrement="true"> <constraints nullable="false" primaryKey="true"/> </column> <column name="runid" type="integer"> <!-- No foreign key constraint --> <constraints nullable="false" /> </column> <column name="timestamp" type="timestamp"> <constraints nullable="false" /> </column> <column name="variable_id" type="integer"> <constraints nullable="false" /> </column> <column name="value" type="double precision"> <constraints nullable="false" /> </column> </createTable> <addForeignKeyConstraint constraintName="fk_datapoint_variable_id" baseTableName="datapoint" baseColumnNames="variable_id" referencedTableName="variable" referencedColumnNames="id" /> <createTable tableName="change"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="confirmed" type="boolean"> <constraints nullable="false"/> </column> <column name="description" type="text"/> <column name="variable_id" type="integer"> <constraints nullable="false"/> </column> <column name="runid" type="integer"> <constraints nullable="false"/> </column> <column name="timestamp" type="timestamp"> <constraints nullable="false"/> </column> </createTable> <addForeignKeyConstraint constraintName="fk_change_variable_id" baseTableName="change" baseColumnNames="variable_id" referencedTableName="variable" referencedColumnNames="id" /> <createTable tableName="watch"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="testid" type="integer"> <constraints nullable="false"/> </column> </createTable> <addUniqueConstraint tableName="watch" columnNames="testid"/> <createTable tableName="watch_users"> <column name="watch_id" type="integer"/> <column name="users" type="text" /> </createTable> <addForeignKeyConstraint constraintName="fk_watch_users" baseTableName="watch_users" baseColumnNames="watch_id" referencedTableName="watch" referencedColumnNames="id" /> <createTable tableName="watch_teams"> <column name="watch_id" type="integer"/> <column name="teams" type="text" /> </createTable> <addForeignKeyConstraint constraintName="fk_watch_teams" baseTableName="watch_teams" baseColumnNames="watch_id" referencedTableName="watch" referencedColumnNames="id" /> <createTable tableName="notificationsettings"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="disabled" type="boolean" defaultValue="false"> <constraints nullable="false"/> </column> <column name="isteam" type="boolean"> <constraints nullable="false"/> </column> <column name="method" type="text"> <constraints nullable="false"/> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="data" type="text" /> </createTable> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE change, datapoint, notificationsettings, variable, watch, watch_teams, watch_users TO "${quarkus.datasource.username}"; <!-- In case of ALL policy the USING is applied on the inserted data as well --> ALTER TABLE change ENABLE ROW LEVEL SECURITY; ALTER TABLE datapoint ENABLE ROW LEVEL SECURITY; ALTER TABLE notificationsettings ENABLE ROW LEVEL SECURITY; ALTER TABLE variable ENABLE ROW LEVEL SECURITY; ALTER TABLE watch ENABLE ROW LEVEL SECURITY; ALTER TABLE watch_teams ENABLE ROW LEVEL SECURITY; ALTER TABLE watch_users ENABLE ROW LEVEL SECURITY; CREATE POLICY change_select ON change FOR SELECT USING (has_role('horreum.alerting') OR exists( SELECT 1 FROM run WHERE run.id = runid AND can_view(run.access, run.owner, run.token) )); CREATE POLICY change_insert ON change FOR INSERT WITH CHECK (has_role('horreum.alerting') OR exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) )); CREATE POLICY change_update ON change FOR UPDATE USING (exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) )) WITH CHECK (has_role('tester') AND exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) )); CREATE POLICY change_delete ON change FOR DELETE USING (has_role('tester') AND exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) )); CREATE POLICY datapoint_select ON datapoint FOR SELECT USING (has_role('horreum.alerting') OR exists( SELECT 1 FROM run WHERE run.id = runid AND can_view(run.access, run.owner, run.token) )); CREATE POLICY datapoint_insert ON datapoint FOR INSERT WITH CHECK (has_role('horreum.alerting')); CREATE POLICY datapoint_update ON datapoint FOR UPDATE USING (has_role('horreum.alerting')); CREATE POLICY datapoint_delete ON datapoint FOR DELETE USING (has_role('horreum.alerting') OR exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) )); CREATE POLICY notificationsettings_policies ON notificationsettings FOR ALL USING (has_role('horreum.alerting') OR has_role(name)); CREATE POLICY variable_select ON variable FOR SELECT USING (has_role('horreum.alerting') OR exists( SELECT 1 FROM test WHERE test.id = testid AND can_view(test.access, test.owner, test.token) )); CREATE POLICY variable_insert ON variable FOR INSERT WITH CHECK (has_role('horreum.alerting') OR exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) )); CREATE POLICY variable_update ON variable FOR UPDATE USING (has_role('horreum.alerting') OR exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) )); CREATE POLICY variable_delete ON variable FOR DELETE USING (has_role('horreum.alerting') OR exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) )); CREATE POLICY watch_select ON watch FOR SELECT USING (true); CREATE POLICY watch_insert ON watch FOR INSERT WITH CHECK (true); CREATE POLICY watch_update ON watch FOR UPDATE USING (has_role('horreum.alerting')); CREATE POLICY watch_delete ON watch FOR DELETE USING (has_role('horreum.alerting')); CREATE POLICY watch_teams_policies ON watch_teams FOR ALL USING (has_role('horreum.alerting') OR has_role(teams)); CREATE POLICY watch_users_policies ON watch_users FOR ALL USING (has_role('horreum.alerting') OR has_role(users)); </sql> </changeSet> <changeSet id="4" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="grafana_dashboard"> <column name="uid" type="text"> <constraints nullable="false" primaryKey="true"/> </column> <column name="testid" type="integer"> <constraints nullable="false" /> </column> <column name="url" type="text"> <constraints nullable="false" /> </column> </createTable> <createTable tableName="grafana_dashboard_variable"> <column name="grafana_dashboard_uid" type="text"> <constraints nullable="false" /> </column> <column name="variables_id" type="integer"> <constraints nullable="false" /> </column> <column name="variables_order" type="integer"> <constraints nullable="false" /> </column> </createTable> <addPrimaryKey tableName="grafana_dashboard_variable" columnNames="grafana_dashboard_uid, variables_order" /> <addForeignKeyConstraint baseTableName="grafana_dashboard_variable" baseColumnNames="grafana_dashboard_uid" constraintName="fk_grafana_dashboard_uid" referencedTableName="grafana_dashboard" referencedColumnNames="uid" /> <addForeignKeyConstraint baseTableName="grafana_dashboard_variable" baseColumnNames="variables_id" constraintName="fk_grafana_dashboard_variables_id" referencedTableName="variable" referencedColumnNames="id" /> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE grafana_dashboard, grafana_dashboard_variable TO "${quarkus.datasource.username}"; ALTER TABLE grafana_dashboard ENABLE ROW LEVEL SECURITY; ALTER TABLE grafana_dashboard_variable ENABLE ROW LEVEL SECURITY; <!-- Anyone who could view the test/dashboard can also create it --> CREATE POLICY grafana_dashboard_policies ON grafana_dashboard FOR ALL USING (exists( SELECT 1 FROM test WHERE test.id = testid AND can_view(test.access, test.owner, test.token) )); CREATE POLICY grafana_dashboard_variable_policies ON grafana_dashboard_variable FOR ALL USING (exists( SELECT 1 FROM test JOIN grafana_dashboard gd ON gd.uid = grafana_dashboard_uid WHERE test.id = gd.testid AND can_view(test.access, test.owner, test.token) )); </sql> </changeSet> <changeSet id="5" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="userinfo"> <column name="username" type="text"> <constraints primaryKey="true" nullable="false" /> </column> </createTable> <createTable tableName="userinfo_teams"> <column name="username" type="text"> <constraints nullable="false" /> </column> <column name="team" type="text"> <constraints nullable="false"/> </column> </createTable> <addForeignKeyConstraint constraintName="fk_userinfo_username" baseTableName="userinfo_teams" baseColumnNames="username" referencedTableName="userinfo" referencedColumnNames="username" /> <addUniqueConstraint tableName="userinfo_teams" columnNames="username,team" /> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE userinfo, userinfo_teams TO "${quarkus.datasource.username}"; ALTER TABLE userinfo ENABLE ROW LEVEL SECURITY; ALTER TABLE userinfo_teams ENABLE ROW LEVEL SECURITY; CREATE POLICY userinfo_policies ON userinfo FOR ALL USING (has_role('horreum.alerting')); CREATE POLICY userinfo_teams_policies ON userinfo_teams FOR ALL USING (has_role('horreum.alerting')); </sql> </changeSet> <changeSet id="6" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="variable"> <column name="group" type="text" /> </addColumn> <addColumn tableName="variable"> <column name="order" type="integer" defaultValue="0"> <constraints nullable="false"/> </column> </addColumn> </changeSet> <changeSet id="7" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropTable tableName="grafana_dashboard_variable" /> <createTable tableName="grafana_panel"> <column name="id" type="integer"> <constraints primaryKey="true" nullable="false" /> </column> <column name="order" type="integer" /> <column name="grafana_dashboard_uid" type="text"> <constraints nullable="false" /> </column> </createTable> <addForeignKeyConstraint constraintName="fk_grafana_panel_dashboard_uid" baseTableName="grafana_panel" baseColumnNames="grafana_dashboard_uid" referencedTableName="grafana_dashboard" referencedColumnNames="uid" /> <createTable tableName="grafana_panel_variable"> <column name="grafana_panel_id" type="integer" /> <column name="variables_id" type="integer"> <constraints nullable="false" /> </column> <column name="variables_order" type="integer"> <constraints nullable="false" /> </column> </createTable> <addPrimaryKey tableName="grafana_panel_variable" columnNames="grafana_panel_id, variables_order" /> <addForeignKeyConstraint baseTableName="grafana_panel_variable" baseColumnNames="grafana_panel_id" constraintName="fk_grafana_panel_id" referencedTableName="grafana_panel" referencedColumnNames="id" /> <addForeignKeyConstraint baseTableName="grafana_panel_variable" baseColumnNames="variables_id" constraintName="fk_grafana_panel_variables_id" referencedTableName="variable" referencedColumnNames="id" /> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE grafana_panel, grafana_panel_variable TO "${quarkus.datasource.username}"; ALTER TABLE grafana_panel ENABLE ROW LEVEL SECURITY; ALTER TABLE grafana_panel_variable ENABLE ROW LEVEL SECURITY; CREATE POLICY grafana_panel_policies ON grafana_panel FOR ALL USING (exists( SELECT 1 FROM test JOIN grafana_dashboard gd ON gd.testid = test.id WHERE gd.uid = grafana_dashboard_uid AND can_view(test.access, test.owner, test.token) )); CREATE POLICY grafana_panel_variable_policies ON grafana_panel_variable FOR ALL USING (exists( SELECT 1 FROM test JOIN grafana_dashboard gd ON gd.testid = test.id JOIN grafana_panel gp ON gp.grafana_dashboard_uid = gd.uid WHERE gp.id = grafana_panel_id AND can_view(test.access, test.owner, test.token) )); </sql> </changeSet> <changeSet id="8" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> GRANT ALL ON SEQUENCE datapoint_id_seq TO "${quarkus.datasource.username}"; </sql> </changeSet> <changeSet id="9" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION to_double(v_input text) RETURNS double precision AS $$ BEGIN BEGIN RETURN v_input::double precision; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="10" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="variable"> <column name="minwindow" type="integer" defaultValue="0"></column> </addColumn> </changeSet> <changeSet id="11" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropColumn tableName="variable"> <column name="maxwindow" /> <column name="confidence" /> <column name="deviationfactor" /> </dropColumn> <addColumn tableName="variable"> <column name="maxdifferencelastdatapoint" type="double precision" defaultValue="0.05"> <constraints nullable="false"/> </column> <column name="maxdifferencefloatingwindow" type="double precision" defaultValue="0.05"> <constraints nullable="false"/> </column> <column name="floatingwindow" type="integer" defaultValue="7"> <constraints nullable="false"/> </column> </addColumn> </changeSet> <changeSet id="12" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="test"> <column name="tags" type="text" /> </addColumn> <createTable tableName="run_tags"> <column name="runid" type="integer"> <constraints primaryKey="true" nullable="false" /> </column> <column name="tags" type="jsonb" /> <column name="extractor_ids" type="jsonb" /> </createTable> <addForeignKeyConstraint constraintName="fk_run_tags_runid" baseTableName="run_tags" baseColumnNames="runid" referencedTableName="run" referencedColumnNames="id" /> <sql> GRANT select, insert, delete ON TABLE run_tags TO "${quarkus.datasource.username}"; </sql> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_delete_test_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE runid IN (SELECT id FROM run WHERE testid = OLD.id); RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_update_test_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE runid IN (SELECT id FROM run WHERE testid = OLD.id); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_delete_run_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE runid = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <!-- We piggy-back on run_schemas --> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_insert_run_schemas_func() RETURNS TRIGGER AS $$ BEGIN WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), tags AS ( SELECT rs.runid, se.id as extractor_id, se.accessor, jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) AS value FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id JOIN run ON run.id = rs.runid WHERE rs.runid = NEW.runid ) INSERT INTO run_tags SELECT tags.runid, jsonb_object_agg(tags.accessor, tags.value) AS tags, jsonb_agg(tags.extractor_id) AS extractor_ids FROM tags GROUP BY runid; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_insert_test_func() RETURNS TRIGGER AS $$ BEGIN WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), tags AS ( SELECT rs.runid, se.id AS extractor_id, se.accessor, jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) AS value FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id JOIN run ON run.id = rs.runid WHERE rs.testid = NEW.id ) INSERT INTO run_tags SELECT tags.runid, jsonb_object_agg(tags.accessor, tags.value) AS tags, jsonb_agg(tags.extractor_id) AS extractor_ids FROM tags GROUP BY runid; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_delete_extractor_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE OLD.id = ANY(extractor_ids); RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_update_extractor_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE OLD.id = ANY(extractor_ids); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_update_extractor_func() RETURNS TRIGGER AS $$ BEGIN WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), matching_runs AS ( SELECT runid FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id WHERE se.id = NEW.id ), tags AS ( SELECT rs.runid, se.id AS extractor_id, se.accessor, jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) AS value FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id JOIN run ON run.id = rs.runid WHERE run.id = ANY(SELECT runid FROM matching_runs) ) INSERT INTO run_tags SELECT tags.runid, jsonb_object_agg(tags.accessor, tags.value) AS tags, jsonb_agg(tags.extractor_id) AS extractor_ids FROM tags GROUP BY runid; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER rt_before_delete BEFORE DELETE ON test FOR EACH ROW EXECUTE FUNCTION rt_before_delete_test_func(); CREATE TRIGGER rt_before_update BEFORE UPDATE ON test FOR EACH ROW EXECUTE FUNCTION rt_before_update_test_func(); CREATE TRIGGER rt_after_insert AFTER INSERT OR UPDATE ON test FOR EACH ROW EXECUTE FUNCTION rt_after_insert_test_func(); CREATE TRIGGER rt_before_delete BEFORE DELETE ON run FOR EACH ROW EXECUTE FUNCTION rt_before_delete_run_func(); CREATE TRIGGER rt_after_insert AFTER INSERT ON run_schemas FOR EACH ROW EXECUTE FUNCTION rt_after_insert_run_schemas_func(); CREATE TRIGGER rt_before_delete BEFORE DELETE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION rt_before_delete_extractor_func(); CREATE TRIGGER rt_before_update BEFORE UPDATE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION rt_before_delete_extractor_func(); CREATE TRIGGER rt_after_update AFTER INSERT OR UPDATE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION rt_after_update_extractor_func(); </sql> <rollback> DROP TRIGGER IF EXISTS rt_before_delete ON test; DROP TRIGGER IF EXISTS rt_before_update ON test; DROP TRIGGER IF EXISTS rt_after_insert ON test; DROP TRIGGER IF EXISTS rt_before_delete ON run; DROP TRIGGER IF EXISTS rt_after_insert ON run_schemas; DROP TRIGGER IF EXISTS rt_before_delete ON schemaextractor; DROP TRIGGER IF EXISTS rt_before_update ON schemaextractor; DROP TRIGGER IF EXISTS rt_after_update ON schemaextractor; </rollback> </changeSet> <changeSet id="13" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="grafana_dashboard"> <column name="tags" type="text" /> </addColumn> </changeSet> <changeSet id="14" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropColumn tableName="run_tags" columnName="extractor_ids" /> <addColumn tableName="run_tags"> <column name="extractor_ids" type="int[]" /> </addColumn> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_insert_run_schemas_func() RETURNS TRIGGER AS $$ BEGIN WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), tags AS ( SELECT rs.runid, se.id as extractor_id, se.accessor, jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) AS value FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id JOIN run ON run.id = rs.runid WHERE rs.runid = NEW.runid ) INSERT INTO run_tags SELECT tags.runid, jsonb_object_agg(tags.accessor, tags.value) AS tags, array_agg(tags.extractor_id) AS extractor_ids FROM tags GROUP BY runid; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_insert_test_func() RETURNS TRIGGER AS $$ BEGIN WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), tags AS ( SELECT rs.runid, se.id AS extractor_id, se.accessor, jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) AS value FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id JOIN run ON run.id = rs.runid WHERE rs.testid = NEW.id ) INSERT INTO run_tags SELECT tags.runid, jsonb_object_agg(tags.accessor, tags.value) AS tags, array_agg(tags.extractor_id) AS extractor_ids FROM tags GROUP BY runid; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_update_extractor_func() RETURNS TRIGGER AS $$ BEGIN WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), matching_runs AS ( SELECT runid FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id WHERE se.id = NEW.id ), tags AS ( SELECT rs.runid, se.id AS extractor_id, se.accessor, jsonb_path_query_first(run.data, (rs.prefix || se.jsonpath)::jsonpath) AS value FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id JOIN run ON run.id = rs.runid WHERE run.id = ANY(SELECT runid FROM matching_runs) ) INSERT INTO run_tags SELECT tags.runid, jsonb_object_agg(tags.accessor, tags.value) AS tags, array_agg(tags.extractor_id) AS extractor_ids FROM tags GROUP BY runid; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="15" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER rt_before_update ON schemaextractor; CREATE TRIGGER rt_before_update BEFORE UPDATE ON schemaextractor FOR EACH ROW EXECUTE FUNCTION rt_before_update_extractor_func(); </sql> </changeSet> <changeSet id="16" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_insert_extractor_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE runid IN (SELECT runid FROM run_schemas WHERE run_schemas.schemaid = NEW.schema_id); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER rt_before_insert BEFORE INSERT ON schemaextractor FOR EACH ROW EXECUTE FUNCTION rt_before_insert_extractor_func(); </sql> </changeSet> <changeSet id="17" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION rt_before_delete_run_schemas_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_tags WHERE run_tags.runid = OLD.runid; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER rt_before_delete BEFORE DELETE ON run_schemas FOR EACH ROW EXECUTE FUNCTION rt_before_delete_run_schemas_func(); </sql> </changeSet> <changeSet id="18" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="grafana_panel"> <column name="name" type="text" /> </addColumn> </changeSet> <changeSet id="19" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="test_stalenesssettings"> <column name="test_id" type="integer"> <constraints nullable="false"/> </column> <column name="maxstaleness" type="bigint"> <constraints nullable="false"/> </column> <column name="tags" type="jsonb" /> </createTable> <addForeignKeyConstraint constraintName="fk_ss_test_id" baseTableName="test_stalenesssettings" baseColumnNames="test_id" referencedTableName="test" referencedColumnNames="id" /> <createTable tableName="lastmissingrunnotification"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false" /> </column> <column name="lastnotification" type="timestamp without time zone"/> <column name="tags" type="jsonb"/> <column name="testid" type="integer"> <constraints nullable="false"/> </column> </createTable> <sql> GRANT select, insert, delete ON TABLE test_stalenesssettings TO "${quarkus.datasource.username}"; GRANT select, insert, delete ON TABLE lastmissingrunnotification TO "${quarkus.datasource.username}"; ALTER TABLE test_stalenesssettings ENABLE ROW LEVEL SECURITY; CREATE POLICY ss_select ON test_stalenesssettings FOR SELECT USING (exists( SELECT 1 FROM test WHERE test.id = test_id AND can_view(test.access, test.owner, test.token) )); CREATE POLICY ss_insert ON test_stalenesssettings FOR INSERT WITH CHECK (exists( SELECT 1 FROM test WHERE test.id = test_id AND has_role(test.owner) )); CREATE POLICY ss_update ON test_stalenesssettings FOR UPDATE USING (exists( SELECT 1 FROM test WHERE test.id = test_id AND has_role(test.owner) )) WITH CHECK (has_role('tester') AND exists( SELECT 1 FROM test WHERE test.id = test_id AND has_role(test.owner) )); CREATE POLICY ss_delete ON test_stalenesssettings FOR DELETE USING (has_role('tester') AND exists( SELECT 1 FROM test WHERE test.id = test_id AND has_role(test.owner) )); ALTER TABLE lastmissingrunnotification ENABLE ROW LEVEL SECURITY; CREATE POLICY lmrn_all ON lastmissingrunnotification FOR ALL USING (has_role('horreum.alerting')); </sql> </changeSet> <changeSet id="20" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="calculationlog"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false" /> </column> <column name="level" type="integer"> <constraints nullable="false" /> </column> <column name="timestamp" type="timestamp without time zone"> <constraints nullable="false" /> </column> <column name="testid" type="integer" /> <column name="runid" type="integer" /> <column name="message" type="text"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT select, insert, delete ON TABLE calculationlog TO "${quarkus.datasource.username}"; ALTER TABLE calculationlog ENABLE ROW LEVEL SECURITY; CREATE POLICY cl_all ON calculationlog FOR ALL USING (has_role('horreum.alerting') OR (exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) ) AND exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) ))); </sql> </changeSet> <changeSet id="21" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> GRANT update ON TABLE test_stalenesssettings TO "${quarkus.datasource.username}"; GRANT update ON TABLE lastmissingrunnotification TO "${quarkus.datasource.username}"; GRANT update ON TABLE calculationlog TO "${quarkus.datasource.username}"; </sql> </changeSet> <changeSet id="22" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropTable tableName="grafana_panel_variable"/> <dropTable tableName="grafana_panel"/> <dropTable tableName="grafana_dashboard"/> </changeSet> <changeSet id="23" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="test"> <column name="notificationsenabled" type="boolean" defaultValue="true"> <constraints nullable="false" /> </column> </addColumn> <createProcedure> CREATE OR REPLACE FUNCTION can_view(access INTEGER, owner TEXT, token TEXT) RETURNS boolean AS $$ BEGIN RETURN ( access = 0 OR (access = 1 AND has_role('viewer')) OR (access = 2 AND has_role(owner) AND has_role('viewer')) OR token = current_setting('horreum.token', true) OR has_role('horreum.alerting') ); END; $$ LANGUAGE plpgsql STABLE; </createProcedure> </changeSet> <changeSet id="24" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="allowedhookprefix"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false" /> </column> <column name="prefix" type="text"> <constraints nullable="false" /> </column> </createTable> <!-- Only admin and test owners can both view and modify webhooks. --> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE allowedhookprefix TO "${quarkus.datasource.username}"; ALTER TABLE allowedhookprefix ENABLE ROW LEVEL SECURITY; CREATE POLICY prefix_select ON allowedhookprefix FOR SELECT USING(true); CREATE POLICY prefix_insert ON allowedhookprefix FOR INSERT WITH CHECK (has_role('admin')); CREATE POLICY prefix_update ON allowedhookprefix FOR UPDATE USING (has_role('admin')); CREATE POLICY prefix_delete ON allowedhookprefix FOR DELETE USING (has_role('admin')); ALTER TABLE hook ENABLE ROW LEVEL SECURITY; CREATE POLICY hook_policies ON hook USING (has_role('admin') OR exists( SELECT 1 FROM test WHERE (type = 'change/new' OR type = 'run/new') AND target = test.id AND can_view(test.access, test.owner, test.token) )); CREATE POLICY hook_write_check ON hook WITH CHECK (exists(SELECT 1 FROM allowedhookprefix ahp WHERE left(url, length(ahp.prefix)) = ahp.prefix)); </sql> </changeSet> <changeSet id="25" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="test_token"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="test_id" type="integer"> <constraints nullable="false"/> </column> <column name="value" type="text"> <constraints nullable="false" /> </column> <column name="permissions" type="integer"> <constraints nullable="false" /> </column> <column name="description" type="text"> <constraints nullable="false"/> </column> </createTable> <addForeignKeyConstraint constraintName="fk_token_test_id" baseTableName="test_token" baseColumnNames="test_id" referencedTableName="test" referencedColumnNames="id" /> <sql> DROP POLICY view_select ON view; DROP POLICY vc_select ON viewcomponent; DROP POLICY variable_select ON variable; DROP POLICY ss_select ON test_stalenesssettings; DROP POLICY hook_policies ON hook; DROP POLICY test_select ON TEST; </sql> <dropColumn tableName="test" columnName="token" /> <createProcedure> CREATE OR REPLACE FUNCTION has_role2(owner TEXT, type TEXT) RETURNS boolean AS $$ BEGIN RETURN (right(owner, 4) = 'team' AND has_role(left(owner, -4) || type)); END; $$ LANGUAGE plpgsql STABLE; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION can_view2(access INTEGER, owner TEXT) RETURNS boolean AS $$ BEGIN RETURN ( access = 0 OR (access = 1 AND has_role('viewer')) OR (access = 2 AND has_role(owner) AND has_role('viewer')) ); END; $$ LANGUAGE plpgsql STABLE; </createProcedure> <createProcedure> -- this function is a security definer and as such avoids regular policies on test_token CREATE OR REPLACE FUNCTION has_read_token(testid INTEGER) RETURNS boolean AS $$ BEGIN RETURN (exists( SELECT 1 FROM test_token WHERE test_id = testid AND (permissions & 1) != 0 AND value = current_setting('horreum.token', true) )); END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <createProcedure> -- this function is a security definer and as such avoids regular policies on test_token CREATE OR REPLACE FUNCTION has_modify_token(testid INTEGER) RETURNS boolean AS $$ BEGIN RETURN (exists( SELECT 1 FROM test_token WHERE test_id = testid AND (permissions & 2) != 0 AND value = current_setting('horreum.token', true) )); END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <createProcedure> -- this function is a security definer and as such avoids regular policies on test_token CREATE OR REPLACE FUNCTION has_upload_token(testid INTEGER) RETURNS boolean AS $$ BEGIN RETURN (exists( SELECT 1 FROM test_token WHERE test_id = testid AND (permissions & 4) != 0 AND value = current_setting('horreum.token', true) )); END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE test_token TO "${quarkus.datasource.username}"; ALTER TABLE test_token ENABLE ROW LEVEL SECURITY; CREATE POLICY token_policy ON test_token USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); </sql> <sql> -- run_select unchanged DROP POLICY run_insert ON run; CREATE POLICY run_insert ON run FOR INSERT WITH CHECK (has_role2(owner, 'uploader') OR has_upload_token(testid)); DROP POLICY run_update ON run; CREATE POLICY run_update ON run FOR UPDATE USING (has_role2(owner, 'tester')); DROP POLICY run_delete ON run; -- Testers update runs' trashed flag but never really delete these CREATE POLICY run_delete ON run FOR DELETE USING (has_role('horreum.system')); </sql> <sql> CREATE POLICY test_select ON test FOR SELECT USING (can_view2(access, owner) OR has_read_token(id)); DROP POLICY test_insert ON test; CREATE POLICY test_insert ON test FOR INSERT WITH CHECK (has_role2(owner, 'tester')); DROP POLICY test_update ON test; CREATE POLICY test_update ON test FOR UPDATE USING (has_role2(owner, 'tester') OR has_modify_token(id)); DROP POLICY test_delete ON test; CREATE POLICY test_delete ON test FOR DELETE USING (has_role2(owner, 'tester') OR has_modify_token(id)); </sql> <sql> -- schema_select unchanged DROP POLICY schema_insert ON schema; CREATE POLICY schema_insert ON schema FOR INSERT WITH CHECK (has_role2(owner, 'tester')); DROP POLICY schema_update ON schema; CREATE POLICY schema_update ON schema FOR UPDATE USING (has_role2(owner, 'tester')); DROP POLICY schema_delete ON schema; CREATE POLICY schema_delete ON schema FOR DELETE USING (has_role2(owner, 'tester')); </sql> <sql> DROP POLICY hook_policy ON hook; CREATE POLICY hook_policy ON hook USING (has_role('admin') OR ( (type = 'change/new' OR type = 'run/new') AND ( has_role2((SELECT owner FROM test WHERE test.id = target), 'tester') OR has_modify_token(target) ) )); DROP POLICY hook_write_check ON hook; CREATE POLICY hook_write_check ON hook AS RESTRICTIVE WITH CHECK (exists(SELECT 1 FROM allowedhookprefix ahp WHERE left(url, length(ahp.prefix)) = ahp.prefix)); </sql> <sql> -- se_select unchanged DROP POLICY se_insert ON schemaextractor; CREATE POLICY se_insert ON schemaextractor FOR INSERT WITH CHECK (has_role2((SELECT owner FROM schema WHERE schema.id = schema_id), 'tester')); DROP POLICY se_update ON schemaextractor; CREATE POLICY se_update ON schemaextractor FOR UPDATE USING (has_role2((SELECT owner FROM schema WHERE schema.id = schema_id), 'tester')); DROP POLICY se_delete ON schemaextractor; CREATE POLICY se_delete ON schemaextractor FOR DELETE USING (has_role2((SELECT owner FROM schema WHERE schema.id = schema_id), 'tester')); </sql> <sql> CREATE POLICY view_select ON view FOR SELECT USING (exists( SELECT 1 FROM test WHERE test.id = test_id AND can_view2(test.access, test.owner) ) OR has_read_token(test_id)); DROP POLICY view_insert ON view; CREATE POLICY view_insert ON view FOR INSERT WITH CHECK (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id)); DROP POLICY view_update ON view; CREATE POLICY view_update ON view FOR UPDATE USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id)); DROP POLICY view_delete ON view; CREATE POLICY view_delete ON view FOR DELETE USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id)); </sql> <sql> CREATE POLICY vc_select ON viewcomponent FOR SELECT USING (exists( SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND (can_view2(test.access, test.owner) OR has_read_token(test.id)) )); DROP POLICY vc_insert ON viewcomponent; CREATE POLICY vc_insert ON viewcomponent FOR INSERT WITH CHECK (exists( SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND (has_role2(test.owner, 'tester') OR has_modify_token(test.id)) )); DROP POLICY vc_update ON viewcomponent; CREATE POLICY vc_update ON viewcomponent FOR UPDATE USING (exists( SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND (has_role2(test.owner, 'tester') OR has_modify_token(test.id)) )); DROP POLICY vc_delete ON viewcomponent; CREATE POLICY vc_delete ON viewcomponent FOR DELETE USING (exists( SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND (has_role2(test.owner, 'tester') OR has_modify_token(test.id)) )); </sql> <sql> -- rs_select unchanged DROP POLICY rs_insert ON run_schemas; CREATE POLICY rs_insert ON run_schemas FOR INSERT WITH CHECK ( has_role2((SELECT owner FROM run WHERE run.id = runid), 'uploader') OR has_upload_token(testid) OR has_role2((SELECT owner FROM schema WHERE schema.id = schemaid), 'tester') ); -- This policy is here to prevent rogue records. However since the schema owner has access -- it can claim that certain run is using his schema even if it is not. CREATE POLICY rs_insert_validate ON run_schemas AS RESTRICTIVE FOR INSERT WITH CHECK (exists(SELECT 1 FROM run WHERE run.id = runid AND run.testid = testid)); DROP POLICY rs_update ON run_schemas; -- run_schemas are never updated, just dropped and inserted DROP POLICY rs_delete ON run_schemas; -- both run update and schema update/delete should drop the row; privilege to any of those is sufficient to remove the record -- though deleting a schema with matching rows isn -- horreum.system can delete the run CREATE POLICY rs_delete ON run_schemas FOR DELETE USING ( has_role('horreum.system') OR has_role2((SELECT owner FROM run WHERE run.id = runid), 'tester') OR has_role2((SELECT owner FROM schema WHERE schema.id = schemaid), 'tester') ); </sql> <sql> -- vd_select unchanged DROP POLICY vd_insert ON view_data; CREATE POLICY vd_insert ON view_data FOR INSERT WITH CHECK (exists( SELECT 1 FROM run JOIN test ON test.id = run.testid WHERE run.id = runid AND (has_role2(run.owner, 'uploader') OR has_role2(test.owner, 'tester') OR has_upload_token(test.id)) ) OR exists ( SELECT 1 FROM schema JOIN schemaextractor se ON se.schema_id = schema.id WHERE se.id = ANY(extractor_ids) AND has_role2(schema.owner, 'tester') )); DROP POLICY vd_update ON view_data; -- view_data is never updated DROP POLICY vd_delete ON view_data; CREATE POLICY vd_delete ON view_data FOR DELETE USING (has_role('horreum.system') OR exists( SELECT 1 FROM run JOIN test ON test.id = run.testid WHERE run.id = runid AND (has_role2(run.owner, 'tester') OR has_role2(test.owner, 'tester')) ) OR exists( SELECT 1 FROM schema JOIN schemaextractor se ON se.schema_id = schema.id WHERE se.id = ANY(extractor_ids) AND has_role2(schema.owner, 'tester') )); </sql> <sql> -- change_select unchanged DROP POLICY change_insert ON change; -- only alerting can create changes CREATE POLICY change_insert ON change FOR INSERT WITH CHECK (has_role('horreum.alerting')); DROP POLICY change_update ON change; CREATE POLICY change_update ON change FOR UPDATE USING (has_role2((SELECT owner FROM run WHERE run.id = runid), 'tester')); DROP POLICY change_delete ON change; CREATE POLICY change_delete ON change FOR DELETE USING (has_role2((SELECT owner FROM run WHERE run.id = runid), 'tester')); </sql> <sql> -- The user deletes datapoints when changing test variables DROP POLICY datapoint_delete ON datapoint; CREATE POLICY datapoint_delete ON datapoint FOR DELETE USING (has_role('horreum.alerting') OR has_role2((SELECT owner FROM run WHERE run.id = runid), 'tester')); </sql> <sql> CREATE POLICY variable_access ON variable USING (has_role('horreum.alerting')); CREATE POLICY variable_select ON variable FOR SELECT USING ( exists(SELECT 1 FROM test WHERE test.id = testid AND can_view2(test.access, test.owner)) OR has_read_token(testid) ); DROP POLICY variable_insert ON variable; CREATE POLICY variable_insert ON variable FOR INSERT WITH CHECK ( has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester') OR has_modify_token(testid) ); DROP POLICY variable_update ON variable; CREATE POLICY variable_update ON variable FOR UPDATE USING ( has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester') OR has_modify_token(testid) ); DROP POLICY variable_delete ON variable; CREATE POLICY variable_delete ON variable FOR DELETE USING ( has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester') OR has_modify_token(testid) ); </sql> <sql> CREATE POLICY ss_select ON test_stalenesssettings FOR SELECT USING ( exists(SELECT 1 FROM test WHERE test.id = test_id AND can_view2(test.access, test.owner)) OR has_read_token(test_id) ); DROP POLICY ss_insert ON test_stalenesssettings; CREATE POLICY ss_insert ON test_stalenesssettings FOR INSERT WITH CHECK ( has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id) ); DROP POLICY ss_update ON test_stalenesssettings; CREATE POLICY ss_update ON test_stalenesssettings FOR UPDATE USING ( has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id) ); DROP POLICY ss_delete ON test_stalenesssettings; CREATE POLICY ss_delete ON test_stalenesssettings FOR DELETE USING ( has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id) ); </sql> <sql> DROP POLICY cl_all ON calculationlog; CREATE POLICY cl_all_alerting ON calculationlog USING (has_role('horreum.alerting')); CREATE POLICY cl_all ON calculationlog FOR ALL USING (( (has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester') OR has_modify_token(testid)) ) AND has_role2((SELECT owner FROM run WHERE run.id = runid), 'tester')); </sql> </changeSet> <changeSet id="26" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropColumn tableName="schema" columnName="testpath" /> <dropColumn tableName="schema" columnName="startpath" /> <dropColumn tableName="schema" columnName="stoppath" /> <dropColumn tableName="schema" columnName="descriptionpath" /> </changeSet> <changeSet id="27" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="watch_optout"> <column name="watch_id" type="integer"/> <column name="optout" type="text" /> </createTable> <addForeignKeyConstraint constraintName="fk_watch_optout" baseTableName="watch_optout" baseColumnNames="watch_id" referencedTableName="watch" referencedColumnNames="id" /> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE watch_optout TO "${quarkus.datasource.username}"; ALTER TABLE watch_optout ENABLE ROW LEVEL SECURITY; CREATE POLICY watch_optout_policies ON watch_optout FOR ALL USING (has_role('horreum.alerting') OR has_role(optout)); </sql> </changeSet> <changeSet id="28" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> CREATE POLICY watch_teams_owner ON watch_teams FOR ALL USING (has_role2((SELECT test.owner FROM test JOIN watch ON test.id = watch.testid WHERE watch.id = watch_id), 'tester')); CREATE POLICY watch_users_owner ON watch_users FOR ALL USING (has_role2((SELECT test.owner FROM test JOIN watch ON test.id = watch.testid WHERE watch.id = watch_id), 'tester')); CREATE POLICY watch_optout_owner ON watch_optout FOR ALL USING (has_role2((SELECT test.owner FROM test JOIN watch ON test.id = watch.testid WHERE watch.id = watch_id), 'tester')); </sql> </changeSet> <changeSet id="29" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_func() RETURNS TRIGGER AS $$ BEGIN WITH pairs AS ( SELECT NEW.data->>'$schema' as uri, '$' as prefix UNION SELECT kv.value->>'$schema' as uri, '$."' || replace(kv.key, '"', '\"') || '"' as prefix FROM jsonb_each(NEW.data) AS kv WHERE jsonb_typeof(NEW.data) = 'object' UNION SELECT value->>'$schema' as uri, '$[' || (row_number() over () - 1) || ']' as prefix FROM jsonb_array_elements(NEW.data) AS value WHERE jsonb_typeof(NEW.data) = 'array' ) INSERT INTO run_schemas SELECT NEW.id as runid, NEW.testid, schema.uri, schema.id as schemaid, prefix FROM pairs JOIN schema ON pairs.uri = schema.uri; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION after_schema_update_func() RETURNS TRIGGER AS $$ BEGIN WITH rs AS ( SELECT id, testid, '$' as prefix, run.data->>'$schema' as uri FROM run WHERE run.data->>'$schema' = NEW.uri UNION SELECT id, testid, '$."' || replace(kv.key, '"', '\"') || '"' as prefix, kv.value->>'$schema' as uri FROM run, jsonb_each(run.data) AS kv WHERE jsonb_typeof(data) = 'object' AND kv.value->>'$schema' = NEW.uri UNION SELECT id, testid, '$[' || (row_number() over () - 1) || ']' as prefix, value->>'$schema' as uri FROM run, jsonb_array_elements(data) AS value WHERE jsonb_typeof(data) = 'array' AND value->>'$schema' = NEW.uri ) INSERT INTO run_schemas SELECT rs.id as runid, rs.testid, rs.uri, NEW.id as schemaid, prefix FROM rs; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql>UPDATE run SET id = id;</sql> </changeSet> <changeSet id="30" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createIndex tableName="view_data" indexName="view_data_runid"> <column name="runid" /> </createIndex> </changeSet> <changeSet id="31" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="userinfo"> <column name="defaultteam" type="text" /> </addColumn> <sql> DROP POLICY userinfo_policies ON userinfo; DROP POLICY userinfo_teams_policies ON userinfo_teams; CREATE POLICY userinfo_rw ON userinfo FOR ALL USING (has_role(username)); CREATE POLICY userinfo_teams_rw ON userinfo_teams FOR ALL USING (has_role(username)); CREATE POLICY userinfo_read ON userinfo_teams FOR SELECT USING (has_role('horreum.alerting')); CREATE POLICY userinfo_teams_read ON userinfo_teams FOR SELECT USING (has_role('horreum.alerting')); </sql> </changeSet> <changeSet id="32" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="test"> <column name="tagscalculation" type="text"/> </addColumn> <createProcedure> CREATE OR REPLACE FUNCTION auth_suffix(prefix TEXT) RETURNS text AS $$ BEGIN RETURN concat(prefix, ';', current_setting('horreum.token', true), ';', current_setting('horreum.userroles', true)); END; $$ LANGUAGE plpgsql STABLE; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_insert_run_schemas_func() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('calculate_tags', auth_suffix(NEW.runid::text) ); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_insert_test_func() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('calculate_tags', auth_suffix(run.id::text)) FROM run where run.testid = NEW.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION rt_after_update_extractor_func() RETURNS TRIGGER AS $$ BEGIN PERFORM * FROM (WITH test_tags AS ( SELECT id AS testid, unnest(regexp_split_to_array(tags, ';')) AS accessor FROM test ), matching_runs AS ( SELECT runid FROM schemaextractor se JOIN test_tags ON se.accessor = test_tags.accessor JOIN run_schemas rs ON rs.testid = test_tags.testid AND rs.schemaid = se.schema_id WHERE se.id = NEW.id ) SELECT pg_notify('calculate_tags', auth_suffix(runid::text)) FROM matching_runs) AS notify; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="33" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="tablereportconfig"> <column name="id" type="integer"> <constraints primaryKey="true" nullable="false"/> </column> <column name="title" type="text"> <constraints nullable="false"/> </column> <column name="testid" type="integer"> <constraints nullable="false"/> </column> <column name="filteraccessors" type="text" /> <column name="filterfunction" type="text" /> <column name="categoryaccessors" type="text" /> <column name="categoryfunction" type="text" /> <column name="categoryformatter" type="text" /> <column name="seriesaccessors" type="text"> <constraints nullable="false" /> </column> <column name="seriesfunction" type="text" /> <column name="seriesformatter" type="text" /> <column name="labelaccessors" type="text" /> <column name="labelfunction" type="text" /> <column name="labelformatter" type="text" /> </createTable> <createTable tableName="reportcomponent"> <column name="id" type="integer"> <constraints primaryKey="true" nullable="false" /> </column> <column name="reportconfig_id" type="integer"> <constraints nullable="false" /> </column> <column name="name" type="text"> <constraints nullable="false" /> </column> <column name="component_order" type="integer"> <constraints nullable="false" /> </column> <column name="accessors" type="text"> <constraints nullable="false" /> </column> <column name="function" type="text" /> </createTable> <addForeignKeyConstraint constraintName="reportcomponent_report_fk" baseTableName="reportcomponent" baseColumnNames="reportconfig_id" referencedTableName="tablereportconfig" referencedColumnNames="id" /> <createTable tableName="tablereport"> <column name="id" type="integer"> <constraints primaryKey="true" nullable="false"/> </column> <column name="config_id" type="integer"> <constraints nullable="false" /> </column> <column name="created" type="timestamp"> <constraints nullable="false" /> </column> </createTable> <addForeignKeyConstraint constraintName="tablereport_config_id" baseTableName="tablereport" baseColumnNames="config_id" referencedTableName="tablereportconfig" referencedColumnNames="id" /> <createTable tableName="tablereport_rundata"> <column name="report_id" type="integer"> <constraints nullable="false" /> </column> <column name="runid" type="integer"> <constraints nullable="false" /> </column> <column name="category" type="text"> <constraints nullable="false" /> </column> <column name="series" type="text"> <constraints nullable="false" /> </column> <column name="label" type="text"> <constraints nullable="false" /> </column> <!-- Liquibase would mess up 'double precision[]' --> <column name="values" type="float8[]"> <constraints nullable="false" /> </column> </createTable> <addForeignKeyConstraint constraintName="tablereport_rundata_report_id" baseTableName="tablereport_rundata" baseColumnNames="report_id" referencedTableName="tablereport" referencedColumnNames="id" /> <addUniqueConstraint tableName="tablereport_rundata" columnNames="report_id, runid" /> <sql> GRANT select, insert, delete, update ON TABLE tablereportconfig, reportcomponent, tablereport, tablereport_rundata TO "${quarkus.datasource.username}"; CREATE POLICY tablereportconfig_select ON tablereportconfig FOR SELECT USING (exists(SELECT 1 FROM test WHERE test.id = testid AND can_view2(test.access, test.owner))); CREATE POLICY tablereportconfig_insert ON tablereportconfig FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM test WHERE test.id = testid), 'tester')); CREATE POLICY tablereportconfig_update ON tablereportconfig FOR UPDATE USING (has_role2((SELECT test.owner FROM test WHERE test.id = testid), 'tester')); CREATE POLICY tablereportconfig_delete ON tablereportconfig FOR DELETE USING (has_role2((SELECT test.owner FROM test WHERE test.id = testid), 'tester')); CREATE POLICY reportcomponent_select ON reportcomponent FOR SELECT USING (exists(SELECT 1 FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = reportconfig_id AND can_view2(test.access, test.owner))); CREATE POLICY reportcomponent_insert ON reportcomponent FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = reportconfig_id), 'tester')); CREATE POLICY reportcomponent_update ON reportcomponent FOR UPDATE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = reportconfig_id), 'tester')); CREATE POLICY reportcomponent_delete ON reportcomponent FOR DELETE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = reportconfig_id), 'tester')); CREATE POLICY tablereport_select ON tablereport FOR SELECT USING (exists(SELECT 1 FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = config_id AND can_view2(test.access, test.owner))); CREATE POLICY tablereport_insert ON tablereport FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = config_id), 'tester')); CREATE POLICY tablereport_update ON tablereport FOR UPDATE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = config_id), 'tester')); CREATE POLICY tablereport_delete ON tablereport FOR DELETE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid WHERE trc.id = config_id), 'tester')); CREATE POLICY tablereport_rundata_select ON tablereport_rundata FOR SELECT USING (exists(SELECT run.owner FROM run WHERE run.id = runid AND can_view2(run.access, run.owner))); CREATE POLICY tablereport_rundata_insert ON tablereport_rundata FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON tr.config_id = trc.id WHERE tr.id = report_id), 'tester')); CREATE POLICY tablereport_rundata_update ON tablereport_rundata FOR UPDATE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON tr.config_id = trc.id WHERE tr.id = report_id), 'tester')); CREATE POLICY tablereport_rundata_delete ON tablereport_rundata FOR DELETE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON tr.config_id = trc.id WHERE tr.id = report_id), 'tester')); </sql> </changeSet> <changeSet id="34" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="reportcomment"> <column name="id" type="integer"> <constraints primaryKey="true" /> </column> <column name="report_id" type="integer"> <constraints nullable="false"/> </column> <column name="level" type="integer"> <constraints nullable="false"/> </column> <column name="category" type="text" /> <column name="component_id" type="integer" /> <column name="comment" type="text"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT select, insert, delete, update ON TABLE reportcomment TO "${quarkus.datasource.username}"; CREATE POLICY reportcomment_select ON reportcomment FOR SELECT USING (exists(SELECT 1 FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON trc.id = tr.config_id WHERE tr.id = report_id AND can_view2(test.access, test.owner))); CREATE POLICY reportcomment_insert ON reportcomment FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON trc.id = tr.config_id WHERE tr.id = report_id), 'tester')); CREATE POLICY reportcomment_update ON reportcomment FOR UPDATE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON trc.id = tr.config_id WHERE tr.id = report_id), 'tester')); CREATE POLICY reportcomment_delete ON reportcomment FOR DELETE USING (has_role2((SELECT test.owner FROM test JOIN tablereportconfig trc ON test.id = trc.testid JOIN tablereport tr ON trc.id = tr.config_id WHERE tr.id = report_id), 'tester')); </sql> <addForeignKeyConstraint constraintName="fk_tablereport_comment_report" baseTableName="reportcomment" baseColumnNames="report_id" referencedTableName="tablereport" referencedColumnNames="id" /> <createIndex tableName="reportcomment" indexName="report_ids"> <column name="report_id" /> </createIndex> </changeSet> <changeSet id="35" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropNotNullConstraint tableName="tablereportconfig" columnName="testid"/> <sql> CREATE POLICY tablereportconfig_update_system ON tablereportconfig FOR UPDATE USING (has_role('horreum.system')); </sql> </changeSet> <changeSet id="36" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="run_expectation"> <column name="id" type="bigint"> <constraints primaryKey="true" /> </column> <column name="testid" type="integer"> <constraints nullable="false"/> </column> <column name="tags" type="jsonb" /> <column name="expectedbefore" type="timestamp"> <constraints nullable="false"/> </column> <column name="expectedby" type="text"/> <column name="backlink" type="text"/> </createTable> <sql> GRANT select, insert, delete ON TABLE run_expectation TO "${quarkus.datasource.username}"; CREATE POLICY run_expectation_select ON run_expectation FOR SELECT USING (has_role('horreum.alerting')); CREATE POLICY run_expectation_insert ON run_expectation FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM test WHERE test.id = testid), 'uploader')); CREATE POLICY run_expectation_delete ON run_expectation FOR DELETE USING (has_role('horreum.alerting')); </sql> </changeSet> <!-- Even if the run does not have any schema we should attempt to calculate its tags (and find these to be null) --> <changeSet id="37" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_func() RETURNS TRIGGER AS $$ DECLARE v_schema text; v_schemaid integer; v_has_schema boolean := false; BEGIN FOR v_schema IN (SELECT jsonb_path_query(NEW.data, '$.\$schema'::jsonpath)#>>'{}') LOOP v_schemaid := (SELECT id FROM schema WHERE uri = v_schema); IF v_schemaid IS NOT NULL THEN INSERT INTO run_schemas (runid, testid, prefix, uri, schemaid) VALUES (NEW.id, NEW.testid, '$', v_schema, v_schemaid); v_has_schema := true; END IF; END LOOP; FOR v_schema IN (SELECT jsonb_path_query(NEW.data, '$.*.\$schema'::jsonpath)#>>'{}') LOOP v_schemaid := (SELECT id FROM schema WHERE uri = v_schema); IF v_schemaid IS NOT NULL THEN INSERT INTO run_schemas (runid, testid, prefix, uri, schemaid) VALUES (NEW.id, NEW.testid, '$.*', v_schema, v_schemaid); v_has_schema := true; END IF; END LOOP; IF NOT v_has_schema THEN PERFORM pg_notify('calculate_tags', auth_suffix(NEW.id::text) ); END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="38" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="tablereport_rundata"> <column name="data" type="jsonb" defaultValue="{}"> <constraints nullable="false"/> </column> </addColumn> <sql> UPDATE tablereport_rundata SET data = array_to_json(values); ALTER TABLE tablereport_rundata ALTER COLUMN data DROP DEFAULT; </sql> <dropColumn tableName="tablereport_rundata" columnName="values"/> <renameColumn tableName="tablereport_rundata" oldColumnName="data" newColumnName="values"/> </changeSet> <changeSet id="39" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="banner"> <column name="id" type="integer"> <constraints primaryKey="true" /> </column> <column name="created" type="timestamp"> <constraints nullable="false" /> </column> <column name="active" type="boolean"> <constraints nullable="false" /> </column> <column name="severity" type="text"> <constraints nullable="false" /> </column> <column name="title" type="text"> <constraints nullable="false" /> </column> <column name="message" type="text" /> </createTable> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE banner TO "${quarkus.datasource.username}"; ALTER TABLE banner ENABLE ROW LEVEL SECURITY; CREATE POLICY banner_select ON banner FOR SELECT USING(true); CREATE POLICY banner_insert ON banner FOR INSERT WITH CHECK (has_role('admin')); CREATE POLICY banner_update ON banner FOR UPDATE USING (has_role('admin')); CREATE POLICY banner_delete ON banner FOR DELETE USING (has_role('admin')); </sql> </changeSet> <changeSet id="40" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="tablereportconfig"> <column name="labeldescription" type="text" /> </addColumn> <addColumn tableName="reportcomponent"> <column name="unit" type="text" /> </addColumn> </changeSet> <changeSet id="41" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="calculationlog"> <column name="source" type="text" defaultValue="variables"> <constraints nullable="false"/> </column> </addColumn> <sql> ALTER TABLE calculationlog ALTER COLUMN source DROP DEFAULT; ALTER POLICY run_select ON run USING (can_view(access, owner, token) OR has_role('horreum.system')); ALTER POLICY cl_all ON calculationlog USING (has_role('horreum.alerting') OR has_role('horreum.system') OR (exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) ) AND exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) ))); </sql> </changeSet> <changeSet id="42" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="schemaextractor" > <column name="deprecatedby_id" type="integer" /> <column name="deleted" type="boolean" defaultValue="false"> <constraints nullable="false"/> </column> </addColumn> <addUniqueConstraint tableName="schemaextractor" columnNames="deprecatedby_id"/> <addUniqueConstraint tableName="schemaextractor" columnNames="accessor,schema_id" /> <addForeignKeyConstraint constraintName="singledeprecation" baseTableName="schemaextractor" baseColumnNames="deprecatedby_id" referencedTableName="schemaextractor" referencedColumnNames="id" /> <dropDefaultValue tableName="schemaextractor" columnName="deleted"/> </changeSet> <changeSet id="43" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="test"> <column name="folder" type="text" /> </addColumn> </changeSet> <changeSet id="44" author="rvansa"> <validCheckSum>ANY</validCheckSum> <comment>Duplicate the config for each report</comment> <sql> CREATE TEMPORARY TABLE tempconfig AS SELECT tr.id AS reportid, c.* FROM tablereportconfig c JOIN tablereport tr ON tr.config_id = c.id; CREATE TEMPORARY TABLE tempcomponent AS SELECT tr.id AS reportid, rc.* FROM reportcomponent rc JOIN tablereport tr ON rc.reportconfig_id = tr.config_id; UPDATE tempconfig SET id = nextval('hibernate_sequence'); UPDATE tempcomponent tc SET id = nextval('hibernate_sequence'), reportconfig_id = (SELECT id FROM tempconfig WHERE tempconfig.reportid = tc.reportid); INSERT INTO tablereportconfig SELECT id, title, testid, filteraccessors, filterfunction, categoryaccessors, categoryfunction, categoryformatter, seriesaccessors, seriesfunction, seriesformatter, labelaccessors, labelfunction, labelformatter, labeldescription FROM tempconfig; INSERT INTO reportcomponent SELECT id, reportconfig_id, name, component_order, accessors, function, unit FROM tempcomponent; UPDATE tablereport tr SET config_id = (SELECT id FROM tempconfig WHERE reportid = tr.id); DELETE FROM reportcomponent WHERE id NOT IN (SELECT id from tempcomponent); DELETE FROM tablereportconfig WHERE id NOT IN (SELECT id from tempconfig); DROP TABLE tempconfig; DROP TABLE tempcomponent; </sql> </changeSet> <changeSet id="45" author="rvansa"> <validCheckSum>ANY</validCheckSum> <comment>Remove leaked watches</comment> <sql> DELETE FROM watch_users WHERE watch_id IN (SELECT id FROM watch WHERE testid NOT IN (SELECT id FROM test)); DELETE FROM watch_teams WHERE watch_id IN (SELECT id FROM watch WHERE testid NOT IN (SELECT id FROM test)); DELETE FROM watch_optout WHERE watch_id IN (SELECT id FROM watch WHERE testid NOT IN (SELECT id FROM test)); DELETE FROM watch WHERE testid NOT IN (SELECT id FROM test); </sql> </changeSet> <changeSet id="46" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> DELETE FROM calculationlog WHERE testid NOT IN (SELECT id FROM test) OR runid NOT IN (SELECT id FROM run); </sql> </changeSet> <changeSet id="47" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="regressiondetection"> <column name="id" type="integer"> <constraints primaryKey="true"/> </column> <column name="variable_id" type="integer"> <constraints nullable="false" foreignKeyName="fk_regression_variable_id" referencedTableName="variable" referencedColumnNames="id"/> </column> <column name="model" type="text"> <constraints nullable="false"/> </column> <column name="config" type="jsonb"> <constraints nullable="false"/> </column> </createTable> <sql> INSERT INTO regressiondetection (SELECT nextval('hibernate_sequence') AS id, id AS variable_id, 'relativeDifference' AS model, jsonb_build_object( 'threshold', maxdifferencelastdatapoint, 'minPrevious', minwindow, 'window', 1, 'filter', 'mean' ) as config FROM variable); INSERT INTO regressiondetection (SELECT nextval('hibernate_sequence') AS id, id AS variable_id, 'relativeDifference' AS model, jsonb_build_object( 'threshold', maxdifferencefloatingwindow, 'minPrevious', floatingwindow, 'window', floatingwindow, 'filter', 'mean' ) AS config FROM variable); </sql> <dropColumn tableName="variable" columnName="minwindow"/> <dropColumn tableName="variable" columnName="maxdifferencelastdatapoint"/> <dropColumn tableName="variable" columnName="floatingwindow"/> <dropColumn tableName="variable" columnName="maxdifferencefloatingwindow"/> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE regressiondetection TO "${quarkus.datasource.username}"; ALTER TABLE regressiondetection ENABLE ROW LEVEL SECURITY; CREATE POLICY rd_access ON regressiondetection USING (has_role('horreum.alerting')); CREATE POLICY rd_select ON regressiondetection FOR SELECT USING ( exists(SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (can_view2(test.access, test.owner) OR has_read_token(testid))) ); CREATE POLICY rd_insert ON regressiondetection FOR INSERT WITH CHECK ( exists (SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (has_role2(test.owner, 'tester') OR has_modify_token(variable.testid))) ); CREATE POLICY rd_update ON regressiondetection FOR UPDATE USING ( exists (SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (has_role2(test.owner, 'tester') OR has_modify_token(variable.testid))) ); CREATE POLICY rd_delete ON regressiondetection FOR DELETE USING ( exists (SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (has_role2(test.owner, 'tester') OR has_modify_token(variable.testid))) ); </sql> </changeSet> <changeSet id="48" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER rt_before_update ON test; DROP TRIGGER rt_after_insert ON test; CREATE TRIGGER rt_before_update BEFORE UPDATE OF tags ON test FOR EACH ROW EXECUTE FUNCTION rt_before_update_test_func(); CREATE TRIGGER rt_after_insert AFTER INSERT OR UPDATE OF tags ON test FOR EACH ROW EXECUTE FUNCTION rt_after_insert_test_func(); </sql> </changeSet> <changeSet id="49" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION json_equals(a jsonb, b jsonb) RETURNS boolean AS $$ BEGIN RETURN ((a IS NULL AND b IS NULL) OR (a @> b AND b @> a)); END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="50" author="rvansa"> <validCheckSum>ANY</validCheckSum> <renameTable oldTableName="regressiondetection" newTableName="changedetection" /> </changeSet> <changeSet id="51" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER before_run_update ON run; DROP TRIGGER after_run_update ON run; CREATE TRIGGER before_run_update BEFORE UPDATE OF data ON run FOR EACH ROW EXECUTE FUNCTION before_run_update_func(); CREATE TRIGGER after_run_update AFTER INSERT OR UPDATE OF data ON run FOR EACH ROW EXECUTE FUNCTION after_run_update_func(); </sql> </changeSet> <changeSet id="52" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="transformer"> <column name="id" type="integer"> <constraints primaryKey="true"/> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="description" type="text"/> <column name="schema_id" type="integer"> <constraints foreignKeyName="transformer_schema_id" referencedTableName="schema" referencedColumnNames="id" nullable="false"/> </column> <column name="function" type="text"/> <column name="owner" type="text"> <constraints nullable="false"/> </column> <column name="access" type="integer"> <constraints nullable="false"/> </column> </createTable> <createTable tableName="transformer_extractors"> <column name="transformer_id" type="integer"> <constraints foreignKeyName="transformer_id" referencedTableName="transformer" referencedColumnNames="id" /> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="jsonpath" type="text"> <constraints nullable="false"/> </column> </createTable> <addUniqueConstraint tableName="transformer_extractors" columnNames="transformer_id,name" /> <createIndex tableName="transformer_extractors" indexName="transformer_index"> <column name="transformer_id" /> </createIndex> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE transformer, transformer_extractors TO "${quarkus.datasource.username}"; ALTER TABLE transformer ENABLE ROW LEVEL SECURITY; ALTER TABLE transformer_extractors ENABLE ROW LEVEL SECURITY; CREATE POLICY tf_select ON transformer FOR SELECT USING (can_view2(access, owner) OR has_role('horreum.system')); CREATE POLICY tf_insert ON transformer FOR INSERT WITH CHECK (has_role2(owner, 'tester')); CREATE POLICY tf_update ON transformer FOR UPDATE USING (has_role2(owner, 'tester')); CREATE POLICY tf_delete ON transformer FOR DELETE USING (has_role2(owner, 'tester')); CREATE POLICY te_select ON transformer_extractors FOR SELECT USING (exists(SELECT 1 FROM transformer tf WHERE tf.id = transformer_id AND can_view2(tf.access, tf.owner)) OR has_role('horreum.system')); CREATE POLICY te_insert ON transformer_extractors FOR INSERT WITH CHECK (exists (SELECT 1 FROM transformer tf WHERE tf.id = transformer_id AND has_role2(tf.owner, 'tester'))); CREATE POLICY te_update ON transformer_extractors FOR UPDATE USING (exists (SELECT 1 FROM transformer tf WHERE tf.id = transformer_id AND has_role2(tf.owner, 'tester'))); CREATE POLICY te_delete ON transformer_extractors FOR DELETE USING (exists (SELECT 1 FROM transformer tf WHERE tf.id = transformer_id AND has_role2(tf.owner, 'tester'))); </sql> </changeSet> <changeSet id="53" author="jwhiting"> <validCheckSum>ANY</validCheckSum> <createSequence sequenceName="dataset_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="dataset"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="data" type="jsonb"> <constraints nullable="false" /> </column> <column name="start" type="timestamp without time zone"> <constraints nullable="false" /> </column> <column name="stop" type="timestamp without time zone"> <constraints nullable="false" /> </column> <column name="description" type="text" /> <column name="testid" type="integer"> <constraints nullable="false" /> </column> <column name="runid" type="integer"> <constraints nullable="false" foreignKeyName="fk_dataset_run_id" references="run(id)"/> </column> <column name="owner" type="text"> <constraints nullable="false" /> </column> <column name="access" type="integer"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT ALL ON SEQUENCE dataset_id_seq TO "${quarkus.datasource.username}"; GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE dataset TO "${quarkus.datasource.username}"; ALTER TABLE dataset ENABLE ROW LEVEL SECURITY; CREATE POLICY dataset_select ON dataset FOR SELECT USING (can_view2(access, owner) OR has_role('horreum.system')); CREATE POLICY dataset_insert ON dataset FOR INSERT WITH CHECK (has_role2(owner, 'uploader') OR has_upload_token(testid)); CREATE POLICY dataset_delete ON dataset FOR DELETE USING (has_role('horreum.system')); </sql> </changeSet> <changeSet id="54" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="test_transformers"> <column name="test_id" type="integer"> <constraints foreignKeyName="fk_test_id" referencedTableName="test" referencedColumnNames="id" nullable="false"/> </column> <column name="transformer_id" type="integer"> <constraints foreignKeyName="fk_transformer_id" referencedTableName="transformer" referencedColumnNames="id" nullable="false"/> </column> </createTable> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE test_transformers TO "${quarkus.datasource.username}"; ALTER TABLE test_transformers ENABLE ROW LEVEL SECURITY; CREATE POLICY tt_select ON test_transformers FOR SELECT USING (exists(SELECT 1 FROM test WHERE test.id = test_id AND can_view2(test.access, test.owner)) OR has_role('horreum.system')); CREATE POLICY tt_insert ON test_transformers FOR INSERT WITH CHECK (exists (SELECT 1 FROM test WHERE test.id = test_id AND has_role2(test.owner, 'tester'))); CREATE POLICY tt_update ON test_transformers FOR UPDATE USING (exists (SELECT 1 FROM test WHERE test.id = test_id AND has_role2(test.owner, 'tester'))); CREATE POLICY tt_delete ON test_transformers FOR DELETE USING (exists (SELECT 1 FROM test WHERE test.id = test_id AND has_role2(test.owner, 'tester'))); </sql> </changeSet> <changeSet id="55" author="jwhiting"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_func() RETURNS TRIGGER AS $$ DECLARE v_schema text; v_schemaid integer; v_has_schema boolean := false; BEGIN FOR v_schema IN (SELECT jsonb_path_query(NEW.data, '$.\$schema'::jsonpath)#>>'{}') LOOP v_schemaid := (SELECT id FROM schema WHERE uri = v_schema); IF v_schemaid IS NOT NULL THEN INSERT INTO run_schemas (runid, testid, prefix, uri, schemaid) VALUES (NEW.id, NEW.testid, '$', v_schema, v_schemaid); v_has_schema := true; END IF; END LOOP; FOR v_schema IN (SELECT jsonb_path_query(NEW.data, '$.*.\$schema'::jsonpath)#>>'{}') LOOP v_schemaid := (SELECT id FROM schema WHERE uri = v_schema); IF v_schemaid IS NOT NULL THEN INSERT INTO run_schemas (runid, testid, prefix, uri, schemaid) VALUES (NEW.id, NEW.testid, '$.*', v_schema, v_schemaid); v_has_schema := true; END IF; END LOOP; IF NOT v_has_schema THEN PERFORM pg_notify('calculate_tags', auth_suffix(NEW.id::text) ); END IF; DELETE FROM dataset WHERE runid = OLD.id; PERFORM pg_notify('calculate_datasets', auth_suffix(NEW.id::text) ); RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION after_run_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset WHERE runid = OLD.id; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER after_run_delete AFTER DELETE ON run FOR EACH ROW EXECUTE FUNCTION after_run_delete_func(); INSERT INTO dataset (id, data, start, stop, description, testid, runid, owner, access) SELECT nextval('dataset_id_seq') as id, data, start, stop, description, testid, id as runid, owner, access FROM run; </sql> <createProcedure> CREATE OR REPLACE FUNCTION before_run_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE runid = OLD.id; DELETE FROM dataset WHERE runid = OLD.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="56" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="dataset"> <column name="ordinal" type="integer" defaultValue="0"> <constraints nullable="false" /> </column> </addColumn> <addUniqueConstraint tableName="dataset" columnNames="runid,ordinal"/> <dropDefaultValue tableName="dataset" columnName="ordinal"/> </changeSet> <changeSet id="57" author="rvansa"> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_non_data_func() RETURNS TRIGGER AS $$ BEGIN UPDATE dataset SET owner = NEW.owner, access = NEW.access, description = NEW.description WHERE runid = NEW.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER after_run_update_non_data AFTER UPDATE OF owner, access, description ON run FOR EACH ROW EXECUTE FUNCTION after_run_update_non_data_func(); <!-- It would be nice to prevent updates of other fields but we don't do that right now --> CREATE POLICY dataset_update ON dataset FOR UPDATE USING (has_role2(owner, 'tester')); </sql> </changeSet> <changeSet id="58" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="transformer_extractors"> <column name="isarray" type="boolean" defaultValue="false"> <constraints nullable="false"/> </column> </addColumn> <dropDefaultValue tableName="transformer_extractors" columnName="isarray"/> <addColumn tableName="transformer"> <column name="targetschemauri" type="text" /> </addColumn> </changeSet> <changeSet id="59" author="jwhiting,rvansa"> <validCheckSum>ANY</validCheckSum> <createSequence sequenceName="label_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="label"> <column name="id" type="integer"> <constraints primaryKey="true" nullable="false" /> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="schema_id" type="integer"> <constraints nullable="false" referencedTableName="schema" referencedColumnNames="id" foreignKeyName="label_schema_id"/> </column> <column name="function" type="text"/> <column name="filtering" type="boolean" defaultValue="true"> <constraints nullable="false"/> </column> <column name="metrics" type="boolean" defaultValue="true"> <constraints nullable="false"/> </column> <column name="owner" type="text"> <constraints nullable="false"/> </column> <column name="access" type="integer"> <constraints nullable="false"/> </column> </createTable> <createTable tableName="label_extractors"> <column name="label_id" type="integer"> <constraints foreignKeyName="label_id" referencedTableName="label" referencedColumnNames="id" /> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="jsonpath" type="text"> <constraints nullable="false"/> </column> <column name="isarray" type="boolean"> <constraints nullable="false" /> </column> </createTable> <addUniqueConstraint tableName="label_extractors" columnNames="label_id,name" /> <createIndex tableName="label_extractors" indexName="label_index"> <column name="label_id" /> </createIndex> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE label, label_extractors TO "${quarkus.datasource.username}"; GRANT ALL ON SEQUENCE label_id_seq TO "${quarkus.datasource.username}"; ALTER TABLE label ENABLE ROW LEVEL SECURITY; ALTER TABLE label_extractors ENABLE ROW LEVEL SECURITY; CREATE POLICY l_select ON label FOR SELECT USING (can_view2(access, owner) OR has_role('horreum.system')); CREATE POLICY l_insert ON label FOR INSERT WITH CHECK (has_role2(owner, 'tester')); CREATE POLICY l_update ON label FOR UPDATE USING (has_role2(owner, 'tester')); CREATE POLICY l_delete ON label FOR DELETE USING (has_role2(owner, 'tester')); CREATE POLICY le_select ON label_extractors FOR SELECT USING (exists(SELECT 1 FROM label WHERE label.id = label_id AND can_view2(label.access, label.owner)) OR has_role('horreum.system')); CREATE POLICY le_insert ON label_extractors FOR INSERT WITH CHECK (exists (SELECT 1 FROM label WHERE label.id = label_id AND has_role2(label.owner, 'tester'))); CREATE POLICY le_update ON label_extractors FOR UPDATE USING (exists(SELECT 1 FROM label WHERE label.id = label_id AND has_role2(label.owner, 'tester'))); CREATE POLICY le_delete ON label_extractors FOR DELETE USING (exists(SELECT 1 FROM label WHERE label.id = label_id AND has_role2(label.owner, 'tester'))); </sql> </changeSet> <changeSet id="60" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="label_values"> <column name="dataset_id" type="integer"> <!-- No FK constraint: the row will be removed via trigger --> <constraints nullable="false"/> </column> <column name="label_id" type="integer"> <constraints nullable="false"/> </column> <!-- null value is not represented as JSON null but as null column value --> <column name="value" type="jsonb" /> </createTable> <addUniqueConstraint tableName="label_values" columnNames="dataset_id,label_id" /> <createIndex tableName="label_values" indexName="by_dataset"> <column name="dataset_id" /> </createIndex> <createTable tableName="dataset_schemas"> <column name="dataset_id" type="integer"> <constraints nullable="false"/> </column> <column name="schema_id" type="integer"> <constraints nullable="false"/> </column> <column name="uri" type="text"> <constraints nullable="false"/> </column> <column name="index" type="integer"> <constraints nullable="false"/> </column> </createTable> <!-- This is an intermediate table for combining all updates to a label/label extractors into one notification --> <createTable tableName="label_recalc_queue"> <column name="schema_id" type="integer"> <constraints nullable="false"/> </column> <column name="label_id" type="integer"> <constraints nullable="false" unique="true" uniqueConstraintName="unique_label"/> </column> </createTable> <createProcedure> CREATE OR REPLACE FUNCTION lv_before_dataset_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM label_values WHERE dataset_id = OLD.id; DELETE FROM dataset_schemas WHERE dataset_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_before_label_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM label_values WHERE label_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_before_label_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM label_values WHERE label_id = OLD.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_before_le_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM label_values WHERE label_id = OLD.label_id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_before_le_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM label_values WHERE label_id = OLD.label_id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION ds_after_dataset_insert_func() RETURNS TRIGGER AS $$ BEGIN <!-- This should not happen after #148 --> IF jsonb_path_query(NEW.data, '$.type() != "array"') THEN RETURN NEW; END IF; WITH uris AS ( SELECT jsonb_array_elements(NEW.data)->>'$schema' AS uri ), indexed as ( SELECT uri, row_number() over () - 1 as index FROM uris ) INSERT INTO dataset_schemas(dataset_id, uri, index, schema_id) SELECT NEW.id as dataset_id, indexed.uri, indexed.index, schema.id FROM indexed JOIN schema ON schema.uri = indexed.uri; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_after_label_update_func() RETURNS TRIGGER AS $$ BEGIN INSERT INTO label_recalc_queue(schema_id, label_id) VALUES (NEW.schema_id, NEW.id) ON CONFLICT (label_id) DO NOTHING; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_after_le_update_func() RETURNS TRIGGER AS $$ DECLARE v_schema integer; BEGIN SELECT schema_id INTO v_schema FROM label WHERE id = NEW.label_id; IF v_schema IS NOT NULL THEN INSERT INTO label_recalc_queue(schema_id, label_id) VALUES (v_schema, NEW.label_id) ON CONFLICT (label_id) DO NOTHING; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION lv_after_le_delete_func() RETURNS TRIGGER AS $$ DECLARE v_schema integer; BEGIN SELECT schema_id INTO v_schema FROM label WHERE id = NEW.label_id; IF v_schema IS NOT NULL THEN INSERT INTO label_recalc_queue(schema_id, label_id) VALUES (v_schema, OLD.label_id) ON CONFLICT (label_id) DO NOTHING; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> <!-- Note: labels are deleted before schema, so these clear label_values --> CREATE OR REPLACE FUNCTION before_schema_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE schemaid = OLD.id; DELETE FROM dataset_schemas WHERE schema_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION recalc_label_values() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('calculate_labels', dataset_id::text || ';' || NEW.label_id) FROM dataset_schemas WHERE dataset_schemas.schema_id = NEW.schema_id; DELETE FROM label_recalc_queue WHERE label_id = NEW.label_id; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> <!-- dataset.data should not be updated --> CREATE TRIGGER ds_after_insert AFTER INSERT ON dataset FOR EACH ROW EXECUTE FUNCTION ds_after_dataset_insert_func(); CREATE TRIGGER lv_before_delete BEFORE DELETE ON dataset FOR EACH ROW EXECUTE FUNCTION lv_before_dataset_delete_func(); CREATE TRIGGER lv_before_delete BEFORE DELETE ON label FOR EACH ROW EXECUTE FUNCTION lv_before_label_delete_func(); CREATE TRIGGER lv_before_update BEFORE UPDATE OF function ON label FOR EACH ROW EXECUTE FUNCTION lv_before_label_update_func(); CREATE TRIGGER lv_before_delete BEFORE DELETE OR UPDATE ON label_extractors FOR EACH ROW EXECUTE FUNCTION lv_before_le_delete_func(); CREATE TRIGGER lv_before_update BEFORE UPDATE ON label_extractors FOR EACH ROW EXECUTE FUNCTION lv_before_le_update_func(); <!-- These triggers need to be deferred until the transaction completes because we likely execute update to both label extractor(s) and function --> CREATE CONSTRAINT TRIGGER lv_after_update AFTER INSERT OR UPDATE OF function ON label DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION lv_after_label_update_func(); CREATE CONSTRAINT TRIGGER lv_after_update AFTER INSERT OR UPDATE ON label_extractors DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION lv_after_le_update_func(); CREATE CONSTRAINT TRIGGER lv_after_delete AFTER DELETE ON label_extractors DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION lv_after_le_delete_func(); CREATE CONSTRAINT TRIGGER recalc_labels AFTER INSERT ON label_recalc_queue DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION recalc_label_values(); GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE label_values, dataset_schemas, label_recalc_queue TO "${quarkus.datasource.username}"; ALTER TABLE label_values ENABLE ROW LEVEL SECURITY; ALTER TABLE dataset_schemas ENABLE ROW LEVEL SECURITY; CREATE POLICY ds_select ON dataset_schemas FOR SELECT USING (exists(SELECT 1 FROM dataset WHERE id = dataset_id AND can_view2(access, owner))); CREATE POLICY ds_insert ON dataset_schemas FOR INSERT WITH CHECK (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND (has_role2(owner, 'uploader') OR has_role2(owner, 'tester')))); CREATE POLICY ds_delete ON dataset_schemas FOR DELETE USING (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND has_role2(owner, 'tester'))); CREATE POLICY lv_select ON label_values FOR SELECT USING (exists(SELECT 1 FROM dataset WHERE dataset.id = dataset_id AND can_view2(access, owner))); CREATE POLICY lv_insert ON label_values FOR INSERT WITH CHECK (has_role('horreum.system')); CREATE POLICY lv_delete ON label_values FOR DELETE USING (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE dataset.id = dataset_id AND has_role2(owner, 'tester'))); ALTER POLICY dataset_delete ON dataset USING (has_role('horreum.system') OR has_role2(owner, 'tester')); </sql> </changeSet> <changeSet id="61" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> ALTER POLICY dataset_insert ON dataset WITH CHECK (has_role('horreum.system') OR (has_role2(owner, 'tester') AND exists((SELECT 1 FROM run WHERE run.id = runid AND has_role2(run.owner, 'tester'))))); </sql> </changeSet> <changeSet id="62" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> WITH vca AS ( SELECT id, unnest(string_to_array(accessors, ';')) AS accessor, headername, render from viewcomponent ), vca2 AS ( SELECT id, replace(vca.accessor, '[]', '') as accessor, vca.accessor like '%[]' as isarray, headername, render FROM vca ), le AS ( SELECT row_number() OVER () AS label_id, vca2.headername, schema_id, vca2.render AS function, array_agg(vca2.accessor) AS accessors, array_agg(isarray) AS isarrays, array_agg('$' || jsonpath) AS jsonpaths, owner, access FROM vca2 JOIN schemaextractor se ON vca2.accessor = se.accessor JOIN schema ON schema.id = schema_id GROUP BY schema_id, vca2.id, vca2.headername, function, owner, access ), label_insert AS ( INSERT INTO label (id, name, schema_id, function, filtering, metrics, owner, access) SELECT label_id, headername, schema_id, function, false, true, owner, access FROM le ) INSERT INTO label_extractors (label_id, name, jsonpath, isarray) SELECT label_id, unnest(accessors), unnest(jsonpaths), unnest(isarrays) FROM le; WITH var AS ( SELECT id, unnest(string_to_array(accessors, ';')) AS accessor, name, calculation from variable ), var2 AS ( SELECT id, replace(var.accessor, '[]', '') as accessor, var.accessor like '%[]' as isarray, name, calculation FROM var ), le AS ( SELECT row_number() OVER () + (SELECT count(*) FROM label) AS label_id, var2.name, schema_id, var2.calculation AS function, array_agg(var2.accessor) AS accessors, array_agg(isarray) AS isarrays, array_agg('$' || jsonpath) AS jsonpaths, owner, access FROM var2 JOIN schemaextractor se ON var2.accessor = se.accessor JOIN schema ON schema.id = schema_id GROUP BY schema_id, var2.id, var2.name, function, owner, access ), label_insert AS ( INSERT INTO label (id, name, schema_id, function, filtering, metrics, owner, access) SELECT label_id, name, schema_id, function, false, true, owner, access FROM le ) INSERT INTO label_extractors (label_id, name, jsonpath, isarray) SELECT label_id, unnest(accessors), unnest(jsonpaths), unnest(isarrays) FROM le; WITH rc AS ( SELECT id, unnest(string_to_array(accessors, ';')) AS accessor, name, function from reportcomponent ), rc2 AS ( SELECT id, replace(rc.accessor, '[]', '') as accessor, rc.accessor like '%[]' as isarray, name, function FROM rc ), le AS ( SELECT row_number() OVER () + (SELECT count(*) FROM label) AS label_id, rc2.name, schema_id, rc2.function, array_agg(rc2.accessor) AS accessors, array_agg(isarray) AS isarrays, array_agg('$' || jsonpath) AS jsonpaths, owner, access FROM rc2 JOIN schemaextractor se ON rc2.accessor = se.accessor JOIN schema ON schema.id = schema_id GROUP BY schema_id, rc2.id, rc2.name, function, owner, access ), label_insert AS ( INSERT INTO label (id, name, schema_id, function, filtering, metrics, owner, access) SELECT label_id, name, schema_id, function, false, true, owner, access FROM le ) INSERT INTO label_extractors (label_id, name, jsonpath, isarray) SELECT label_id, unnest(accessors), unnest(jsonpaths), unnest(isarrays) FROM le; WITH tt AS ( SELECT id, unnest(string_to_array(tags, ';')) AS accessor, name, tagscalculation as function from test ), tt2 AS ( SELECT id, replace(tt.accessor, '[]', '') as accessor, tt.accessor like '%[]' as isarray, name, function FROM tt ), le AS ( SELECT row_number() OVER () + (SELECT count(*) FROM label) AS label_id, tt2.name || '_tags' AS name, schema_id, tt2.function, array_agg(tt2.accessor) AS accessors, array_agg(isarray) AS isarrays, array_agg('$' || jsonpath) AS jsonpaths, owner, access FROM tt2 JOIN schemaextractor se ON tt2.accessor = se.accessor JOIN schema ON schema.id = schema_id GROUP BY schema_id, tt2.id, tt2.name, function, owner, access ), label_insert AS ( INSERT INTO label (id, name, schema_id, function, filtering, metrics, owner, access) SELECT label_id, name, schema_id, function, true, false, owner, access FROM le ) INSERT INTO label_extractors (label_id, name, jsonpath, isarray) SELECT label_id, unnest(accessors), unnest(jsonpaths), unnest(isarrays) FROM le; WITH acs AS ( SELECT DISTINCT unnest(string_to_array(filteraccessors, ';') || string_to_array(categoryaccessors, ';') || string_to_array(labelaccessors, ';')) AS accessor FROM tablereportconfig ), le AS ( SELECT row_number() over () + (SELECT count(*) FROM label) AS label_id, acs.accessor, array_agg('$' || jsonpath) as jsonpaths, schema_id, owner, access FROM acs JOIN schemaextractor se ON acs.accessor = se.accessor JOIN schema ON schema.id = schema_id GROUP BY schema_id, acs.accessor, owner, access ), label_insert AS ( INSERT INTO label (id, name, schema_id, function, filtering, metrics, owner, access) SELECT label_id, accessor, schema_id, NULL, true, false, owner, access FROM le ) INSERT INTO label_extractors (label_id, name, jsonpath, isarray) SELECT label_id, accessor, unnest(jsonpaths), false FROM le; <!-- Remove duplicate labels --> WITH ids AS ( SELECT l1.id FROM label l1 JOIN label l2 ON l1.name = l2.name AND l1.schema_id = l2.schema_id WHERE l1.id > l2.id ), drop_extractors AS ( DELETE FROM label_extractors USING ids WHERE label_id = ids.id ) DELETE FROM label USING ids WHERE label.id = ids.id; </sql> </changeSet> <!-- We need a new transaction to alter the table due to triggers --> <changeSet id="63" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addUniqueConstraint tableName="label" columnNames="name,schema_id"/> </changeSet> <changeSet id="64" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="viewcomponent"> <column name="labels" type="jsonb" /> </addColumn> <sql> UPDATE viewcomponent SET labels = jsonb_build_array(headername); DROP TRIGGER vd_before_delete ON run_schemas; DROP TRIGGER vd_after_insert ON run_schemas; DROP TRIGGER vd_before_update ON schemaextractor; DROP TRIGGER vd_before_delete ON schemaextractor; DROP TRIGGER vd_after_update ON schemaextractor; DROP TRIGGER vd_before_delete ON viewcomponent; DROP TRIGGER vd_before_update ON viewcomponent; DROP TRIGGER vd_after_update ON viewcomponent; DROP FUNCTION vd_before_delete_run_func; DROP FUNCTION vd_after_insert_run_func; DROP FUNCTION vd_before_update_extractor_func; DROP FUNCTION vd_before_delete_extractor_func; DROP FUNCTION vd_after_update_extractor_func; DROP FUNCTION vd_before_delete_vc_func; DROP FUNCTION vd_before_update_vc_func; DROP FUNCTION vd_after_update_vc_func; </sql> <addNotNullConstraint tableName="viewcomponent" columnName="labels"/> <dropColumn tableName="viewcomponent" columnName="accessors"/> <dropTable tableName="view_data"/> </changeSet> <changeSet id="65" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="dataset_view"> <column name="dataset_id" type="integer" /> <column name="view_id" type="integer" /> <column name="label_ids" type="int[]" /> <column name="value" type="jsonb" /> </createTable> <addPrimaryKey tableName="dataset_view" columnNames="dataset_id,view_id"/> <createTable tableName="view_recalc_queue"> <column name="dataset_id" type="integer"> <constraints nullable="false" unique="true"/> </column> <column name="view_id" type="integer" /> <column name="roles" type="text"> <constraints nullable="false"/> </column> </createTable> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE dataset_view TO "${quarkus.datasource.username}"; ALTER TABLE dataset_view ENABLE ROW LEVEL SECURITY; CREATE POLICY dsv_select ON dataset_view FOR SELECT USING (exists(SELECT 1 FROM dataset WHERE id = dataset_id AND can_view2(access, owner))); CREATE POLICY dsv_insert ON dataset_view FOR INSERT WITH CHECK (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND has_role2(owner, 'tester'))); CREATE POLICY dsv_delete ON dataset_view FOR DELETE USING (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND has_role2(owner, 'tester'))); <!-- As the view is calculated in the deferred trigger the horreum.userroles is already unset; we need to persist the permissions in the roles column but this must not be readable by others --> GRANT INSERT, DELETE ON TABLE view_recalc_queue TO "${quarkus.datasource.username}"; GRANT SELECT (dataset_id, view_id) ON TABLE view_recalc_queue TO "${quarkus.datasource.username}"; </sql> <createProcedure> CREATE OR REPLACE FUNCTION dsv_after_lv_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset_view WHERE dataset_id = OLD.dataset_id; INSERT INTO view_recalc_queue(dataset_id, roles) VALUES (OLD.dataset_id, current_setting('horreum.userroles', true)) ON CONFLICT DO NOTHING; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION dsv_after_lv_insert_func() RETURNS TRIGGER AS $$ BEGIN INSERT INTO view_recalc_queue(dataset_id, roles) VALUES (NEW.dataset_id, current_setting('horreum.userroles', true)) ON CONFLICT DO NOTHING; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION dsv_after_vc_delete_func() RETURNS TRIGGER AS $$ BEGIN WITH ds AS ( DELETE FROM dataset_view WHERE view_id = OLD.view_id RETURNING dataset_id, view_id ) INSERT INTO view_recalc_queue(dataset_id, view_id, roles) SELECT dataset_id, view_id, current_setting('horreum.userroles', true) FROM ds ON CONFLICT DO NOTHING; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION dsv_after_vc_update_func() RETURNS TRIGGER AS $$ BEGIN WITH ds AS ( DELETE FROM dataset_view WHERE view_id = OLD.view_id OR view_id = NEW.view_id RETURNING dataset_id, view_id ) INSERT INTO view_recalc_queue(dataset_id, view_id, roles) SELECT dataset_id, view_id, current_setting('horreum.userroles', true) FROM ds ON CONFLICT DO NOTHING; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> <!-- This is invoked as deferrd trigger and horreum.userroles is already unset - we can pass it to the trigger using a non-readable column --> CREATE OR REPLACE FUNCTION recalc_dataset_view() RETURNS TRIGGER AS $$ BEGIN PERFORM set_config('horreum.userroles', NEW.roles, true); WITH view_agg AS ( SELECT vc.view_id, vc.id as vcid, array_agg(DISTINCT label.id) as label_ids, jsonb_object_agg(label.name, lv.value) as value FROM dataset_schemas ds JOIN label ON label.schema_id = ds.schema_id JOIN viewcomponent vc ON vc.labels ? label.name JOIN label_values lv ON lv.label_id = label.id WHERE ds.dataset_id = NEW.dataset_id AND (NEW.view_id IS NULL OR NEW.view_id = vc.view_id) GROUP BY vc.view_id, vcid ) INSERT INTO dataset_view (dataset_id, view_id, label_ids, value) SELECT NEW.dataset_id, view_id, array_agg(DISTINCT label_id), jsonb_object_agg(vcid, value) FROM view_agg, unnest(label_ids) as label_id GROUP BY view_id; DELETE FROM view_recalc_queue WHERE dataset_id = NEW.dataset_id AND (view_id IS NULL OR view_id = NEW.view_id); RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER dsv_after_delete AFTER DELETE ON label_values FOR EACH ROW EXECUTE FUNCTION dsv_after_lv_delete_func(); CREATE TRIGGER dsv_after_insert AFTER INSERT ON label_values FOR EACH ROW EXECUTE FUNCTION dsv_after_lv_insert_func(); CREATE TRIGGER dsv_after_delete AFTER DELETE ON viewcomponent FOR EACH ROW EXECUTE FUNCTION dsv_after_vc_delete_func(); CREATE TRIGGER dsv_after_update AFTER INSERT OR UPDATE OF labels ON viewcomponent FOR EACH ROW EXECUTE FUNCTION dsv_after_vc_update_func(); CREATE CONSTRAINT TRIGGER recalc_dataset_view AFTER INSERT ON view_recalc_queue DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION recalc_dataset_view(); </sql> </changeSet> <changeSet id="66" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="test"> <column name="fingerprint_labels" type="jsonb" /> </addColumn> <addColumn tableName="test"> <column name="fingerprint_filter" type="text" /> </addColumn> <dropColumn tableName="variable" columnName="accessors"/> <addColumn tableName="variable"> <column name="labels" type="jsonb" defaultValue="[]"> <constraints nullable="false"/> </column> </addColumn> <sql> UPDATE variable SET labels = jsonb_build_array(name); </sql> <dropDefaultValue tableName="variable" columnName="labels"/> </changeSet> <changeSet id="67" author="rvansa"> <validCheckSum>ANY</validCheckSum> <!-- We will need to recompute them all anyway --> <sql> DELETE FROM datapoint; DELETE FROM change; DELETE FROM calculationlog; UPDATE test SET fingerprint_labels = jsonb_build_array(name || '_tags'); </sql> <renameColumn tableName="datapoint" oldColumnName="runid" newColumnName="dataset_id"/> <renameColumn tableName="change" oldColumnName="runid" newColumnName="dataset_id"/> <renameColumn tableName="calculationlog" oldColumnName="runid" newColumnName="dataset_id"/> <renameTable oldTableName="calculationlog" newTableName="datasetlog" /> <sql> ALTER POLICY cl_all ON datasetlog USING (has_role('horreum.system') OR (exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) ) AND exists( SELECT 1 FROM dataset WHERE id = dataset_id AND has_role(owner) ) )); ALTER POLICY datapoint_select ON datapoint USING (has_role('horreum.system') OR exists( SELECT 1 FROM dataset WHERE dataset.id = dataset_id AND can_view2(dataset.access, dataset.owner) )); ALTER POLICY datapoint_insert ON datapoint WITH CHECK (has_role('horreum.system')); ALTER POLICY datapoint_update ON datapoint USING (has_role('horreum.system')); ALTER POLICY datapoint_delete ON datapoint USING (has_role('horreum.system') OR has_role2((SELECT owner FROM dataset WHERE dataset.id = dataset_id), 'tester')); ALTER POLICY change_select ON change USING (has_role('horreum.system') OR exists( SELECT 1 FROM dataset WHERE dataset.id = dataset_id AND can_view2(dataset.access, dataset.owner) )); ALTER POLICY change_insert ON change WITH CHECK (has_role('horreum.system')); ALTER POLICY change_update ON change USING (has_role2((SELECT owner FROM dataset WHERE dataset.id = dataset_id), 'tester')); ALTER POLICY change_delete ON change USING (has_role('horreum.system') OR has_role2((SELECT owner FROM dataset WHERE dataset.id = dataset_id), 'tester')); ALTER POLICY variable_access ON variable USING (has_role('horreum.system') OR has_role('horreum.alerting')); ALTER POLICY rd_access ON changedetection USING (has_role('horreum.system') OR has_role('horreum.alerting')); ALTER POLICY test_select ON test USING (can_view2(access, owner) OR has_read_token(id) OR has_role('horreum.system')); ALTER POLICY lv_select ON label_values USING (exists(SELECT 1 FROM dataset WHERE dataset.id = dataset_id AND can_view2(access, owner)) OR has_role('horreum.system')); </sql> <createTable tableName="fingerprint"> <column name="dataset_id" type="integer"> <constraints primaryKey="true" nullable="false"/> </column> <column name="fingerprint" type="jsonb" /> </createTable> <createTable tableName="fingerprint_recalc_queue"> <column name="dataset_id" type="integer"> <constraints nullable="false" unique="true"/> </column> <column name="roles" type="text"> <constraints nullable="false"/> </column> </createTable> <createProcedure> CREATE OR REPLACE FUNCTION fp_after_lv_delete_func() RETURNS TRIGGER AS $$ DECLARE labels jsonb; BEGIN labels := (SELECT fingerprint_labels FROM test JOIN dataset ON test.id = dataset.testid WHERE dataset.id = OLD.dataset_id); IF labels ? (SELECT name FROM label WHERE id = OLD.label_id) THEN DELETE FROM fingerprint WHERE dataset_id = OLD.dataset_id; INSERT INTO fingerprint_recalc_queue(dataset_id, roles) VALUES (OLD.dataset_id, current_setting('horreum.userroles', true)) ON CONFLICT DO NOTHING; END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION fp_after_lv_insert_func() RETURNS TRIGGER AS $$ DECLARE labels jsonb; BEGIN labels := (SELECT fingerprint_labels FROM test JOIN dataset ON test.id = dataset.testid WHERE dataset.id = NEW.dataset_id); IF labels ? (SELECT name FROM label WHERE id = NEW.label_id) THEN DELETE FROM fingerprint WHERE dataset_id = NEW.dataset_id; INSERT INTO fingerprint_recalc_queue(dataset_id, roles) VALUES (NEW.dataset_id, current_setting('horreum.userroles', true)) ON CONFLICT DO NOTHING; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION fp_after_test_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM fingerprint USING dataset WHERE dataset.testid = NEW.id AND dataset.id = dataset_id; WITH ds AS ( SELECT id FROM dataset WHERE testid = NEW.id ) INSERT INTO fingerprint_recalc_queue(dataset_id, roles) SELECT id, current_setting('horreum.userroles', true) FROM ds ON CONFLICT DO NOTHING; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> <!-- This is invoked as deferrd trigger and horreum.userroles is already unset - we can pass it to the trigger using a non-readable column --> CREATE OR REPLACE FUNCTION recalc_fingerprint() RETURNS TRIGGER AS $$ BEGIN PERFORM set_config('horreum.userroles', NEW.roles, true); WITH fps AS ( SELECT NEW.dataset_id, jsonb_object_agg(label.name, lv.value) AS fingerprint, count(DISTINCT label.name) > 1 AS multi FROM test LEFT JOIN label ON test.fingerprint_labels ? label.name LEFT JOIN label_values lv ON label.id = lv.label_id WHERE test.id = (SELECT testid FROM dataset WHERE id = NEW.dataset_id) AND lv.dataset_id = NEW.dataset_id ) INSERT INTO fingerprint(dataset_id, fingerprint) SELECT dataset_id, (CASE WHEN fps.multi THEN fingerprint ELSE (SELECT value FROM jsonb_each(fingerprint)) END) FROM fps WHERE fingerprint IS NOT NULL; DELETE FROM fingerprint_recalc_queue WHERE dataset_id = NEW.dataset_id; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE fingerprint TO "${quarkus.datasource.username}"; ALTER TABLE fingerprint ENABLE ROW LEVEL SECURITY; CREATE POLICY fp_select ON fingerprint FOR SELECT USING (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND can_view2(access, owner))); CREATE POLICY fp_insert ON fingerprint FOR INSERT WITH CHECK (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND has_role2(owner, 'tester'))); CREATE POLICY fp_delete ON fingerprint FOR DELETE USING (has_role('horreum.system') OR exists(SELECT 1 FROM dataset WHERE id = dataset_id AND has_role2(owner, 'tester'))); <!-- As the view is calculated in the deferred trigger the horreum.userroles is already unset; we need to persist the permissions in the roles column but this must not be readable by others --> GRANT INSERT, DELETE ON TABLE fingerprint_recalc_queue TO "${quarkus.datasource.username}"; GRANT SELECT (dataset_id) ON TABLE fingerprint_recalc_queue TO "${quarkus.datasource.username}"; CREATE TRIGGER fp_after_delete AFTER DELETE ON label_values FOR EACH ROW EXECUTE FUNCTION fp_after_lv_delete_func(); CREATE TRIGGER fp_after_insert AFTER INSERT ON label_values FOR EACH ROW EXECUTE FUNCTION fp_after_lv_insert_func(); CREATE TRIGGER fp_after_update AFTER UPDATE OF fingerprint_labels ON test FOR EACH ROW EXECUTE FUNCTION fp_after_test_update_func(); CREATE CONSTRAINT TRIGGER recalc_fingerprint AFTER INSERT ON fingerprint_recalc_queue DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION recalc_fingerprint(); </sql> <!-- Because the ? operator won't be parsed for native query correctly--> <createProcedure> CREATE OR REPLACE FUNCTION json_contains(container jsonb, element text) RETURNS boolean AS $$ BEGIN RETURN container ? element; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="68" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="tablereportconfig"> <column name="filterlabels" type="jsonb" /> <column name="categorylabels" type="jsonb" /> <column name="serieslabels" type="jsonb" /> <column name="scalelabels" type="jsonb" /> </addColumn> <sql> UPDATE tablereportconfig SET filterlabels = to_jsonb(string_to_array(filteraccessors, ';')), categorylabels = to_jsonb(string_to_array(categoryaccessors, ';')), serieslabels = to_jsonb(string_to_array(seriesaccessors, ';')), scalelabels = to_jsonb(string_to_array(labelaccessors, ';')); </sql> <addNotNullConstraint tableName="tablereportconfig" columnName="serieslabels" /> <dropColumn tableName="tablereportconfig" columnName="filteraccessors"/> <dropColumn tableName="tablereportconfig" columnName="categoryaccessors"/> <dropColumn tableName="tablereportconfig" columnName="seriesaccessors"/> <dropColumn tableName="tablereportconfig" columnName="labelaccessors"/> <renameColumn tableName="tablereportconfig" oldColumnName="labelfunction" newColumnName="scalefunction"/> <renameColumn tableName="tablereportconfig" oldColumnName="labelformatter" newColumnName="scaleformatter"/> <renameColumn tableName="tablereportconfig" oldColumnName="labeldescription" newColumnName="scaledescription"/> <addColumn tableName="reportcomponent"> <column name="labels" type="jsonb"/> </addColumn> <sql> UPDATE reportcomponent SET labels = jsonb_build_array(name); </sql> <dropColumn tableName="reportcomponent" columnName="accessors"/> <addNotNullConstraint tableName="reportcomponent" columnName="labels"/> <renameTable oldTableName="tablereport_rundata" newTableName="tablereport_data" /> <addColumn tableName="tablereport_data"> <column name="dataset_id" type="integer" /> <column name="ordinal" type="integer" /> </addColumn> <renameColumn tableName="tablereport_data" oldColumnName="label" newColumnName="scale" /> <sql> UPDATE tablereport_data trd SET dataset_id = (SELECT id FROM dataset WHERE dataset.runid = trd.runid LIMIT 1), ordinal = 0; </sql> <dropUniqueConstraint tableName="tablereport_data" constraintName="tablereport_rundata_report_id_runid_key"/> <addNotNullConstraint tableName="tablereport_data" columnName="dataset_id"/> <addNotNullConstraint tableName="tablereport_data" columnName="ordinal"/> <addUniqueConstraint tableName="tablereport_data" columnNames="report_id,dataset_id,ordinal" /> </changeSet> <changeSet id="69" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION ds_after_schema_update_func() RETURNS TRIGGER AS $$ BEGIN WITH ds AS ( SELECT id, jsonb_array_elements(data)->>'$schema' as uri FROM dataset WHERE jsonb_path_query_first(data, '$.type() == "array"')::boolean ), indexed AS ( SELECT id, uri, row_number() over (PARTITION BY id) - 1 AS index FROM ds ) INSERT INTO dataset_schemas(dataset_id, uri, index, schema_id) SELECT indexed.id, NEW.uri, indexed.index, NEW.id FROM indexed WHERE indexed.uri = NEW.uri; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION before_schema_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE schemaid = OLD.id; DELETE FROM dataset_schemas WHERE schema_id = OLD.id; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER ds_after_schema_update AFTER INSERT OR UPDATE OF uri ON schema FOR EACH ROW EXECUTE FUNCTION ds_after_schema_update_func(); </sql> </changeSet> <changeSet id="70" author="rvansa"> <validCheckSum>ANY</validCheckSum> <!-- We'll keep the old timestamp around to resolve any mess that's created by this change --> <renameColumn tableName="run" oldColumnName="start" newColumnName="old_start" /> <addColumn tableName="run"> <column name="start" type="timestamptz"/> </addColumn> <sql> UPDATE run SET start = old_start; </sql> <addNotNullConstraint tableName="run" columnName="start"/> <dropNotNullConstraint tableName="run" columnName="old_start"/> <modifyDataType tableName="run" columnName="stop" newDataType="timestamptz"/> <modifyDataType tableName="dataset" columnName="start" newDataType="timestamptz"/> <modifyDataType tableName="dataset" columnName="stop" newDataType="timestamptz"/> <modifyDataType tableName="datasetlog" columnName="timestamp" newDataType="timestamptz"/> </changeSet> <changeSet id="71" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropTable tableName="lastmissingrunnotification"/> <renameTable oldTableName="test_stalenesssettings" newTableName="missingdata_rule"/> <addColumn tableName="missingdata_rule"> <column name="id" type="integer"/> <column name="name" type="text"/> <column name="condition" type="text" /> <column name="last_notification" type="timestamptz" /> </addColumn> <renameColumn tableName="missingdata_rule" oldColumnName="tags" newColumnName="labels" /> <dropForeignKeyConstraint baseTableName="missingdata_rule" constraintName="fk_ss_test_id" /> <sql> UPDATE missingdata_rule SET id = nextval('hibernate_sequence'); </sql> <addPrimaryKey tableName="missingdata_rule" columnNames="id"/> <addNotNullConstraint tableName="missingdata_rule" columnName="id" /> <createTable tableName="missingdata_ruleresult"> <column name="rule_id" type="integer"> <constraints nullable="false" referencedTableName="missingdata_rule" referencedColumnNames="id" foreignKeyName="rr_rule_id"/> </column> <column name="dataset_id" type="integer"> <!-- No constraint, we'll remove through triggers --> <constraints nullable="false"/> </column> <column name="timestamp" type="timestamptz"> <constraints nullable="false"/> </column> </createTable> <createProcedure> CREATE OR REPLACE FUNCTION mdr_on_test_delete() RETURNS TRIGGER AS $$ BEGIN DELETE FROM missingdata_rule WHERE test_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION mdr_on_rule_delete() RETURNS TRIGGER AS $$ BEGIN DELETE FROM missingdata_ruleresult WHERE rule_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION mdr_on_dataset_delete() RETURNS TRIGGER AS $$ BEGIN DELETE FROM missingdata_ruleresult WHERE dataset_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE POLICY ss_all ON missingdata_rule FOR ALL USING (has_role('horreum.system')); GRANT select, insert, delete, update ON TABLE missingdata_ruleresult TO "${quarkus.datasource.username}"; ALTER TABLE missingdata_ruleresult ENABLE ROW LEVEL SECURITY; CREATE POLICY mdr_all ON missingdata_ruleresult FOR ALL USING (has_role('horreum.system')); <!-- Let tester delete the result along with the dataset when trashing the run --> CREATE POLICY mdr_select ON missingdata_ruleresult FOR SELECT USING (exists(SELECT 1 FROM dataset WHERE id = dataset_id AND can_view2(access, owner))); CREATE POLICY mdr_delete ON missingdata_ruleresult FOR DELETE USING (has_role2((SELECT owner FROM dataset WHERE id = dataset_id), 'tester')); CREATE TRIGGER mdr_before_delete BEFORE DELETE ON test FOR EACH ROW EXECUTE FUNCTION mdr_on_test_delete(); CREATE TRIGGER mdr_before_delete BEFORE DELETE ON missingdata_rule FOR EACH ROW EXECUTE FUNCTION mdr_on_rule_delete(); CREATE TRIGGER mdr_before_delete BEFORE DELETE ON dataset FOR EACH ROW EXECUTE FUNCTION mdr_on_dataset_delete(); </sql> </changeSet> <changeSet id="72" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropColumn tableName="run_expectation" columnName="tags"/> <dropTable tableName="run_tags"/> <sql> DROP TRIGGER rt_after_insert ON test; DROP TRIGGER rt_before_update ON test; DROP TRIGGER rt_before_delete ON test; DROP TRIGGER rt_before_delete ON run; DROP TRIGGER rt_after_insert ON run_schemas; DROP TRIGGER rt_before_delete ON run_schemas; DROP TRIGGER rt_before_insert ON schemaextractor; DROP TRIGGER rt_before_update ON schemaextractor; DROP TRIGGER rt_after_update ON schemaextractor; DROP TRIGGER rt_before_delete ON schemaextractor; DROP FUNCTION rt_after_insert_test_func; DROP FUNCTION rt_before_update_test_func; DROP FUNCTION rt_before_delete_test_func; DROP FUNCTION rt_before_delete_run_func; DROP FUNCTION rt_after_insert_run_schemas_func; DROP FUNCTION rt_before_delete_run_schemas_func; DROP FUNCTION rt_before_insert_extractor_func; DROP FUNCTION rt_before_update_extractor_func; DROP FUNCTION rt_after_update_extractor_func; DROP FUNCTION rt_before_delete_extractor_func; </sql> </changeSet> <changeSet id="73" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropTable tableName="schemaextractor"/> </changeSet> <changeSet id="74" author="jwhiting"> <validCheckSum>ANY</validCheckSum> <createTable tableName="transformationlog"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false" /> </column> <column name="level" type="integer"> <constraints nullable="false" /> </column> <column name="timestamp" type="timestamp without time zone"> <constraints nullable="false" /> </column> <column name="testid" type="integer" /> <column name="runid" type="integer" /> <column name="message" type="text"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT select, insert, delete ON TABLE transformationlog TO "${quarkus.datasource.username}"; ALTER TABLE transformationlog ENABLE ROW LEVEL SECURITY; CREATE POLICY cl_all ON transformationlog FOR ALL USING ((exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) ) AND exists( SELECT 1 FROM run WHERE run.id = runid AND has_role(run.owner) )) OR has_role('horreum.system')); DROP POLICY dataset_insert ON dataset; CREATE POLICY dataset_insert ON dataset FOR INSERT WITH CHECK (has_role2(owner, 'uploader') OR has_upload_token(testid) OR has_role('horreum.system')); </sql> </changeSet> <changeSet id="75" author="rvansa"> <validCheckSum>ANY</validCheckSum> <!-- This operation can take a long time - we don't want to execute in sync with schema update --> <createProcedure> CREATE OR REPLACE FUNCTION after_schema_update_func() RETURNS TRIGGER AS $$ BEGIN IF OLD.uri IS NULL OR OLD.uri != NEW.uri THEN PERFORM pg_notify('new_or_updated_schema', NEW.id::text); END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <renameColumn tableName="run_schemas" oldColumnName="prefix" newColumnName="key"/> <addColumn tableName="run_schemas"> <column name="type" type="integer" /> </addColumn> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_func() RETURNS TRIGGER AS $$ BEGIN WITH rs AS ( SELECT 0 AS type, NULL AS key, NEW.data->>'$schema' AS uri UNION SELECT 1 AS type, values.key, values.value->>'$schema' FROM jsonb_each(NEW.data) as values WHERE jsonb_typeof(NEW.data) = 'object' UNION SELECT 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri FROM jsonb_array_elements(NEW.data) WHERE jsonb_typeof(NEW.data) = 'array' ) INSERT INTO run_schemas(runid, testid, type, key, uri, schemaid) SELECT NEW.id, NEW.testid, rs.type, rs.key, rs.uri, schema.id FROM rs JOIN schema ON schema.uri = rs.uri; DELETE FROM dataset WHERE runid = OLD.id; PERFORM pg_notify('calculate_datasets', NEW.id::text); RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE FUNCTION update_run_schemas(runid INTEGER) RETURNS void AS $$ BEGIN WITH rs AS ( SELECT id, testid, 0 AS type, NULL AS key, data->>'$schema' AS uri FROM run WHERE id = runid UNION SELECT id, testid, 1 AS type, values.key, values.value->>'$schema' FROM run, jsonb_each(run.data) as values WHERE id = runid AND jsonb_typeof(data) = 'object' UNION SELECT id, testid, 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri FROM run, jsonb_array_elements(data) WHERE id = runid AND jsonb_typeof(data) = 'array' ) INSERT INTO run_schemas(runid, testid, type, key, uri, schemaid) SELECT rs.id, rs.testid, rs.type, rs.key, rs.uri, schema.id FROM rs JOIN schema ON schema.uri = rs.uri; END; $$ LANGUAGE plpgsql VOLATILE; </createProcedure> <sql> DROP TRIGGER ds_after_schema_update ON schema; DROP FUNCTION ds_after_schema_update_func; CREATE POLICY schema_select_system ON schema FOR SELECT USING (has_role('horreum.system')); CREATE POLICY rs_system ON run_schemas USING (has_role('horreum.system')); <!-- We won't do a big update data = data to run the trigger because that would also delete the datasets; recalculation can't be triggered from migration (app not ready yet), though --> DELETE FROM run_schemas; SELECT update_run_schemas(id) FROM run; </sql> <addNotNullConstraint tableName="run_schemas" columnName="runid"/> <addNotNullConstraint tableName="run_schemas" columnName="testid"/> <addNotNullConstraint tableName="run_schemas" columnName="schemaid"/> <addNotNullConstraint tableName="run_schemas" columnName="uri"/> <addNotNullConstraint tableName="run_schemas" columnName="type"/> </changeSet> <changeSet id="76" author="rvansa"> <validCheckSum>ANY</validCheckSum> <modifyDataType tableName="transformationlog" columnName="timestamp" newDataType="timestamptz"/> </changeSet> <changeSet id="77" author="rvansa"> <validCheckSum>ANY</validCheckSum> <!-- We have accidentally duplicated ViewComponent.render and Label.function - let's remove that from label as in some instances we expect to get 3 parameters to the ViewComponent.render --> <sql> SELECT set_config('horreum.userroles', '', false); UPDATE label SET function = NULL WHERE id IN ( SELECT label.id FROM viewcomponent JOIN label ON render = function and json_contains(viewcomponent.labels, label.name) ); SELECT set_config('horreum.userroles', NULL, false); </sql> <!-- Let's split run_schemas update and notifications into independent functions for easier maintenance --> <createProcedure> CREATE OR REPLACE FUNCTION rs_after_run_update() RETURNS TRIGGER AS $$ BEGIN WITH rs AS ( SELECT 0 AS type, NULL AS key, NEW.data->>'$schema' AS uri UNION SELECT 1 AS type, values.key, values.value->>'$schema' FROM jsonb_each(NEW.data) as values WHERE jsonb_typeof(NEW.data) = 'object' UNION SELECT 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri FROM jsonb_array_elements(NEW.data) WHERE jsonb_typeof(NEW.data) = 'array' ) INSERT INTO run_schemas(runid, testid, type, key, uri, schemaid) SELECT NEW.id, NEW.testid, rs.type, rs.key, rs.uri, schema.id FROM rs JOIN schema ON schema.uri = rs.uri; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION after_run_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset WHERE runid = OLD.id; PERFORM pg_notify('calculate_datasets', NEW.id::text || ';' || (OLD.id IS NOT NULL)::text); RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER rs_after_run_update AFTER INSERT OR UPDATE OF data ON run FOR EACH ROW EXECUTE FUNCTION rs_after_run_update(); </sql> <!-- These indexes prevent sequential scan on dataset_schemas which causes conflicts with many concurrent operations --> <createIndex tableName="dataset_schemas" indexName="ds_datasets"> <column name="dataset_id" /> </createIndex> <createIndex tableName="dataset_schemas" indexName="ds_schemas"> <column name="schema_id" /> </createIndex> </changeSet> <changeSet id="78" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> <!-- This is invoked as deferred trigger and horreum.userroles is already unset - we can pass it to the trigger using a non-readable column --> CREATE OR REPLACE FUNCTION recalc_fingerprint() RETURNS TRIGGER AS $$ BEGIN PERFORM set_config('horreum.userroles', NEW.roles, true); WITH fps AS ( SELECT jsonb_object_agg(label.name, lv.value) FILTER (WHERE label.name IS NOT NULL) AS fingerprint FROM test LEFT JOIN label ON test.fingerprint_labels ? label.name LEFT JOIN label_values lv ON label.id = lv.label_id WHERE test.id = (SELECT testid FROM dataset WHERE id = NEW.dataset_id) AND lv.dataset_id = NEW.dataset_id ) INSERT INTO fingerprint(dataset_id, fingerprint) SELECT NEW.dataset_id, fingerprint FROM fps WHERE fingerprint IS NOT NULL; DELETE FROM fingerprint_recalc_queue WHERE dataset_id = NEW.dataset_id; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="79" author="rvansa"> <validCheckSum>ANY</validCheckSum> <!-- Variable and label functions are duplicated --> <sql> UPDATE variable SET calculation = NULL WHERE id IN ( SELECT variable.id FROM variable JOIN label ON variable.name = label.name AND variable.calculation = label.function AND (SELECT value#>>'{}' FROM jsonb_array_elements(labels) LIMIT 1)::text = label.name ); </sql> </changeSet> <changeSet id="80" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> CREATE POLICY test_select_system ON test FOR SELECT USING (has_role('horreum.system')); CREATE POLICY test_token_select_system ON test_token FOR SELECT USING (has_role('horreum.system')); CREATE POLICY view_select_system ON view FOR SELECT USING (has_role('horreum.system')); </sql> </changeSet> <changeSet id="81" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> SELECT setval('label_id_seq', (SELECT MAX(id) FROM label)); </sql> <!-- Forgot to remove this earlier --> <dropColumn tableName="test" columnName="tagscalculation" /> </changeSet> <changeSet id="82" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="reportlog"> <column name="id" type="int8"> <constraints primaryKey="true"/> </column> <column name="report_id" type="integer"> <constraints nullable="false" foreignKeyName="tablereport_id" referencedTableName="tablereport" referencedColumnNames="id"/> </column> <column name="timestamp" type="timestamptz"> <constraints nullable="false"/> </column> <column name="level" type="integer"> <constraints nullable="false"/> </column> <column name="message" type="text"> <constraints nullable="false"/> </column> </createTable> <sql> GRANT select, insert, delete, update ON TABLE reportlog TO "${quarkus.datasource.username}"; CREATE POLICY rl_select ON reportlog FOR SELECT USING (exists(SELECT 1 FROM tablereport tr JOIN tablereportconfig trc ON tr.config_id = trc.id JOIN test ON test.id = trc.testid WHERE trc.id = report_id AND can_view2(test.access, test.owner))); CREATE POLICY rl_insert ON reportlog FOR INSERT WITH CHECK (has_role2((SELECT test.owner FROM tablereport tr JOIN tablereportconfig trc ON tr.config_id = trc.id JOIN test ON test.id = trc.testid WHERE trc.id = report_id), 'tester')); CREATE POLICY rl_delete ON reportlog FOR DELETE USING (has_role2((SELECT test.owner FROM tablereport tr JOIN tablereportconfig trc ON tr.config_id = trc.id JOIN test ON test.id = testid WHERE trc.id = report_id), 'tester')); </sql> </changeSet> <changeSet id="83" author="rvansa"> <sql> CREATE TYPE extractor AS (name text, jsonpath jsonpath, isarray boolean); </sql> </changeSet> <changeSet id="84" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION recalc_dataset_view() RETURNS TRIGGER AS $$ BEGIN PERFORM set_config('horreum.userroles', NEW.roles, true); <!-- Make sure we won't conflict with existing view --> DELETE FROM dataset_view WHERE dataset_id = NEW.dataset_id AND (NEW.view_id IS NULL OR NEW.view_id = view_id); WITH view_agg AS ( SELECT vc.view_id, vc.id as vcid, array_agg(DISTINCT label.id) as label_ids, jsonb_object_agg(label.name, lv.value) as value FROM dataset_schemas ds JOIN label ON label.schema_id = ds.schema_id JOIN viewcomponent vc ON vc.labels ? label.name JOIN label_values lv ON lv.label_id = label.id WHERE ds.dataset_id = NEW.dataset_id AND (NEW.view_id IS NULL OR NEW.view_id = vc.view_id) AND vc.view_id IN (SELECT view.id FROM view JOIN dataset ON view.test_id = dataset.testid WHERE dataset.id = NEW.dataset_id) GROUP BY vc.view_id, vcid ) INSERT INTO dataset_view (dataset_id, view_id, label_ids, value) SELECT NEW.dataset_id, view_id, array_agg(DISTINCT label_id), jsonb_object_agg(vcid, value) FROM view_agg, unnest(label_ids) as label_id GROUP BY view_id; DELETE FROM view_recalc_queue WHERE dataset_id = NEW.dataset_id AND (view_id IS NULL OR view_id = NEW.view_id); RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <!-- Delete those values for non-existent view/dataset combinations --> <sql> DELETE FROM dataset_view USING ( SELECT dv.dataset_id, dv.view_id FROM dataset_view dv JOIN view ON dv.view_id = view.id JOIN dataset ON dataset.id = dv.dataset_id WHERE dataset.testid != view.test_id ) AS extra WHERE dataset_view.dataset_id = extra.dataset_id AND dataset_view.view_id = extra.view_id; </sql> </changeSet> <changeSet id="85" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION dsv_after_vc_update_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset_view WHERE view_id = OLD.view_id OR view_id = NEW.view_id; INSERT INTO view_recalc_queue(dataset_id, view_id, roles) SELECT dataset.id, NEW.view_id, current_setting('horreum.userroles', true) FROM dataset WHERE testid = ( SELECT test_id FROM view WHERE view.id = NEW.view_id ) ON CONFLICT DO NOTHING; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="86" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="run_validationerrors"> <column name="run_id" type="integer"> <constraints nullable="false" foreignKeyName="validationerrors_runid" referencedTableName="run" referencedColumnNames="id" /> </column> <column name="schema_id" type="integer"> <constraints nullable="false" foreignKeyName="validationerrors_schemaid" referencedTableName="schema" referencedColumnNames="id" /> </column> <column name="error" type="jsonb"> <constraints nullable="false" /> </column> </createTable> <createIndex tableName="run_validationerrors" indexName="validationerrors_by_run_id"> <column name="run_id" /> </createIndex> <createTable tableName="dataset_validationerrors"> <column name="dataset_id" type="integer"> <constraints nullable="false" foreignKeyName="validationerrors_datasetid" referencedTableName="dataset" referencedColumnNames="id" /> </column> <column name="schema_id" type="integer"> <constraints nullable="false" foreignKeyName="validationerrors_schemaid" referencedTableName="schema" referencedColumnNames="id" /> </column> <column name="error" type="jsonb"> <constraints nullable="false" /> </column> </createTable> <createIndex tableName="dataset_validationerrors" indexName="validationerrors_by_dataset_id"> <column name="dataset_id" /> </createIndex> <createProcedure> CREATE OR REPLACE FUNCTION validate_run_data() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_validationerrors WHERE run_id = NEW.id; PERFORM pg_notify('validate_run_data', NEW.id::text ); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION validate_dataset_data() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset_validationerrors WHERE dataset_id = NEW.id; PERFORM pg_notify('validate_dataset_data', NEW.id::text ); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION revalidate_all() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset_validationerrors WHERE schema_id = NEW.id; DELETE FROM run_validationerrors WHERE schema_id = NEW.id; PERFORM pg_notify('revalidate_all', NEW.id::text ); RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION delete_run_validations() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_validationerrors WHERE run_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION delete_dataset_validations() RETURNS TRIGGER AS $$ BEGIN DELETE FROM dataset_validationerrors WHERE dataset_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION before_schema_update_func() RETURNS TRIGGER AS $$ BEGIN IF OLD.uri != NEW.uri THEN DELETE FROM run_schemas WHERE schemaid = OLD.id; DELETE FROM dataset_schemas WHERE schema_id = OLD.id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> GRANT select, insert, delete ON TABLE run_validationerrors, dataset_validationerrors TO "${quarkus.datasource.username}"; CREATE POLICY rve_select ON run_validationerrors FOR SELECT USING (exists(SELECT 1 FROM run WHERE id = run_id AND can_view(access, owner, token))); CREATE POLICY rve_insert ON run_validationerrors FOR INSERT WITH CHECK (has_role('horreum.system')); CREATE POLICY rve_delete ON run_validationerrors FOR DELETE USING (has_role2((SELECT owner FROM run WHERE run.id = run_id), 'tester')); CREATE POLICY dve_select ON dataset_validationerrors FOR SELECT USING (exists(SELECT 1 FROM dataset WHERE id = dataset_id AND can_view2(access, owner))); CREATE POLICY dve_insert ON dataset_validationerrors FOR INSERT WITH CHECK (has_role('horreum.system')); CREATE POLICY dve_delete ON dataset_validationerrors FOR DELETE USING (has_role2((SELECT owner FROM dataset WHERE dataset.id = dataset_id), 'tester')); CREATE TRIGGER validate_run_data AFTER INSERT OR UPDATE OF data ON run FOR EACH ROW EXECUTE FUNCTION validate_run_data(); CREATE TRIGGER validate_dataset_data AFTER INSERT OR UPDATE OF data ON dataset FOR EACH ROW EXECUTE FUNCTION validate_dataset_data(); CREATE TRIGGER delete_run_validations BEFORE DELETE ON run FOR EACH ROW EXECUTE FUNCTION delete_run_validations(); CREATE TRIGGER delete_dataset_validations BEFORE DELETE ON dataset FOR EACH ROW EXECUTE FUNCTION delete_dataset_validations(); CREATE TRIGGER revalidate_all AFTER INSERT OR UPDATE OF schema ON schema FOR EACH ROW EXECUTE FUNCTION revalidate_all(); </sql> </changeSet> <changeSet id="87" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION recalc_dataset_view() RETURNS TRIGGER AS $$ BEGIN PERFORM set_config('horreum.userroles', NEW.roles, true); DELETE FROM dataset_view WHERE dataset_id = NEW.dataset_id AND (NEW.view_id IS NULL OR NEW.view_id = view_id); WITH view_agg AS ( SELECT vc.view_id, vc.id as vcid, array_agg(DISTINCT label.id) as label_ids, jsonb_object_agg(label.name, lv.value) as value FROM dataset_schemas ds JOIN label ON label.schema_id = ds.schema_id JOIN viewcomponent vc ON vc.labels ? label.name JOIN label_values lv ON lv.label_id = label.id AND lv.dataset_id = ds.dataset_id WHERE ds.dataset_id = NEW.dataset_id AND (NEW.view_id IS NULL OR NEW.view_id = vc.view_id) AND vc.view_id IN (SELECT view.id FROM view JOIN dataset ON view.test_id = dataset.testid WHERE dataset.id = NEW.dataset_id) GROUP BY vc.view_id, vcid ) INSERT INTO dataset_view (dataset_id, view_id, label_ids, value) SELECT NEW.dataset_id, view_id, array_agg(DISTINCT label_id), jsonb_object_agg(vcid, value) FROM view_agg, unnest(label_ids) as label_id GROUP BY view_id; DELETE FROM view_recalc_queue WHERE dataset_id = NEW.dataset_id AND (view_id IS NULL OR view_id = NEW.view_id); RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> </changeSet> <changeSet id="88" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="experiment_profile"> <column name="id" type="integer"> <constraints primaryKey="true"/> </column> <column name="name" type="text"> <constraints nullable="false"/> </column> <column name="test_id" type="integer"> <constraints nullable="false" foreignKeyName="experiment_profile_test" referencedTableName="test" referencedColumnNames="id"/> </column> <column name="selector_labels" type="jsonb"> <constraints nullable="false"/> </column> <column name="selector_filter" type="text" /> <column name="baseline_labels" type="jsonb"> <constraints nullable="false"/> </column> <column name="baseline_filter" type="text" /> <column name="extra_labels" type="jsonb" /> </createTable> <createTable tableName="experiment_comparisons"> <column name="profile_id" type="integer"> <constraints nullable="false" foreignKeyName="experiment_comparison_profile" referencedTableName="experiment_profile" referencedColumnNames="id"/> </column> <column name="variable_id" type="integer"> <constraints nullable="false" foreignKeyName="experiment_comparison_variable" referencedTableName="variable" referencedColumnNames="id" /> </column> <column name="model" type="text"> <constraints nullable="false"/> </column> <column name="config" type="jsonb"> <constraints nullable="false"/> </column> </createTable> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE experiment_profile, experiment_comparisons TO "${quarkus.datasource.username}"; ALTER TABLE experiment_profile ENABLE ROW LEVEL SECURITY; ALTER TABLE experiment_comparisons ENABLE ROW LEVEL SECURITY; CREATE POLICY ep_select ON experiment_profile FOR SELECT USING(exists( SELECT 1 FROM test WHERE test.id = test_id AND (can_view2(access, owner) OR has_read_token(test.id)) ) OR has_role('horreum.system')); CREATE POLICY ep_insert ON experiment_profile FOR INSERT WITH CHECK (exists( SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester') OR has_modify_token(test.id)) )); CREATE POLICY ep_update ON experiment_profile FOR UPDATE USING (exists( SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester') OR has_modify_token(test.id)) )); CREATE POLICY ep_delete ON experiment_profile FOR DELETE USING (exists( SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester') OR has_modify_token(test.id)) )); CREATE POLICY ec_select ON experiment_comparisons FOR SELECT USING(exists( SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (can_view2(access, owner) OR has_read_token(test.id)) ) OR has_role('horreum.system')); CREATE POLICY ec_insert ON experiment_comparisons FOR INSERT WITH CHECK (exists( SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester') OR has_modify_token(test.id)) )); CREATE POLICY ec_update ON experiment_comparisons FOR UPDATE USING (exists( SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester') OR has_modify_token(test.id)) )); CREATE POLICY ec_delete ON experiment_comparisons FOR DELETE USING (exists( SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester') OR has_modify_token(test.id)) )); </sql> </changeSet> <changeSet id="89" author="rvansa"> <validCheckSum>ANY</validCheckSum> <renameTable oldTableName="allowedhookprefix" newTableName="allowedsite"/> <renameTable oldTableName="hook" newTableName="action"/> <renameColumn tableName="action" oldColumnName="type" newColumnName="event"/> <renameColumn tableName="action" oldColumnName="target" newColumnName="test_id"/> <addColumn tableName="action"> <column name="type" type="text" defaultValue="http"> <constraints nullable="false"/> </column> <column name="config" type="jsonb" defaultValue="{}"> <constraints nullable="false"/> </column> <column name="secrets" type="jsonb" defaultValue="{}"> <constraints nullable="false"/> </column> </addColumn> <dropColumn tableName="action" columnName="active"/> <sql> UPDATE action SET config = jsonb_build_object('url', url); DROP POLICY hook_policy ON action; CREATE POLICY action_policy ON action USING ( has_role('horreum.system') OR has_role('admin') OR has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester') OR has_modify_token(test_id) ); DROP POLICY hook_write_check ON action; CREATE POLICY action_write_check ON action WITH CHECK (config->'url' IS NULL OR exists(SELECT 1 FROM allowedsite site WHERE left(config->>'url', length(site.prefix)) = site.prefix)); </sql> <dropUniqueConstraint tableName="action" constraintName="hook_url_type_target_key"/> <dropColumn tableName="action" columnName="url"/> <dropDefaultValue tableName="action" columnName="type"/> <dropDefaultValue tableName="action" columnName="config"/> <dropDefaultValue tableName="action" columnName="secrets"/> <renameSequence oldSequenceName="hook_id_seq" newSequenceName="action_id_seq" /> </changeSet> <changeSet id="90" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="actionlog"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false" /> </column> <column name="level" type="integer"> <constraints nullable="false" /> </column> <column name="timestamp" type="timestamptz"> <constraints nullable="false" /> </column> <column name="testid" type="integer"> <constraints nullable="false"/> </column> <column name="event" type="text"> <constraints nullable="false"/> </column> <column name="type" type="text" /> <column name="message" type="text"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT select, insert, delete ON TABLE actionlog TO "${quarkus.datasource.username}"; ALTER TABLE actionlog ENABLE ROW LEVEL SECURITY; CREATE POLICY al_all ON actionlog FOR ALL USING (has_role('horreum.system') OR has_role('admin') OR exists( SELECT 1 FROM test WHERE test.id = testid AND has_role(test.owner) )); </sql> </changeSet> <changeSet id="91" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropColumn tableName="datapoint" columnName="timestamp"/> <addColumn tableName="datapoint"> <column name="timestamp" type="timestamptz" defaultValue="1970-01-01T00:00:00Z"> <constraints nullable="false"/> </column> </addColumn> <sql> UPDATE datapoint SET timestamp = start FROM dataset WHERE dataset.id = dataset_id; </sql> <dropDefaultValue tableName="datapoint" columnName="timestamp"/> </changeSet> <changeSet id="92" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropNotNullConstraint tableName="dataset_validationerrors" columnName="schema_id"/> </changeSet> <changeSet id="93" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="action"> <column name="run_always" type="boolean" defaultValue="false"> <constraints nullable="false"/> </column> </addColumn> </changeSet> <changeSet id="94" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="test"> <column name="timeline_labels" type="jsonb" /> <column name="timeline_function" type="text" /> </addColumn> </changeSet> <changeSet id="95" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION run_exists(runid INTEGER, test_id INTEGER) RETURNS boolean AS $$ BEGIN RETURN exists(SELECT 1 FROM run WHERE run.id = runid AND run.testid = test_id); END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION is_uploader_for_run(runid INTEGER) RETURNS boolean AS $$ BEGIN RETURN exists(SELECT 1 FROM run WHERE run.id = runid AND has_role2(owner, 'uploader')); END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <sql> ALTER POLICY rs_insert_validate ON run_schemas WITH CHECK (run_exists(runid, testid)); ALTER POLICY rs_insert ON run_schemas WITH CHECK ( is_uploader_for_run(runid) OR has_upload_token(testid) OR has_role2((SELECT owner FROM schema WHERE schema.id = schemaid), 'tester') ); <!-- Uploader cannot normally read schemas but we need to give him access in order to create run_schemas --> ALTER POLICY schema_select ON schema USING (can_view(access, owner, token) OR has_role2(owner, 'uploader')); ALTER POLICY ds_select ON dataset_schemas USING (has_role('horreum.system') OR (exists( SELECT 1 FROM dataset WHERE dataset.id = dataset_id AND can_view2(access, owner) ))); </sql> </changeSet> <changeSet id="96" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createTable tableName="messagebus_subscriptions"> <column name="channel" type="text"> <constraints nullable="false"/> </column> <column name="index" type="integer"> <constraints nullable="false"/> </column> <column name="component" type="text"> <constraints nullable="false"/> </column> </createTable> <addUniqueConstraint tableName="messagebus_subscriptions" columnNames="channel,index"/> <addUniqueConstraint tableName="messagebus_subscriptions" columnNames="channel,component"/> <createTable tableName="messagebus"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false"/> </column> <column name="timestamp" type="timestamptz"> <constraints nullable="false"/> </column> <column name="channel" type="text"> <constraints nullable="false"/> </column> <column name="message" type="jsonb"/> <column name="flags" type="int"> <constraints nullable="false"/> </column> </createTable> <createSequence sequenceName="messagebus_seq"/> <createProcedure> CREATE OR REPLACE FUNCTION messagebus_delete() RETURNS TRIGGER AS $$ BEGIN DELETE FROM messagebus WHERE id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> GRANT select, insert, update, delete ON TABLE messagebus, messagebus_subscriptions TO "${quarkus.datasource.username}"; GRANT ALL ON SEQUENCE messagebus_seq TO "${quarkus.datasource.username}"; CREATE POLICY messagebus_insert ON messagebus FOR INSERT WITH CHECK (has_role('horreum.messagebus')); CREATE POLICY messagebus_all ON messagebus FOR ALL USING (has_role('horreum.system')); CREATE POLICY mbs_all ON messagebus_subscriptions FOR ALL USING (has_role('horreum.system')); CREATE TRIGGER messagebus_delete AFTER UPDATE OF flags ON messagebus FOR EACH ROW WHEN (NEW.flags = 0) EXECUTE FUNCTION messagebus_delete(); </sql> </changeSet> <changeSet id="97" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createIndex tableName="datapoint" indexName="datapoint_datasets"> <column name="dataset_id" /> </createIndex> <createIndex tableName="datapoint" indexName="datapoint_timestamps"> <column name="timestamp" /> </createIndex> <createIndex tableName="change" indexName="change_datasets"> <column name="dataset_id" /> </createIndex> </changeSet> <changeSet id="98" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> ALTER POLICY cl_all_alerting ON datasetlog USING (has_role('horreum.system')); ALTER POLICY notificationsettings_policies ON notificationsettings USING ((has_role('horreum.system'::text) OR has_role(name))); ALTER POLICY run_expectation_delete ON run_expectation USING (has_role('horreum.system')); ALTER POLICY run_expectation_select ON run_expectation USING (has_role('horreum.system')); ALTER POLICY userinfo_teams_read ON userinfo_teams USING (has_role('horreum.system')); DROP POLICY userinfo_read ON userinfo_teams; CREATE POLICY userinfo_read ON userinfo FOR SELECT USING (has_role('horreum.system')); ALTER POLICY watch_delete ON watch USING (has_role('horreum.system')); ALTER POLICY watch_update ON watch USING (has_role('horreum.system')); ALTER POLICY watch_teams_policies ON watch_teams USING ((has_role('horreum.system') OR has_role(teams))); ALTER POLICY watch_users_policies ON watch_users USING ((has_role('horreum.system') OR has_role(users))); ALTER POLICY watch_optout_policies ON watch_optout USING ((has_role('horreum.system') OR has_role(optout))); ALTER POLICY variable_access ON variable USING (has_role('horreum.system')); ALTER POLICY rd_access ON changedetection USING (has_role('horreum.system')); </sql> <createProcedure> CREATE OR REPLACE FUNCTION can_view(access INTEGER, owner TEXT, token TEXT) RETURNS boolean AS $$ BEGIN RETURN ( access = 0 OR (access = 1 AND has_role('viewer')) OR (access = 2 AND has_role(owner) AND has_role('viewer')) OR token = current_setting('horreum.token', true) OR has_role('horreum.system') ); END; $$ LANGUAGE plpgsql STABLE; </createProcedure> </changeSet> <changeSet id="99" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION recalc_label_values() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('calculate_labels', (SELECT testid FROM dataset WHERE id = dataset_id)::text || ';' || dataset_id::text || ';' || NEW.label_id) FROM dataset_schemas WHERE dataset_schemas.schema_id = NEW.schema_id; DELETE FROM label_recalc_queue WHERE label_id = NEW.label_id; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <addColumn tableName="messagebus"> <column name="testid" type="integer" defaultValue="-1"> <constraints nullable="false"/> </column> </addColumn> <dropDefaultValue tableName="messagebus" columnName="testid"/> </changeSet> <changeSet id="100" author="rvansa"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION rs_after_run_trash() RETURNS TRIGGER AS $$ BEGIN DELETE FROM run_schemas WHERE runid = OLD.id; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER rs_after_run_trash AFTER UPDATE OF trashed ON run FOR EACH ROW WHEN (NEW.trashed) EXECUTE FUNCTION rs_after_run_trash(); CREATE TRIGGER rs_after_run_untrash AFTER UPDATE OF trashed ON run FOR EACH ROW WHEN (NOT NEW.trashed) EXECUTE FUNCTION rs_after_run_update(); ALTER POLICY run_update ON run USING (has_role2(owner, 'tester') OR has_role('horreum.system')); </sql> </changeSet> <changeSet id="101" author="rvansa"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="run"> <column name="metadata" type="jsonb"> <constraints checkConstraint="metadata IS NULL OR jsonb_typeof(metadata) = 'array'" /> </column> </addColumn> <addColumn tableName="run_schemas"> <column name="source" type="integer" defaultValue="0"> <!-- 0 means run.data, 1 is run.metadata --> <constraints nullable="false" checkConstraint="source BETWEEN 0 AND 1"/> </column> </addColumn> <dropDefaultValue tableName="run_schemas" columnName="source"/> <createProcedure> CREATE OR REPLACE FUNCTION rs_after_run_update() RETURNS TRIGGER AS $$ BEGIN WITH rs AS ( SELECT 0 AS type, NULL AS key, NEW.data->>'$schema' AS uri, 0 AS source UNION SELECT 1 AS type, values.key, values.value->>'$schema' AS uri, 0 AS source FROM jsonb_each(NEW.data) as values WHERE jsonb_typeof(NEW.data) = 'object' UNION SELECT 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri, 0 AS source FROM jsonb_array_elements(NEW.data) WHERE jsonb_typeof(NEW.data) = 'array' UNION SELECT 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri, 1 AS source FROM jsonb_array_elements(NEW.metadata) WHERE NEW.metadata IS NOT NULL ) INSERT INTO run_schemas(runid, testid, source, type, key, uri, schemaid) SELECT NEW.id, NEW.testid, rs.source, rs.type, rs.key, rs.uri, schema.id FROM rs JOIN schema ON schema.uri = rs.uri; RETURN NULL; END; $$ LANGUAGE plpgsql; </createProcedure> <createProcedure> CREATE OR REPLACE FUNCTION update_run_schemas(runid INTEGER) RETURNS void AS $$ BEGIN WITH rs AS ( SELECT id, testid, 0 AS type, NULL AS key, data->>'$schema' AS uri, 0 AS source FROM run WHERE id = runid UNION SELECT id, testid, 1 AS type, values.key, values.value->>'$schema' AS uri, 0 AS source FROM run, jsonb_each(run.data) as values WHERE id = runid AND jsonb_typeof(data) = 'object' UNION SELECT id, testid, 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri, 0 AS source FROM run, jsonb_array_elements(data) WHERE id = runid AND jsonb_typeof(data) = 'array' UNION SELECT id, testid, 2 AS type, (row_number() OVER () - 1)::text AS key, value->>'$schema' as uri, 1 AS source FROM run, jsonb_array_elements(metadata) WHERE id = runid AND metadata IS NOT NULL ) INSERT INTO run_schemas(runid, testid, source, type, key, uri, schemaid) SELECT rs.id, rs.testid, rs.source, rs.type, rs.key, rs.uri, schema.id FROM rs JOIN schema ON schema.uri = rs.uri; END; $$ LANGUAGE plpgsql VOLATILE; </createProcedure> <sql> DROP TRIGGER rs_after_run_update ON run; CREATE TRIGGER rs_after_run_update AFTER INSERT OR UPDATE OF data, metadata ON run FOR EACH ROW EXECUTE FUNCTION rs_after_run_update(); </sql> </changeSet> <changeSet id="102" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> DELETE FROM dataset_validationerrors WHERE error->>'type' LIKE '% not defined'; UPDATE schema SET schema = NULL WHERE jsonb_typeof(schema) != 'object'; </sql> </changeSet> <changeSet id="103" author="rvansa"> <validCheckSum>ANY</validCheckSum> <sql> DELETE FROM changedetection WHERE variable_id IN (SELECT variable.id FROM variable LEFT JOIN test ON test.id = testid WHERE test.id IS NULL); DELETE FROM variable WHERE id IN (SELECT variable.id FROM variable LEFT JOIN test ON test.id = testid WHERE test.id IS NULL); </sql> </changeSet> <changeSet id="104" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropColumn tableName="test" columnName="defaultview_id" /> </changeSet> <changeSet id="105" author="rvansa"> <validCheckSum>ANY</validCheckSum> <dropForeignKeyConstraint baseTableName="experiment_profile" constraintName="experiment_profile_test" /> <sql> <!-- Cleanup after test delete is executed using system role --> ALTER POLICY ep_delete ON experiment_profile USING ( has_role('horreum.system') OR exists( SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester') OR has_modify_token(test.id))) ); ALTER POLICY ec_delete ON experiment_comparisons USING ( has_role('horreum.system') OR exists( SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester') OR has_modify_token(test.id))) ); </sql> </changeSet> <changeSet id="106" author="jwhiting"> <validCheckSum>ANY</validCheckSum> <createProcedure> CREATE OR REPLACE FUNCTION before_variable_delete_func() RETURNS TRIGGER AS $$ BEGIN DELETE FROM change WHERE variable_id = OLD.id; DELETE FROM datapoint WHERE variable_id = OLD.id; DELETE FROM experiment_comparisons WHERE variable_id = OLD.id; RETURN OLD; END; $$ LANGUAGE plpgsql; </createProcedure> <sql> CREATE TRIGGER before_variable_delete BEFORE DELETE ON variable FOR EACH ROW EXECUTE FUNCTION before_variable_delete_func(); </sql> </changeSet> <changeSet id="107" author="stalep"> <validCheckSum>ANY</validCheckSum> <createSequence sequenceName="actionlog_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="allowedsite_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="banner_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="change_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="datapoint_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="datasetlog_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="notificationsettings_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="reportcomment_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="reportcomponent_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="reportlog_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="run_expectation_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="tablereport_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="tablereportconfig_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="transformationlog_seq" startValue="1" incrementBy="50" cacheSize="1" /> <createSequence sequenceName="changedetectionidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="experimentprofileidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="mdridgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="subscriptionidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="tokenidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="transformeridgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="variableidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="viewcomponentidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="viewidgenerator" startValue="1" incrementBy="1" cacheSize="1" /> <sql> GRANT ALL ON SEQUENCE ActionLog_SEQ, AllowedSite_SEQ, Banner_SEQ, Change_SEQ, DataPoint_SEQ, DatasetLog_SEQ, NotificationSettings_SEQ, ReportComment_SEQ, ReportComponent_SEQ, ReportLog_SEQ, Run_Expectation_SEQ, TableReport_SEQ, TableReportConfig_SEQ, TransformationLog_SEQ, changeDetectionIdGenerator, experimentProfileIdGenerator, mdrIdGenerator, subscriptionIdGenerator, tokenIdGenerator, transformerIdGenerator, variableIdGenerator, viewComponentIdGenerator, viewIdGenerator TO "${quarkus.datasource.username}"; </sql> </changeSet> <changeSet id="108" author="jwhiting"> <validCheckSum>ANY</validCheckSum> <sql> alter table dataset drop constraint fk_dataset_run_id; </sql> <addForeignKeyConstraint constraintName="fk_datapoint_dataset_id" baseTableName="datapoint" baseColumnNames="dataset_id" referencedTableName="dataset" referencedColumnNames="id" /> </changeSet> <changeSet id="109" author="johara"> <validCheckSum>ANY</validCheckSum> <createSequence sequenceName="actionlog_id_generator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="datasetlog_id_generator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="reportlog_id_generator" startValue="1" incrementBy="1" cacheSize="1" /> <createSequence sequenceName="transformationlog_id_generator" startValue="1" incrementBy="1" cacheSize="1" /> <sql> select setval('actionlog_id_generator', (select max(id)+1 from actionlog), false); select setval('datasetlog_id_generator', (select max(id)+1 from datasetlog), false); select setval('reportlog_id_generator', (select max(id)+1 from reportlog), false); select setval('transformationlog_id_generator', (select max(id)+1 from transformationlog), false); GRANT ALL ON SEQUENCE actionlog_id_generator, datasetlog_id_generator, reportlog_id_generator, transformationlog_id_generator TO "${quarkus.datasource.username}"; </sql> </changeSet> <changeSet id="110" author="stalep"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER IF EXISTS before_run_delete ON run; DROP FUNCTION IF EXISTS before_run_delete_func(); DROP TRIGGER IF EXISTS before_schema_delete ON schema; DROP FUNCTION IF EXISTS before_schema_delete_func(); DROP TRIGGER IF EXISTS mdr_before_delete ON test; DROP FUNCTION IF EXISTS mdr_on_test_delete(); DROP TRIGGER IF EXISTS mdr_before_delete ON missingdata_rule; DROP FUNCTION IF EXISTS mdr_on_rule_delete(); DROP TRIGGER IF EXISTS mdr_before_delete ON dataset; DROP FUNCTION IF EXISTS mdr_on_dataset_delete(); DROP TRIGGER IF EXISTS rs_after_run_trash ON run; DROP FUNCTION IF EXISTS rs_after_run_trash(); DROP TRIGGER IF EXISTS after_run_delete ON run; DROP FUNCTION IF EXISTS after_run_delete_func(); DROP TRIGGER IF EXISTS lv_before_delete ON dataset; DROP FUNCTION IF EXISTS lv_before_dataset_delete_func(); DROP TRIGGER IF EXISTS lv_before_delete ON label; DROP FUNCTION IF EXISTS lv_before_label_delete_func(); DROP TRIGGER IF EXISTS lv_before_delete ON label_extractors; DROP FUNCTION IF EXISTS lv_before_le_delete_func(); DROP TRIGGER IF EXISTS lv_after_delete ON label_extractors; DROP FUNCTION IF EXISTS lv_after_le_delete_func(); DROP TRIGGER IF EXISTS after_run_update on run; DROP FUNCTION IF EXISTS after_run_update_func(); DROP TRIGGER IF EXISTS fp_after_delete ON label_values; DROP FUNCTION IF EXISTS fp_after_lv_delete_func(); DROP TRIGGER IF EXISTS fp_after_insert ON label_values; DROP FUNCTION IF EXISTS fp_after_lv_insert_func(); DROP TRIGGER IF EXISTS recalc_fingerprint ON fingerprint_recalc_queue; DROP TRIGGER IF EXISTS fp_after_update ON test; DROP FUNCTION IF EXISTS fp_after_test_update_func(); DROP TRIGGER IF EXISTS after_schema_update ON schema; DROP FUNCTION IF EXISTS after_schema_update_func(); DROP TRIGGER IF EXISTS validate_run_data ON run; DROP FUNCTION IF EXISTS validate_run_data(); DROP TRIGGER IF EXISTS validate_dataset_data ON dataset; DROP FUNCTION IF EXISTS validate_dataset_data(); DROP TRIGGER IF EXISTS revalidate_all ON schema; DROP FUNCTION IF EXISTS revalidate_all(); </sql> </changeSet> <changeSet id="111" author="johara"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER IF EXISTS messagebus_delete ON messagebus; DROP FUNCTION IF EXISTS messagebus_delete(); DROP POLICY if EXISTS messagebus_insert ON messagebus; DROP POLICY if EXISTS messagebus_all ON messagebus; DROP POLICY if EXISTS mbs_all ON messagebus_subscriptions; DROP TABLE IF EXISTS messagebus_subscriptions; DROP TABLE IF EXISTS messagebus; </sql> </changeSet> <changeSet id="112" author="johara"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER IF EXISTS lv_after_update ON label_extractors; DROP FUNCTION IF EXISTS lv_after_le_update_func(); DROP TRIGGER IF EXISTS lv_before_update ON label_extractors; DROP FUNCTION IF EXISTS lv_before_le_update_func(); DROP TRIGGER IF EXISTS dsv_after_delete ON label_values; DROP FUNCTION IF EXISTS dsv_after_lv_delete_func(); -- DROP TRIGGER IF EXISTS recalc_dataset_view ON view_recalc_queue; -- DROP FUNCTION IF EXISTS recalc_dataset_view(); -- still need db procedure until https://hibernate.atlassian.net/browse/HHH-17314 is fixed in quarkus CREATE OR REPLACE PROCEDURE calc_dataset_view(datasetId bigint) AS $$ BEGIN WITH view_agg AS ( SELECT vc.view_id, vc.id as vcid, array_agg(DISTINCT label.id) as label_ids, jsonb_object_agg(label.name, lv.value) as value FROM dataset_schemas ds JOIN label ON label.schema_id = ds.schema_id JOIN viewcomponent vc ON vc.labels ? label.name JOIN label_values lv ON lv.label_id = label.id WHERE ds.dataset_id = datasetId GROUP BY vc.view_id, vcid ) INSERT INTO dataset_view (dataset_id, view_id, label_ids, value) SELECT datasetId, view_id, array_agg(DISTINCT label_id), jsonb_object_agg(vcid, value) FROM view_agg, unnest(label_ids) as label_id GROUP BY view_id; END $$ LANGUAGE plpgsql; </sql> </changeSet> <changeSet id="113" author="jpederse"> <validCheckSum>ANY</validCheckSum> <sql> CREATE INDEX label_values_label_id ON label_values (label_id); </sql> </changeSet> <changeSet id="114" author="johara"> <validCheckSum>ANY</validCheckSum> <addColumn tableName="fingerprint"> <column name="fp_hash" type="integer"/> </addColumn> <sql> CREATE INDEX fingerprint_fp_hash ON fingerprint (fp_hash); </sql> </changeSet> <changeSet id="115" author="barreiro"> <validCheckSum>ANY</validCheckSum> <!-- remove the encryption in `has_role` in the db procedure, and we can remove `horreum.db.secret` property and the corresponding database table --> <dropTable tableName="dbsecret"/> <createProcedure> <!-- redefine `has_role()` to remove signing --> CREATE OR REPLACE FUNCTION has_role(owner TEXT) RETURNS boolean AS $$ DECLARE v_userroles TEXT; v_role TEXT; BEGIN v_userroles := current_setting('horreum.userroles', true); IF v_userroles = '' OR v_userroles IS NULL THEN RETURN 0; END IF; FOREACH v_role IN ARRAY regexp_split_to_array(v_userroles, ',') LOOP IF v_role = owner THEN RETURN 1; END IF; END LOOP; RETURN 0; END; $$ LANGUAGE plpgsql SECURITY DEFINER STABLE; </createProcedure> <sql> DROP EXTENSION pgcrypto; </sql> </changeSet> <changeSet id="116" author="johara"> <validCheckSum>ANY</validCheckSum> <createSequence sequenceName="backend_id_seq" startValue="10" incrementBy="1" cacheSize="1" /> <createTable tableName="backendconfig"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="name" type="text"> <constraints nullable="false" /> </column> <column name="configuration" type="jsonb"> <constraints nullable="false" /> </column> <column name="type" type="integer"> <constraints nullable="false" /> </column> <column name="owner" type="text"> <constraints nullable="false" /> </column> <column name="access" type="integer"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE backendconfig TO "${quarkus.datasource.username}"; GRANT ALL ON SEQUENCE backend_id_seq TO "${quarkus.datasource.username}"; </sql> <insert tableName="backendconfig"> <column name="id" value="1" /> <column name="name" value="Postgres - Default" /> <column name="owner" value="horreum.system" /> <column name="type" value="0" /> <column name="access" value="0" /> <column name="configuration" value='{ "builtIn": true }' /> </insert> <addColumn tableName="test"> <column name="backendconfig_id" type="integer" /> </addColumn> <sql> update test set backendconfig_id = 1 WHERE backendconfig_id IS NULL; </sql> </changeSet> <changeSet id="117" author="stalep"> <validCheckSum>ANY</validCheckSum> <sql> DROP TRIGGER IF EXISTS before_run_update ON run; DROP FUNCTION IF EXISTS before_run_update_func(); </sql> </changeSet> <changeSet id="118" author="barreiro"> <validCheckSum>ANY</validCheckSum> <!-- Extend userinfo for support of internal security --> <addColumn tableName="userinfo"> <column name="password" type="text"/> <column name="email" type="text"/> <column name="first_name" type="text"/> <column name="last_name" type="text"/> </addColumn> <createSequence sequenceName="team_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="team"> <column name="id" type="integer"> <constraints nullable="false" primaryKey="true"/> </column> <column name="team_name" type="text"> <constraints nullable="false"/> </column> </createTable> <delete tableName="userinfo_teams"/> <dropColumn tableName="userinfo_teams" columnName="team"/> <addColumn tableName="userinfo_teams"> <column name="team_id" type="integer"> <constraints nullable="false"/> </column> <column name="team_role" type="text"> <constraints nullable="false"/> </column> </addColumn> <addForeignKeyConstraint constraintName="fk_userinfo_teams" baseTableName="userinfo_teams" baseColumnNames="team_id" referencedTableName="team" referencedColumnNames="id"/> <addUniqueConstraint tableName="userinfo_teams" columnNames="username,team_id,team_role"/> <createTable tableName="userinfo_roles"> <column name="username" type="text"> <constraints nullable="false"/> </column> <column name="role" type="text"> <constraints nullable="false"/> </column> </createTable> <addForeignKeyConstraint constraintName="fk_userinfo_roles" baseTableName="userinfo_roles" baseColumnNames="username" referencedTableName="userinfo" referencedColumnNames="username"/> <addUniqueConstraint tableName="userinfo_roles" columnNames="username,role"/> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE team, userinfo_roles TO "${quarkus.datasource.username}"; GRANT ALL ON SEQUENCE team_id_seq TO "${quarkus.datasource.username}"; ALTER TABLE userinfo_roles ENABLE ROW LEVEL SECURITY; CREATE POLICY userinfo_roles_read ON userinfo_roles USING (has_role('horreum.system')); CREATE POLICY userinfo_roles_rw ON userinfo_roles FOR ALL USING (has_role(username)); </sql> </changeSet> <changeSet id="119" author="johara"> <validCheckSum>ANY</validCheckSum> <sql> UPDATE changedetection SET config=subquery.newConfig FROM (select id as newID, jsonb_set(config, '{model}', to_jsonb(changedetection.model::text), true) as newConfig from changedetection) AS subquery WHERE changedetection.id=subquery.newID; </sql> </changeSet> <changeSet id="120" author="johara"> <validCheckSum>ANY</validCheckSum> <createTable tableName="changedetectionlog"> <column name="id" type="bigint"> <constraints primaryKey="true" nullable="false" /> </column> <column name="level" type="integer"> <constraints nullable="false" /> </column> <column name="timestamp" type="timestamptz"> <constraints nullable="false" /> </column> <column name="variableid" type="integer" /> <column name="fingerprint" type="jsonb" /> <column name="message" type="text"> <constraints nullable="false" /> </column> </createTable> <sql> GRANT select, insert, delete, update ON TABLE changedetectionlog TO "${quarkus.datasource.username}"; ALTER TABLE changedetectionlog ENABLE ROW LEVEL SECURITY; CREATE POLICY cdl_all ON changedetectionlog FOR ALL USING (has_role('horreum.system')); </sql> <createSequence sequenceName="changedetectionlog_id_generator" startValue="1" incrementBy="1" cacheSize="1" /> <sql> select setval('changedetectionlog_id_generator', (select max(id)+1 from changedetectionlog), false); GRANT ALL ON SEQUENCE changedetectionlog_id_generator TO "${quarkus.datasource.username}"; </sql> </changeSet> <changeSet id="121" author="lampajr"> <validCheckSum>ANY</validCheckSum> <!-- we are using actionlog_id_generator as sequence for actionlog table --> <dropSequence sequenceName="actionlog_seq" /> <sql> <!-- reset sequences that have not been properly reset in changeset 107 --> select setval('allowedsite_seq', (select max(id)+50 from allowedsite), false); select setval('banner_seq', (select max(id)+50 from banner), false); select setval('change_seq', (select max(id)+50 from change), false); select setval('datapoint_seq', (select max(id)+50 from datapoint), false); select setval('datasetlog_seq', (select max(id)+50 from datasetlog), false); select setval('notificationsettings_seq', (select max(id)+50 from notificationsettings), false); select setval('reportcomment_seq', (select max(id)+50 from reportcomment), false); select setval('reportcomponent_seq', (select max(id)+50 from reportcomponent), false); select setval('reportlog_seq', (select max(id)+50 from reportlog), false); select setval('run_expectation_seq', (select max(id)+50 from run_expectation), false); select setval('tablereport_seq', (select max(id)+50 from tablereport), false); select setval('tablereportconfig_seq', (select max(id)+50 from tablereportconfig), false); select setval('transformationlog_seq', (select max(id)+50 from transformationlog), false); select setval('changedetectionidgenerator', (select max(id)+1 from changedetection), false); select setval('experimentprofileidgenerator', (select max(id)+1 from experiment_profile), false); select setval('mdridgenerator', (select max(id)+1 from missingdata_rule), false); select setval('subscriptionidgenerator', (select max(id)+1 from watch), false); select setval('tokenidgenerator', (select max(id)+1 from test_token), false); select setval('transformeridgenerator', (select max(id)+1 from transformer), false); select setval('variableidgenerator', (select max(id)+1 from variable), false); select setval('viewcomponentidgenerator', (select max(id)+1 from viewcomponent), false); select setval('viewidgenerator', (select max(id)+1 from view), false); </sql> </changeSet> <changeSet id="122" author="lampajr"> <validCheckSum>ANY</validCheckSum> <createIndex tableName="dataset" indexName="dataset_testid_key"> <column name="testid" /> </createIndex> <sql> CREATE INDEX idx_label_filtering_partial ON label (id) WHERE filtering = TRUE; CREATE INDEX idx_label_metrics_partial ON label (id) WHERE metrics = TRUE; </sql> </changeSet> <changeSet id="123" author="barreiro"> <validCheckSum>ANY</validCheckSum> <createSequence sequenceName="userinfo_apikey_id_seq" startValue="1" incrementBy="1" cacheSize="1" /> <createTable tableName="userinfo_apikey"> <column name="id" type="bigint"/> <column name="hash" type="text"> <constraints nullable="false" unique="true"/> </column> <column name="username" type="text"> <constraints nullable="false"/> </column> <column name="name" type="text"/> <column name="type" type="smallint"/> <column name="revoked" type="bool"/> <column name="creation" type="timestamptz"/> <column name="access" type="timestamptz"/> <column name="active" type="bigint"/> </createTable> <createIndex tableName="userinfo_apikey" indexName="userinfo_apikey_hash"> <column name="hash" /> </createIndex> <addForeignKeyConstraint constraintName="fk_userinfo_apikey" baseTableName="userinfo_apikey" baseColumnNames="username" referencedTableName="userinfo" referencedColumnNames="username"/> <sql> GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE userinfo_apikey TO "${quarkus.datasource.username}"; GRANT ALL ON SEQUENCE userinfo_apikey_id_seq TO "${quarkus.datasource.username}"; ALTER TABLE userinfo_apikey ENABLE ROW LEVEL SECURITY; CREATE POLICY userinfo_apikey_read ON userinfo_apikey USING (has_role('horreum.system')); CREATE POLICY userinfo_apikey_rw ON userinfo_apikey FOR ALL USING (has_role(username)); </sql> </changeSet> <changeSet id="124" author="barreiro"> <validCheckSum>ANY</validCheckSum> <!-- remove test token feature --> <sql> ALTER POLICY run_insert ON run WITH CHECK (has_role2(owner, 'uploader')); ALTER POLICY run_select ON run USING (can_view2(access, owner) OR has_role('horreum.system')); ALTER POLICY rs_insert ON run_schemas WITH CHECK (is_uploader_for_run(runid) OR has_role2((SELECT owner FROM schema WHERE id = schemaid), 'tester')); ALTER POLICY rs_select ON run_schemas USING (exists(SELECT 1 FROM run WHERE run.id = runid AND can_view2(access, owner))); ALTER POLICY rve_select ON run_validationerrors USING (exists(SELECT 1 FROM run WHERE id = run_id AND can_view2(access, owner))); ALTER POLICY schema_select ON schema USING (can_view2(access, owner) OR has_role2(owner, 'uploader')); ALTER POLICY test_delete ON test USING (has_role2(owner, 'tester')); ALTER POLICY test_select ON test USING (can_view2(access, owner)); ALTER POLICY test_update ON test USING (has_role2(owner, 'tester')); ALTER POLICY variable_delete ON variable USING (has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester')); ALTER POLICY variable_insert ON variable WITH CHECK (has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester')); ALTER POLICY variable_select ON variable USING (exists(SELECT 1 FROM test WHERE test.id = testid AND can_view2(test.access, test.owner))); ALTER POLICY variable_update ON variable USING (has_role2((SELECT owner FROM test WHERE test.id = testid), 'tester')); ALTER POLICY view_delete ON view USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY view_insert ON view WITH CHECK (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY view_select ON view USING (exists(SELECT 1 FROM test WHERE test.id = test_id AND can_view2(test.access, test.owner))); ALTER POLICY view_update ON view USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY vc_delete ON viewcomponent USING (exists(SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND has_role2(test.owner, 'tester'))); ALTER POLICY vc_insert ON viewcomponent WITH CHECK (exists(SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND has_role2(test.owner, 'tester'))); ALTER POLICY vc_select ON viewcomponent USING (exists(SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND can_view2(test.access, test.owner))); ALTER POLICY vc_update ON viewcomponent USING (exists(SELECT 1 FROM test JOIN view ON view.test_id = test.id WHERE view.id = view_id AND has_role2(test.owner, 'tester'))); ALTER POLICY ss_delete ON missingdata_rule USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY ss_insert ON missingdata_rule WITH CHECK (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY ss_select ON missingdata_rule USING (exists(SELECT 1 FROM test WHERE test.id = test_id AND can_view2(test.access, test.owner))); ALTER POLICY ss_update ON missingdata_rule USING (has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY rd_delete ON changedetection USING (exists(SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (has_role2(test.owner, 'tester')))); ALTER POLICY rd_insert ON changedetection WITH CHECK (exists(SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (has_role2(test.owner, 'tester')))); ALTER POLICY rd_select ON changedetection USING (exists(SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (can_view2(test.access, test.owner)))); ALTER POLICY rd_update ON changedetection USING (exists(SELECT 1 FROM test JOIN variable ON test.id = variable.testid WHERE variable.id = variable_id AND (has_role2(test.owner, 'tester')))); ALTER POLICY ep_delete ON experiment_profile USING (exists(SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester')))); ALTER POLICY ep_insert ON experiment_profile WITH CHECK (exists(SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester')))); ALTER POLICY ep_select ON experiment_profile USING(exists(SELECT 1 FROM test WHERE test.id = test_id AND (can_view2(access, owner)) OR has_role('horreum.system'))); ALTER POLICY ep_update ON experiment_profile USING (exists(SELECT 1 FROM test WHERE test.id = test_id AND (has_role2(owner, 'tester')))); ALTER POLICY ec_delete ON experiment_comparisons USING (exists(SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester')))); ALTER POLICY ec_insert ON experiment_comparisons WITH CHECK (exists(SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester')))); ALTER POLICY ec_select ON experiment_comparisons USING (exists(SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (can_view2(access, owner)) OR has_role('horreum.system'))); ALTER POLICY ec_update ON experiment_comparisons USING (exists(SELECT 1 FROM experiment_profile ep JOIN test ON test.id = test_id WHERE profile_id = ep.id AND (has_role2(owner, 'tester')))); ALTER POLICY action_policy ON action USING (has_role('horreum.system') OR has_role('admin') OR has_role2((SELECT owner FROM test WHERE test.id = test_id), 'tester')); ALTER POLICY dataset_insert ON dataset WITH CHECK (has_role2(owner, 'uploader') OR has_role('horreum.system')); DROP FUNCTION can_view; <!-- calls to this function are replaced with existing `can_view2` --> DROP FUNCTION has_read_token; DROP FUNCTION has_modify_token; DROP FUNCTION has_upload_token; DROP FUNCTION auth_suffix; </sql> <dropColumn tableName="run" columnName="token" /> <dropColumn tableName="schema" columnName="token" /> <dropTable tableName="test_token" /> </changeSet> <changeSet id="125" author="lampajr"> <validCheckSum>ANY</validCheckSum> <sql> -- still need db procedure until https://hibernate.atlassian.net/browse/HHH-17314 is fixed in quarkus CREATE OR REPLACE PROCEDURE calc_dataset_view(datasetId bigint) AS $$ BEGIN WITH view_agg AS ( SELECT vc.view_id, vc.id as vcid, array_agg(DISTINCT label.id) as label_ids, jsonb_object_agg(label.name, lv.value) as value FROM dataset_schemas ds JOIN label ON label.schema_id = ds.schema_id JOIN viewcomponent vc ON vc.labels ? label.name JOIN label_values lv ON lv.label_id = label.id AND lv.dataset_id = ds.dataset_id WHERE ds.dataset_id = datasetId AND vc.view_id IN (SELECT view.id FROM view JOIN dataset ON view.test_id = dataset.testid WHERE dataset.id = datasetId) GROUP BY vc.view_id, vcid ) INSERT INTO dataset_view (dataset_id, view_id, label_ids, value) SELECT datasetId, view_id, array_agg(DISTINCT label_id), jsonb_object_agg(vcid, value) FROM view_agg, unnest(label_ids) as label_id GROUP BY view_id; END $$ LANGUAGE plpgsql; </sql> </changeSet> </databaseChangeLog>
© 2015 - 2025 Weber Informatics LLC | Privacy Policy