|
| 1 | +/* |
| 2 | + * Copyright 1999-2017 Alibaba Group Holding Ltd. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package com.alibaba.druid.bvt.sql.postgresql.issues; |
| 17 | + |
| 18 | +import com.alibaba.druid.DbType; |
| 19 | +import com.alibaba.druid.sql.SQLUtils; |
| 20 | +import com.alibaba.druid.sql.ast.SQLStatement; |
| 21 | +import org.junit.Test; |
| 22 | + |
| 23 | +import java.util.List; |
| 24 | + |
| 25 | +import static org.junit.Assert.assertEquals; |
| 26 | +import static org.junit.Assert.assertTrue; |
| 27 | + |
| 28 | +/** |
| 29 | + * Regression test for JumpServer issue #16341. |
| 30 | + * |
| 31 | + * <p>Druid 1.2.28 fails to round-trip several PostgreSQL {@code ALTER COLUMN} forms: |
| 32 | + * <ul> |
| 33 | + * <li>{@code ALTER COLUMN ... TYPE <type> COLLATE <collation>} drops the COLLATE clause;</li> |
| 34 | + * <li>{@code ALTER COLUMN ... SET DATA TYPE <type>} throws a ParserException |
| 35 | + * (the SET branch only accepted NOT NULL / DEFAULT);</li> |
| 36 | + * <li>{@code ALTER COLUMN ... TYPE <type> USING <expr>} throws a ParserException |
| 37 | + * at the USING keyword.</li> |
| 38 | + * </ul> |
| 39 | + * |
| 40 | + * <p>Covered scenarios: |
| 41 | + * <ol> |
| 42 | + * <li>{@code TYPE} + {@code COLLATE};</li> |
| 43 | + * <li>{@code TYPE CHARACTER VARYING(n)};</li> |
| 44 | + * <li>{@code SET DATA TYPE};</li> |
| 45 | + * <li>{@code TYPE ... USING}.</li> |
| 46 | + * </ol> |
| 47 | + * |
| 48 | + * <p>The fix is a targeted backport of upstream commits |
| 49 | + * {@code d42545ff} (PG {@code SET DATA TYPE} / {@code COLLATE}, issues #6467 / #6573) and |
| 50 | + * {@code 26b856b1} (PG {@code TYPE ... USING}, issue #6064). |
| 51 | + */ |
| 52 | +public class JumpServer16341Test { |
| 53 | + private static String roundTrip(String sql) { |
| 54 | + List<SQLStatement> statements = SQLUtils.parseStatements(sql, DbType.postgresql); |
| 55 | + assertEquals(1, statements.size()); |
| 56 | + return SQLUtils.toSQLString(statements.get(0), DbType.postgresql); |
| 57 | + } |
| 58 | + |
| 59 | + /** Collapse all runs of whitespace into single spaces, for format-independent checks. */ |
| 60 | + private static String squash(String s) { |
| 61 | + return s.replaceAll("\\s+", " ").trim(); |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Scenario 1: ALTER COLUMN ... TYPE <char type> COLLATE <collation>. |
| 66 | + * |
| 67 | + * <p>The TYPE keyword and the COLLATE clause (with its quoted schema/name) must both survive |
| 68 | + * the round-trip; the output must not degrade to a bare {@code ALTER COLUMN col <type>} form. |
| 69 | + */ |
| 70 | + @Test |
| 71 | + public void scenario1_type_and_collate() { |
| 72 | + String sql = "ALTER TABLE \"public\".\"test_alter_table\" " |
| 73 | + + "ALTER COLUMN \"job_code\" " |
| 74 | + + "TYPE VARCHAR(255) COLLATE \"pg_catalog\".\"default\""; |
| 75 | + |
| 76 | + String out = roundTrip(sql); |
| 77 | + String upper = squash(out).toUpperCase(); |
| 78 | + |
| 79 | + // Type-change keyword (TYPE or SET DATA TYPE) must remain before the data type. |
| 80 | + assertTrue("output must keep TYPE/SET DATA TYPE before the data type: " + out, |
| 81 | + upper.contains("TYPE VARCHAR(255)")); |
| 82 | + |
| 83 | + // COLLATE keyword and its quoted content must be preserved exactly. |
| 84 | + assertTrue("COLLATE keyword must be present: " + out, upper.contains("COLLATE")); |
| 85 | + assertTrue("COLLATE content must be preserved: " + out, |
| 86 | + out.contains("\"pg_catalog\".\"default\"")); |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Scenario 2: ALTER COLUMN ... TYPE CHARACTER VARYING(n). |
| 91 | + * |
| 92 | + * <p>The TYPE keyword and the {@code CHARACTER VARYING(n)} type name must be preserved. |
| 93 | + */ |
| 94 | + @Test |
| 95 | + public void scenario2_type_character_varying() { |
| 96 | + String sql = "ALTER TABLE \"public\".\"test_alter_table\" " |
| 97 | + + "ALTER COLUMN \"job_code\" " |
| 98 | + + "TYPE CHARACTER VARYING(255)"; |
| 99 | + |
| 100 | + String out = roundTrip(sql); |
| 101 | + String upper = squash(out).toUpperCase(); |
| 102 | + |
| 103 | + assertTrue("output must keep TYPE/SET DATA TYPE: " + out, upper.contains("TYPE")); |
| 104 | + assertTrue("CHARACTER VARYING(255) must be preserved: " + out, |
| 105 | + upper.contains("CHARACTER VARYING(255)")); |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * Scenario 3: ALTER COLUMN ... SET DATA TYPE <type>. |
| 110 | + * |
| 111 | + * <p>Must parse without error and re-emit a legal type-change statement (SET DATA TYPE or |
| 112 | + * normalized to TYPE). |
| 113 | + */ |
| 114 | + @Test |
| 115 | + public void scenario3_set_data_type() { |
| 116 | + String sql = "ALTER TABLE test_alter_table " |
| 117 | + + "ALTER COLUMN job_code " |
| 118 | + + "SET DATA TYPE VARCHAR(255)"; |
| 119 | + |
| 120 | + String out = roundTrip(sql); |
| 121 | + String upper = squash(out).toUpperCase(); |
| 122 | + |
| 123 | + assertTrue("output must keep TYPE/SET DATA TYPE and VARCHAR(255): " + out, |
| 124 | + upper.contains("TYPE") && upper.contains("VARCHAR(255)")); |
| 125 | + } |
| 126 | + |
| 127 | + /** |
| 128 | + * Scenario 4: ALTER COLUMN ... TYPE <type> USING <expr>. |
| 129 | + * |
| 130 | + * <p>The type-change keyword and the USING expression must both survive the round-trip. |
| 131 | + */ |
| 132 | + @Test |
| 133 | + public void scenario4_type_using() { |
| 134 | + String sql = "ALTER TABLE test_alter_table " |
| 135 | + + "ALTER COLUMN value TYPE bigint USING value::bigint"; |
| 136 | + |
| 137 | + String out = roundTrip(sql); |
| 138 | + String upper = squash(out).toUpperCase(); |
| 139 | + |
| 140 | + assertTrue("output must keep TYPE/SET DATA TYPE bigint: " + out, |
| 141 | + upper.contains("TYPE") && upper.contains("BIGINT")); |
| 142 | + assertTrue("USING keyword must be present: " + out, upper.contains("USING")); |
| 143 | + // The USING expression must be preserved (case-insensitive on the bare identifiers, |
| 144 | + // but the :: cast form and structure must remain). |
| 145 | + assertTrue("USING expression must be preserved: " + out, |
| 146 | + out.toLowerCase().contains("value::bigint")); |
| 147 | + } |
| 148 | +} |
0 commit comments