diff --git a/src/main/java/cz/startnet/utils/pgdiff/PgDiffTriggers.java b/src/main/java/cz/startnet/utils/pgdiff/PgDiffTriggers.java index dd48dadc..5a0e038a 100644 --- a/src/main/java/cz/startnet/utils/pgdiff/PgDiffTriggers.java +++ b/src/main/java/cz/startnet/utils/pgdiff/PgDiffTriggers.java @@ -5,8 +5,8 @@ */ package cz.startnet.utils.pgdiff; +import cz.startnet.utils.pgdiff.schema.PgRelation; import cz.startnet.utils.pgdiff.schema.PgSchema; -import cz.startnet.utils.pgdiff.schema.PgTable; import cz.startnet.utils.pgdiff.schema.PgTrigger; import java.io.PrintWriter; import java.util.ArrayList; @@ -30,17 +30,17 @@ public class PgDiffTriggers { public static void createTriggers(final PrintWriter writer, final PgSchema oldSchema, final PgSchema newSchema, final SearchPathHelper searchPathHelper) { - for (final PgTable newTable : newSchema.getTables()) { - final PgTable oldTable; + for (final PgRelation newRelation : newSchema.getRels()) { + final PgRelation oldRelation; if (oldSchema == null) { - oldTable = null; + oldRelation = null; } else { - oldTable = oldSchema.getTable(newTable.getName()); + oldRelation = oldSchema.getRelation(newRelation.getName()); } // Add new triggers - for (final PgTrigger trigger : getNewTriggers(oldTable, newTable)) { + for (final PgTrigger trigger : getNewTriggers(oldRelation, newRelation)) { searchPathHelper.outputSearchPath(writer); writer.println(); writer.println(trigger.getCreationSQL()); @@ -59,18 +59,18 @@ public static void createTriggers(final PrintWriter writer, public static void dropTriggers(final PrintWriter writer, final PgSchema oldSchema, final PgSchema newSchema, final SearchPathHelper searchPathHelper) { - for (final PgTable newTable : newSchema.getTables()) { - final PgTable oldTable; + for (final PgRelation newRelation : newSchema.getRels()) { + final PgRelation oldRelation; if (oldSchema == null) { - oldTable = null; + oldRelation = null; } else { - oldTable = oldSchema.getTable(newTable.getName()); + oldRelation = oldSchema.getRelation(newRelation.getName()); } // Drop triggers that no more exist or are modified for (final PgTrigger trigger : - getDropTriggers(oldTable, newTable)) { + getDropTriggers(oldRelation, newRelation)) { searchPathHelper.outputSearchPath(writer); writer.println(); writer.println(trigger.getDropSQL()); @@ -81,20 +81,20 @@ public static void dropTriggers(final PrintWriter writer, /** * Returns list of triggers that should be dropped. * - * @param oldTable original table - * @param newTable new table + * @param oldRelation original relation + * @param newRelation new relation * * @return list of triggers that should be dropped */ - private static List getDropTriggers(final PgTable oldTable, - final PgTable newTable) { + private static List getDropTriggers(final PgRelation oldRelation, + final PgRelation newRelation) { @SuppressWarnings("CollectionWithoutInitialCapacity") final List list = new ArrayList(); - if (newTable != null && oldTable != null) { - final List newTriggers = newTable.getTriggers(); + if (newRelation != null && oldRelation != null) { + final List newTriggers = newRelation.getTriggers(); - for (final PgTrigger oldTrigger : oldTable.getTriggers()) { + for (final PgTrigger oldTrigger : oldRelation.getTriggers()) { if (!newTriggers.contains(oldTrigger)) { list.add(oldTrigger); } @@ -107,22 +107,22 @@ private static List getDropTriggers(final PgTable oldTable, /** * Returns list of triggers that should be added. * - * @param oldTable original table - * @param newTable new table + * @param oldRelation original relation + * @param newRelation new relation * * @return list of triggers that should be added */ - private static List getNewTriggers(final PgTable oldTable, - final PgTable newTable) { + private static List getNewTriggers(final PgRelation oldRelation, + final PgRelation newRelation) { @SuppressWarnings("CollectionWithoutInitialCapacity") final List list = new ArrayList(); - if (newTable != null) { - if (oldTable == null) { - list.addAll(newTable.getTriggers()); + if (newRelation != null) { + if (oldRelation == null) { + list.addAll(newRelation.getTriggers()); } else { - for (final PgTrigger newTrigger : newTable.getTriggers()) { - if (!oldTable.getTriggers().contains(newTrigger)) { + for (final PgTrigger newTrigger : newRelation.getTriggers()) { + if (!oldRelation.getTriggers().contains(newTrigger)) { list.add(newTrigger); } } @@ -147,16 +147,16 @@ public static void alterComments(final PrintWriter writer, return; } - for (PgTable oldTable : oldSchema.getTables()) { - final PgTable newTable = newSchema.getTable(oldTable.getName()); + for (PgRelation oldRelation : oldSchema.getRels()) { + final PgRelation newRelation = newSchema.getRelation(oldRelation.getName()); - if (newTable == null) { + if (newRelation == null) { continue; } - for (final PgTrigger oldTrigger : oldTable.getTriggers()) { + for (final PgTrigger oldTrigger : oldRelation.getTriggers()) { final PgTrigger newTrigger = - newTable.getTrigger(oldTrigger.getName()); + newRelation.getTrigger(oldTrigger.getName()); if (newTrigger == null) { continue; @@ -175,7 +175,7 @@ public static void alterComments(final PrintWriter writer, PgDiffUtils.getQuotedName(newTrigger.getName())); writer.print(" ON "); writer.print(PgDiffUtils.getQuotedName( - newTrigger.getTableName())); + newTrigger.getRelationName())); writer.print(" IS "); writer.print(newTrigger.getComment()); writer.println(';'); @@ -188,7 +188,7 @@ public static void alterComments(final PrintWriter writer, PgDiffUtils.getQuotedName(newTrigger.getName())); writer.print(" ON "); writer.print(PgDiffUtils.getQuotedName( - newTrigger.getTableName())); + newTrigger.getRelationName())); writer.println(" IS NULL;"); } } diff --git a/src/main/java/cz/startnet/utils/pgdiff/parsers/CreateTriggerParser.java b/src/main/java/cz/startnet/utils/pgdiff/parsers/CreateTriggerParser.java index 168d5766..3b5a9953 100644 --- a/src/main/java/cz/startnet/utils/pgdiff/parsers/CreateTriggerParser.java +++ b/src/main/java/cz/startnet/utils/pgdiff/parsers/CreateTriggerParser.java @@ -35,9 +35,11 @@ public static void parse(final PgDatabase database, trigger.setName(objectName); if (parser.expectOptional("BEFORE")) { - trigger.setBefore(true); + trigger.setEventTimeQualification(PgTrigger.EventTimeQualification.before); } else if (parser.expectOptional("AFTER")) { - trigger.setBefore(false); + trigger.setEventTimeQualification(PgTrigger.EventTimeQualification.after); + } else if (parser.expectOptional("INSTEAD OF")) { + trigger.setEventTimeQualification(PgTrigger.EventTimeQualification.instead_of); } boolean first = true; @@ -70,9 +72,9 @@ public static void parse(final PgDatabase database, parser.expect("ON"); - final String tableName = parser.parseIdentifier(); + final String relationName = parser.parseIdentifier(); - trigger.setTableName(ParserUtils.getObjectName(tableName)); + trigger.setRelationName(ParserUtils.getObjectName(relationName)); if (parser.expectOptional("FOR")) { parser.expectOptional("EACH"); @@ -100,9 +102,9 @@ public static void parse(final PgDatabase database, || "_slony_denyaccess".equals(trigger.getName())); if (!ignoreSlonyTrigger) { - final PgSchema tableSchema = database.getSchema( - ParserUtils.getSchemaName(tableName, database)); - tableSchema.getTable(trigger.getTableName()).addTrigger(trigger); + final PgSchema schema = database.getSchema( + ParserUtils.getSchemaName(relationName, database)); + schema.getRelation(trigger.getRelationName()).addTrigger(trigger); } } diff --git a/src/main/java/cz/startnet/utils/pgdiff/schema/PgTrigger.java b/src/main/java/cz/startnet/utils/pgdiff/schema/PgTrigger.java index 96be3d44..007d67b0 100644 --- a/src/main/java/cz/startnet/utils/pgdiff/schema/PgTrigger.java +++ b/src/main/java/cz/startnet/utils/pgdiff/schema/PgTrigger.java @@ -9,6 +9,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.HashMap; +import java.util.Map; /** * Stores trigger information. @@ -17,6 +19,31 @@ */ public class PgTrigger { + /** + * Enumeration of when, with respect to event, a trigger should fire. + * e.g. BEFORE, AFTER or INSTEAD OF an event. + */ + public enum EventTimeQualification { + before, + after, + instead_of; + + private static final Map stringRepresentation; + + static { + HashMap aMap = new HashMap(); + aMap.put(EventTimeQualification.before, "BEFORE"); + aMap.put(EventTimeQualification.after, "AFTER"); + aMap.put(EventTimeQualification.instead_of, "INSTEAD OF"); + + stringRepresentation = Collections.unmodifiableMap(aMap); + } + + public static String toString(EventTimeQualification eventTimeQualification) { + return stringRepresentation.get(eventTimeQualification); + } + } + /** * Function name and arguments that should be fired on the trigger. */ @@ -26,14 +53,14 @@ public class PgTrigger { */ private String name; /** - * Name of the table the trigger is defined on. + * Name of the relation the trigger is defined on. */ - private String tableName; + private String relationName; /** - * Whether the trigger should be fired BEFORE or AFTER action. Default is - * before. + * Whether the trigger should be fired BEFORE, AFTER or INSTEAD OF an event. + * Default is before. */ - private boolean before = true; + private EventTimeQualification eventTimeQualification = EventTimeQualification.before; /** * Whether the trigger should be fired FOR EACH ROW or FOR EACH STATEMENT. * Default is FOR EACH STATEMENT. @@ -70,21 +97,21 @@ public class PgTrigger { private String comment; /** - * Setter for {@link #before}. + * Setter for {@link #eventTimeQualification}. * - * @param before {@link #before} + * @param eventTimeQualification {@link #eventTimeQualification} */ - public void setBefore(final boolean before) { - this.before = before; + public void setEventTimeQualification(final EventTimeQualification eventTimeQualification) { + this.eventTimeQualification = eventTimeQualification; } /** - * Getter for {@link #before}. + * Getter for {@link #eventTimeQualification}. * - * @return {@link #before} + * @return {@link #eventTimeQualification} */ - public boolean isBefore() { - return before; + public EventTimeQualification getEventTimeQualification() { + return eventTimeQualification; } /** @@ -116,7 +143,7 @@ public String getCreationSQL() { sbSQL.append(PgDiffUtils.getQuotedName(getName())); sbSQL.append(System.getProperty("line.separator")); sbSQL.append("\t"); - sbSQL.append(isBefore() ? "BEFORE" : "AFTER"); + sbSQL.append(EventTimeQualification.toString(getEventTimeQualification())); boolean firstEvent = true; @@ -169,7 +196,7 @@ public String getCreationSQL() { } sbSQL.append(" ON "); - sbSQL.append(PgDiffUtils.getQuotedName(getTableName())); + sbSQL.append(PgDiffUtils.getQuotedName(getRelationName())); sbSQL.append(System.getProperty("line.separator")); sbSQL.append("\tFOR EACH "); sbSQL.append(isForEachRow() ? "ROW" : "STATEMENT"); @@ -192,7 +219,7 @@ public String getCreationSQL() { sbSQL.append("COMMENT ON TRIGGER "); sbSQL.append(PgDiffUtils.getQuotedName(name)); sbSQL.append(" ON "); - sbSQL.append(PgDiffUtils.getQuotedName(tableName)); + sbSQL.append(PgDiffUtils.getQuotedName(relationName)); sbSQL.append(" IS "); sbSQL.append(comment); sbSQL.append(';'); @@ -208,7 +235,7 @@ public String getCreationSQL() { */ public String getDropSQL() { return "DROP TRIGGER " + PgDiffUtils.getQuotedName(getName()) + " ON " - + PgDiffUtils.getQuotedName(getTableName()) + ";"; + + PgDiffUtils.getQuotedName(getRelationName()) + ";"; } /** @@ -338,21 +365,21 @@ public void setOnTruncate(final boolean onTruncate) { } /** - * Setter for {@link #tableName}. + * Setter for {@link #relationName}. * - * @param tableName {@link #tableName} + * @param relationName {@link #relationName} */ - public void setTableName(final String tableName) { - this.tableName = tableName; + public void setRelationName(final String relationName) { + this.relationName = relationName; } /** - * Getter for {@link #tableName}. + * Getter for {@link #relationName}. * - * @return {@link #tableName} + * @return {@link #relationName} */ - public String getTableName() { - return tableName; + public String getRelationName() { + return relationName; } /** @@ -399,7 +426,7 @@ public boolean equals(final Object object) { equals = true; } else if (object instanceof PgTrigger) { final PgTrigger trigger = (PgTrigger) object; - equals = (before == trigger.isBefore()) + equals = (eventTimeQualification == trigger.getEventTimeQualification()) && (forEachRow == trigger.isForEachRow()) && function.equals(trigger.getFunction()) && name.equals(trigger.getName()) @@ -407,7 +434,7 @@ public boolean equals(final Object object) { && (onInsert == trigger.isOnInsert()) && (onUpdate == trigger.isOnUpdate()) && (onTruncate == trigger.isOnTruncate()) - && tableName.equals(trigger.getTableName()); + && relationName.equals(trigger.getRelationName()); if (equals) { final List sorted1 = @@ -426,8 +453,8 @@ public boolean equals(final Object object) { @Override public int hashCode() { - return (getClass().getName() + "|" + before + "|" + forEachRow + "|" + return (getClass().getName() + "|" + eventTimeQualification + "|" + forEachRow + "|" + function + "|" + name + "|" + onDelete + "|" + onInsert + "|" - + onUpdate + "|" + onTruncate + "|" + tableName).hashCode(); + + onUpdate + "|" + onTruncate + "|" + relationName).hashCode(); } } diff --git a/src/test/java/cz/startnet/utils/pgdiff/PgDiffTest.java b/src/test/java/cz/startnet/utils/pgdiff/PgDiffTest.java index 973ef517..a21e84bb 100644 --- a/src/test/java/cz/startnet/utils/pgdiff/PgDiffTest.java +++ b/src/test/java/cz/startnet/utils/pgdiff/PgDiffTest.java @@ -227,7 +227,9 @@ public static Collection parameters() { {"drop_unlogged_table", false, false, false, false}, // Test scenarios where /**/ comments. {"add_table_issue115", false, false, false, false}, - {"add_column_issue134", false, false, false, false} + {"add_column_issue134", false, false, false, false}, + // Tests view triggers (support for 'INSTEAD OF') + {"view_triggers", false, false, false, false} }); } /** diff --git a/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_diff.sql b/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_diff.sql new file mode 100644 index 00000000..e0bd5fca --- /dev/null +++ b/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_diff.sql @@ -0,0 +1,18 @@ +DROP TRIGGER trg_testview_instead_of_delete ON testview; + +DROP TRIGGER trg_testview_instead_of_insert ON testview; + +CREATE TRIGGER trg_testview_instead_of_delete_new_name + INSTEAD OF DELETE ON testview + FOR EACH ROW + EXECUTE PROCEDURE fn_trg_testview(); + +CREATE TRIGGER trg_testview_before_update + BEFORE UPDATE ON testview + FOR EACH ROW + EXECUTE PROCEDURE fn_trg_testview(); + +CREATE TRIGGER trg_testview_after_insert + AFTER INSERT ON testview + FOR EACH ROW + EXECUTE PROCEDURE fn_trg_testview(); diff --git a/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_new.sql b/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_new.sql new file mode 100644 index 00000000..d4f04b74 --- /dev/null +++ b/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_new.sql @@ -0,0 +1,74 @@ +-- +-- PostgreSQL database dump +-- + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = off; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET escape_string_warning = off; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres +-- + +COMMENT ON SCHEMA public IS 'Standard public schema'; + + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: testtable; Type: TABLE; Schema: public; Owner: fordfrog; Tablespace: +-- + +CREATE TABLE testtable ( + id bigint, + name character varying(30) +); + + +ALTER TABLE public.testtable OWNER TO fordfrog; + +-- +-- Name: testview; Type: VIEW; Schema: public; Owner: fordfrog +-- + +CREATE VIEW testview AS + SELECT testtable.id, testtable.name FROM testtable; + + +ALTER TABLE public.testview OWNER TO fordfrog; + +CREATE FUNCTION fn_trg_testview() RETURNS trigger LANGUAGE plpgsql +AS $$ +BEGIN + -- do nothing + RETURN OLD; +END; +$$; + +ALTER FUNCTION public.fn_trg_testview() OWNER TO fordfrog; + +CREATE TRIGGER trg_testview_instead_of_delete_new_name INSTEAD OF DELETE ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); +CREATE TRIGGER trg_testview_instead_of_update INSTEAD OF UPDATE ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); +CREATE TRIGGER trg_testview_before_update before UPDATE ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); +CREATE TRIGGER trg_testview_after_insert after INSERT ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); + +-- +-- Name: public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; + +REVOKE ALL ON FUNCTION fn_trg_testview() FROM PUBLIC; +REVOKE ALL ON FUNCTION fn_trg_testview() FROM postgres; +GRANT ALL ON FUNCTION fn_trg_testview() TO PUBLIC; +GRANT ALL ON FUNCTION fn_trg_testview() TO postgres; + diff --git a/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_original.sql b/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_original.sql new file mode 100644 index 00000000..ad0e7d65 --- /dev/null +++ b/src/test/resources/cz/startnet/utils/pgdiff/view_triggers_original.sql @@ -0,0 +1,73 @@ +-- +-- PostgreSQL database dump +-- + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = off; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET escape_string_warning = off; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres +-- + +COMMENT ON SCHEMA public IS 'Standard public schema'; + + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: testtable; Type: TABLE; Schema: public; Owner: fordfrog; Tablespace: +-- + +CREATE TABLE testtable ( + id bigint, + name character varying(30) +); + + +ALTER TABLE public.testtable OWNER TO fordfrog; + +-- +-- Name: testview; Type: VIEW; Schema: public; Owner: fordfrog +-- + +CREATE VIEW testview AS + SELECT testtable.id, testtable.name FROM testtable; + + +ALTER TABLE public.testview OWNER TO fordfrog; + +CREATE FUNCTION fn_trg_testview() RETURNS trigger LANGUAGE plpgsql +AS $$ +BEGIN + -- do nothing + RETURN OLD; +END; +$$; + +ALTER FUNCTION public.fn_trg_testview() OWNER TO fordfrog; + +CREATE TRIGGER trg_testview_instead_of_delete INSTEAD OF DELETE ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); +CREATE TRIGGER trg_testview_instead_of_insert INSTEAD OF INSERT ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); +CREATE TRIGGER trg_testview_instead_of_update INSTEAD OF UPDATE ON testview FOR EACH ROW EXECUTE PROCEDURE fn_trg_testview(); + +-- +-- Name: public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +REVOKE ALL ON SCHEMA public FROM postgres; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO PUBLIC; + +REVOKE ALL ON FUNCTION fn_trg_testview() FROM PUBLIC; +REVOKE ALL ON FUNCTION fn_trg_testview() FROM postgres; +GRANT ALL ON FUNCTION fn_trg_testview() TO PUBLIC; +GRANT ALL ON FUNCTION fn_trg_testview() TO postgres; +