|
| 1 | +/*- |
| 2 | + * #%L |
| 3 | + * JSQLParser library |
| 4 | + * %% |
| 5 | + * Copyright (C) 2004 - 2026 JSQLParser |
| 6 | + * %% |
| 7 | + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 |
| 8 | + * #L% |
| 9 | + */ |
| 10 | +package net.sf.jsqlparser.statement.create; |
| 11 | + |
| 12 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 13 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
| 14 | +import static org.junit.jupiter.api.Assertions.assertNotSame; |
| 15 | +import static org.junit.jupiter.api.Assertions.assertNull; |
| 16 | +import static org.junit.jupiter.api.Assertions.assertSame; |
| 17 | + |
| 18 | +import java.util.ArrayList; |
| 19 | +import java.util.List; |
| 20 | +import net.sf.jsqlparser.JSQLParserException; |
| 21 | +import net.sf.jsqlparser.parser.CCJSqlParserUtil; |
| 22 | +import net.sf.jsqlparser.schema.Table; |
| 23 | +import net.sf.jsqlparser.statement.ReferentialAction; |
| 24 | +import net.sf.jsqlparser.statement.ReferentialAction.Action; |
| 25 | +import net.sf.jsqlparser.statement.ReferentialAction.Type; |
| 26 | +import net.sf.jsqlparser.statement.Statement; |
| 27 | +import net.sf.jsqlparser.statement.alter.Alter; |
| 28 | +import net.sf.jsqlparser.statement.create.table.ConstraintAttributes; |
| 29 | +import net.sf.jsqlparser.statement.create.table.CreateTable; |
| 30 | +import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex; |
| 31 | +import net.sf.jsqlparser.statement.create.table.ForeignKeyReference; |
| 32 | +import net.sf.jsqlparser.util.deparser.StatementDeParser; |
| 33 | +import org.junit.jupiter.api.Test; |
| 34 | +import org.junit.jupiter.params.ParameterizedTest; |
| 35 | +import org.junit.jupiter.params.provider.ValueSource; |
| 36 | + |
| 37 | +class ForeignKeyReferenceMutationTest { |
| 38 | + @Test |
| 39 | + void keepsLegacyAndStructuredAccessOnTheSameValues() { |
| 40 | + ForeignKeyIndex index = new ForeignKeyIndex(); |
| 41 | + Table table = new Table("parent"); |
| 42 | + List<String> columns = new ArrayList<>(List.of("id")); |
| 43 | + index.setTable(table); |
| 44 | + index.setReferencedColumnNames(columns); |
| 45 | + index.setReferentialAction(Type.DELETE, Action.CASCADE); |
| 46 | + |
| 47 | + ForeignKeyReference reference = index.getReference(); |
| 48 | + assertSame(table, reference.getTable()); |
| 49 | + assertSame(columns, reference.getReferencedColumnNames()); |
| 50 | + assertEquals(Action.CASCADE, reference.getReferentialAction(Type.DELETE).getAction()); |
| 51 | + |
| 52 | + Table replacement = new Table("other_parent"); |
| 53 | + reference.setTable(replacement); |
| 54 | + reference.setReferencedColumnNames(new ArrayList<>(List.of("other_id"))); |
| 55 | + reference.setReferentialAction(Type.DELETE, Action.RESTRICT); |
| 56 | + assertSame(replacement, index.getTable()); |
| 57 | + assertSame(reference.getReferencedColumnNames(), index.getReferencedColumnNames()); |
| 58 | + assertEquals(Action.RESTRICT, index.getReferentialAction(Type.DELETE).getAction()); |
| 59 | + |
| 60 | + index.addReferencedColumnNames("tenant_id"); |
| 61 | + index.getReferencedColumnNames().remove("other_id"); |
| 62 | + assertEquals(List.of("tenant_id"), reference.getReferencedColumnNames()); |
| 63 | + index.removeReferentialAction(Type.DELETE); |
| 64 | + assertNull(reference.getReferentialAction(Type.DELETE)); |
| 65 | + reference.getReferentialActions().add(new ReferentialAction(Type.UPDATE, Action.CASCADE)); |
| 66 | + assertSame(reference.getReferentialAction(Type.UPDATE), |
| 67 | + index.getReferentialAction(Type.UPDATE)); |
| 68 | + } |
| 69 | + |
| 70 | + @Test |
| 71 | + void preservesNullableLegacyFieldsAndAnEmptyReference() { |
| 72 | + ForeignKeyIndex index = new ForeignKeyIndex(); |
| 73 | + index.setReference(null); |
| 74 | + assertNull(index.getTable()); |
| 75 | + assertNull(index.getReferencedColumnNames()); |
| 76 | + assertNull(index.getReferentialAction(Type.DELETE)); |
| 77 | + assertNull(index.getMatchType()); |
| 78 | + index.setTable(new Table("parent")); |
| 79 | + index.setReferencedColumnNames(new ArrayList<>(List.of("id"))); |
| 80 | + index.setTable(null); |
| 81 | + index.setReferencedColumnNames(null); |
| 82 | + assertNull(index.getReference().getTable()); |
| 83 | + assertNull(index.getReference().getReferencedColumnNames()); |
| 84 | + } |
| 85 | + |
| 86 | + @Test |
| 87 | + void clearingReferenceRetainsLegacyValuesAndDiscardsReferenceOnlyOptions() { |
| 88 | + ForeignKeyReference reference = new ForeignKeyReference() |
| 89 | + .withTable(new Table("parent")) |
| 90 | + .withReferencedColumnNames(new ArrayList<>(List.of("id"))) |
| 91 | + .withMatchType(ForeignKeyReference.MatchType.FULL) |
| 92 | + .withUsingPeriod(true) |
| 93 | + .withReferentialAction(Type.DELETE, Action.CASCADE); |
| 94 | + reference.setConstraintName("column_fk"); |
| 95 | + reference.setConstraintAttributes(new ConstraintAttributes()); |
| 96 | + ForeignKeyIndex index = new ForeignKeyIndex().withReference(reference); |
| 97 | + |
| 98 | + index.setReference(null); |
| 99 | + |
| 100 | + assertSame(reference.getTable(), index.getTable()); |
| 101 | + assertSame(reference.getReferencedColumnNames(), index.getReferencedColumnNames()); |
| 102 | + assertEquals(Action.CASCADE, index.getReferentialAction(Type.DELETE).getAction()); |
| 103 | + assertNull(index.getMatchType()); |
| 104 | + ForeignKeyReference detached = index.getReference(); |
| 105 | + assertNotSame(reference, detached); |
| 106 | + assertFalse(detached.isUsingPeriod()); |
| 107 | + assertNull(detached.getConstraintName()); |
| 108 | + assertNull(detached.getConstraintAttributes()); |
| 109 | + } |
| 110 | + |
| 111 | + @Test |
| 112 | + void clearingReferenceRetainsTheLatestStructuredValues() { |
| 113 | + ForeignKeyReference reference = new ForeignKeyReference() |
| 114 | + .withTable(new Table("parent")) |
| 115 | + .withReferencedColumnNames(new ArrayList<>(List.of("id"))); |
| 116 | + ForeignKeyIndex index = new ForeignKeyIndex().withReference(reference); |
| 117 | + Table replacement = new Table("other_parent"); |
| 118 | + List<String> columns = new ArrayList<>(List.of("other_id")); |
| 119 | + reference.setTable(replacement); |
| 120 | + reference.setReferencedColumnNames(columns); |
| 121 | + reference.setReferentialAction(Type.DELETE, Action.SET_NULL); |
| 122 | + reference.getReferentialAction(Type.DELETE).setColumnNames(List.of("child_id")); |
| 123 | + |
| 124 | + index.setReference(null); |
| 125 | + |
| 126 | + assertSame(replacement, index.getTable()); |
| 127 | + assertSame(columns, index.getReferencedColumnNames()); |
| 128 | + assertEquals(List.of("child_id"), index.getReferentialAction(Type.DELETE).getColumnNames()); |
| 129 | + assertEquals(List.of("child_id"), |
| 130 | + index.getReference().getReferentialAction(Type.DELETE).getColumnNames()); |
| 131 | + } |
| 132 | + |
| 133 | + @Test |
| 134 | + void accessingReferenceDoesNotReplaceActionsOrDiscardTheirColumns() { |
| 135 | + ForeignKeyIndex index = new ForeignKeyIndex(); |
| 136 | + index.setReferentialAction(Type.DELETE, Action.SET_NULL); |
| 137 | + ReferentialAction action = index.getReferentialAction(Type.DELETE); |
| 138 | + action.setColumnNames(List.of("child_id")); |
| 139 | + |
| 140 | + assertSame(action, index.getReference().getReferentialAction(Type.DELETE)); |
| 141 | + assertEquals(List.of("child_id"), index.getReferentialAction(Type.DELETE).getColumnNames()); |
| 142 | + action.setAction(Action.SET_DEFAULT); |
| 143 | + assertEquals(Action.SET_DEFAULT, |
| 144 | + index.getReference().getReferentialAction(Type.DELETE).getAction()); |
| 145 | + } |
| 146 | + |
| 147 | + @Test |
| 148 | + @SuppressWarnings("deprecation") |
| 149 | + void deprecatedActionSettersUpdateStructuredReference() { |
| 150 | + ForeignKeyIndex index = new ForeignKeyIndex(); |
| 151 | + ForeignKeyReference reference = index.getReference(); |
| 152 | + index.setOnDeleteReferenceOption("CASCADE"); |
| 153 | + index.setOnUpdateReferenceOption("RESTRICT"); |
| 154 | + assertEquals(Action.CASCADE, reference.getReferentialAction(Type.DELETE).getAction()); |
| 155 | + assertEquals("RESTRICT", index.getOnUpdateReferenceOption()); |
| 156 | + reference.setReferentialAction(Type.UPDATE, Action.CASCADE); |
| 157 | + assertEquals("CASCADE", index.getOnUpdateReferenceOption()); |
| 158 | + index.setOnDeleteReferenceOption(null); |
| 159 | + index.setOnUpdateReferenceOption(null); |
| 160 | + assertNull(reference.getReferentialAction(Type.DELETE)); |
| 161 | + assertNull(reference.getReferentialAction(Type.UPDATE)); |
| 162 | + } |
| 163 | + |
| 164 | + @ParameterizedTest |
| 165 | + @ValueSource(booleans = {false, true}) |
| 166 | + void mutationsSurviveBothRenderersAndReparsing(boolean create) throws JSQLParserException { |
| 167 | + String prefix = create ? "CREATE TABLE child (parent_id INT, " : "ALTER TABLE child ADD "; |
| 168 | + Statement statement = CCJSqlParserUtil.parse(prefix |
| 169 | + + "CONSTRAINT fk FOREIGN KEY (parent_id) REFERENCES parent (id) ON DELETE CASCADE" |
| 170 | + + (create ? ")" : "")); |
| 171 | + ForeignKeyIndex index = foreignKey(statement); |
| 172 | + ForeignKeyReference reference = index.getReference(); |
| 173 | + reference.setTable(new Table("other_parent")); |
| 174 | + reference.getReferencedColumnNames().set(0, "other_id"); |
| 175 | + index.setReferentialAction(Type.DELETE, Action.SET_NULL); |
| 176 | + index.getReferentialAction(Type.DELETE).setColumnNames(List.of("parent_id")); |
| 177 | + index.setMatchType(ForeignKeyReference.MatchType.SIMPLE); |
| 178 | + |
| 179 | + StringBuilder deparsed = new StringBuilder(); |
| 180 | + statement.accept(new StatementDeParser(deparsed)); |
| 181 | + assertEquals(statement.toString(), deparsed.toString()); |
| 182 | + Statement reparsed = CCJSqlParserUtil.parse(deparsed.toString()); |
| 183 | + assertEquals(deparsed.toString(), reparsed.toString()); |
| 184 | + ForeignKeyIndex result = foreignKey(reparsed); |
| 185 | + assertEquals("other_parent", result.getTable().getName()); |
| 186 | + assertEquals(List.of("other_id"), result.getReferencedColumnNames()); |
| 187 | + assertEquals(List.of("parent_id"), |
| 188 | + result.getReferentialAction(Type.DELETE).getColumnNames()); |
| 189 | + assertEquals(ForeignKeyReference.MatchType.SIMPLE, result.getMatchType()); |
| 190 | + } |
| 191 | + |
| 192 | + private static ForeignKeyIndex foreignKey(Statement statement) { |
| 193 | + return (ForeignKeyIndex) (statement instanceof CreateTable |
| 194 | + ? ((CreateTable) statement).getIndexes().get(0) |
| 195 | + : ((Alter) statement).getAlterExpressions().get(0).getIndex()); |
| 196 | + } |
| 197 | +} |
0 commit comments