diff --git a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java index 7b7e59232..d94fc9fb5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java +++ b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java @@ -38,6 +38,22 @@ public enum ObjectType { private Map> typeToParameters = new HashMap<>(); private boolean ifExists = false; private boolean materialized = false; + private boolean concurrently; + + /** PostgreSQL DROP INDEX CONCURRENTLY, preceding IF EXISTS and the index name. */ + public boolean isConcurrently() { + return concurrently; + } + + public void setConcurrently(boolean concurrently) { + this.concurrently = concurrently; + } + + public Drop withConcurrently(boolean concurrently) { + setConcurrently(concurrently); + return this; + } + private boolean isUsingTemporary; @@ -201,7 +217,11 @@ public StringBuilder appendTo(StringBuilder builder, Consumer tablePrinte if (materialized) { builder.append("MATERIALIZED "); } - builder.append(type).append(ifExists ? " IF EXISTS " : " "); + builder.append(type); + if (concurrently) { + builder.append(" CONCURRENTLY"); + } + builder.append(ifExists ? " IF EXISTS " : " "); appendNames(builder, tablePrinter); if ("FUNCTION".equals(type)) { builder.append(formatFuncParams(getParamsByType("FUNCTION"))); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 6c42e69e6..bd312dca1 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -16334,6 +16334,9 @@ Drop Drop(): tk= ) { drop.setType(tk.image); } + [ LOOKAHEAD({ drop.getObjectType() == Drop.ObjectType.INDEX + && getToken(1).kind == K_CONCURRENTLY }) + { drop.setConcurrently(true); } ] [ LOOKAHEAD(2) {drop.setIfExists(true);} ] diff --git a/src/test/java/net/sf/jsqlparser/statement/drop/PostgreSqlDropIndexTest.java b/src/test/java/net/sf/jsqlparser/statement/drop/PostgreSqlDropIndexTest.java new file mode 100644 index 000000000..e0fd692a3 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/drop/PostgreSqlDropIndexTest.java @@ -0,0 +1,56 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.drop; + +import static org.junit.jupiter.api.Assertions.*; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlDropIndexTest { + @ParameterizedTest + @ValueSource(strings = {"DROP INDEX CONCURRENTLY ix", + "DROP INDEX CONCURRENTLY IF EXISTS ix", + "DROP INDEX CONCURRENTLY IF EXISTS public.ix RESTRICT"}) + void concurrencyIsIndependentOfTarget(String sql) throws JSQLParserException { + Drop drop = (Drop) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL)); + assertTrue(drop.isConcurrently()); + assertEquals(sql.contains("IF EXISTS"), drop.isIfExists()); + assertEquals("ix", drop.getName().getName()); + assertEquals(sql, drop.toString()); + drop.setName(new Table("new_ix")); + String expected = sql.replace("public.ix", "ix").replace("ix", "new_ix"); + assertEquals(expected, drop.toString()); + StringBuilder buffer = new StringBuilder(); + drop.accept(new StatementDeParser(buffer), null); + assertEquals(expected, buffer.toString()); + Drop reparsed = (Drop) CCJSqlParserUtil.parse(expected); + assertEquals("new_ix", reparsed.getName().getName()); + assertTrue(reparsed.isConcurrently()); + assertEquals(2, CCJSqlParserUtil.parseStatements(expected + "; SELECT 1").size()); + drop.setConcurrently(false); + assertEquals(expected.replace("CONCURRENTLY ", ""), drop.toString()); + } + + @Test + void ordinaryAndQuotedConcurrentNamesArePreserved() throws JSQLParserException { + for (String sql : new String[] {"DROP TABLE concurrently", "DROP INDEX \"concurrently\"", + "DROP INDEX ix1, ix2 CASCADE", "DROP INDEX ix ON t ALGORITHM = INPLACE"}) { + Drop drop = (Drop) CCJSqlParserUtil.parse(sql); + assertFalse(drop.isConcurrently()); + assertEquals(sql, drop.toString()); + } + } +}