Skip to content

Commit 433143e

Browse files
authored
Support PostgreSQL CREATE INDEX header options (#2527)
1 parent 536fe3b commit 433143e

4 files changed

Lines changed: 120 additions & 11 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class CreateIndex implements Statement {
2323
private List<String> tailParameters;
2424
private boolean indexTypeBeforeOn = false;
2525
private boolean usingIfNotExists = false;
26+
private boolean concurrently;
27+
private boolean only;
2628

2729
public boolean isIndexTypeBeforeOn() {
2830
return indexTypeBeforeOn;
@@ -41,6 +43,22 @@ public CreateIndex setUsingIfNotExists(boolean usingIfNotExists) {
4143
return this;
4244
}
4345

46+
public boolean isConcurrently() {
47+
return concurrently;
48+
}
49+
50+
public void setConcurrently(boolean concurrently) {
51+
this.concurrently = concurrently;
52+
}
53+
54+
public boolean isOnly() {
55+
return only;
56+
}
57+
58+
public void setOnly(boolean only) {
59+
this.only = only;
60+
}
61+
4462
@Override
4563
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
4664
return statementVisitor.visit(this, context);
@@ -82,17 +100,24 @@ public String toString() {
82100
}
83101

84102
buffer.append("INDEX ");
103+
if (concurrently) {
104+
buffer.append("CONCURRENTLY ");
105+
}
85106
if (usingIfNotExists) {
86107
buffer.append("IF NOT EXISTS ");
87108
}
88-
buffer.append(index.getName());
109+
if (index.getName() != null) {
110+
buffer.append(index.getName()).append(" ");
111+
}
89112

90113
if (index.getUsing() != null && isIndexTypeBeforeOn()) {
91-
buffer.append(" USING ");
92-
buffer.append(index.getUsing());
114+
buffer.append("USING ").append(index.getUsing()).append(" ");
93115
}
94116

95-
buffer.append(" ON ");
117+
buffer.append("ON ");
118+
if (only) {
119+
buffer.append("ONLY ");
120+
}
96121
buffer.append(table.getFullyQualifiedName());
97122

98123
if (index.getUsing() != null && !isIndexTypeBeforeOn()) {
@@ -134,4 +159,14 @@ public CreateIndex withTailParameters(List<String> tailParameters) {
134159
this.setTailParameters(tailParameters);
135160
return this;
136161
}
162+
163+
public CreateIndex withConcurrently(boolean concurrently) {
164+
setConcurrently(concurrently);
165+
return this;
166+
}
167+
168+
public CreateIndex withOnly(boolean only) {
169+
setOnly(only);
170+
return this;
171+
}
137172
}

src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,25 @@ public void deParse(CreateIndex createIndex) {
3232
}
3333

3434
builder.append("INDEX ");
35+
if (createIndex.isConcurrently()) {
36+
builder.append("CONCURRENTLY ");
37+
}
3538
if (createIndex.isUsingIfNotExists()) {
3639
builder.append("IF NOT EXISTS ");
3740
}
38-
builder.append(index.getName());
41+
if (index.getName() != null) {
42+
builder.append(index.getName()).append(" ");
43+
}
3944

4045
String using = index.getUsing();
4146
if (using != null && createIndex.isIndexTypeBeforeOn()) {
42-
builder.append(" USING ");
43-
builder.append(using);
47+
builder.append("USING ").append(using).append(" ");
4448
}
4549

46-
builder.append(" ON ");
50+
builder.append("ON ");
51+
if (createIndex.isOnly()) {
52+
builder.append("ONLY ");
53+
}
4754
builder.append(createIndex.getTable().getFullyQualifiedName());
4855

4956
if (using != null && !createIndex.isIndexTypeBeforeOn()) {

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11277,11 +11277,20 @@ CreateIndex CreateIndex():
1127711277
[ parameter=CreateParameter() ]
1127811278

1127911279
<K_INDEX>
11280+
[ LOOKAHEAD({ getToken(1).kind == K_CONCURRENTLY })
11281+
<K_CONCURRENTLY> { createIndex.setConcurrently(true); } ]
1128011282
[ LOOKAHEAD(2) <K_IF> <K_NOT> <K_EXISTS> { createIndex.setUsingIfNotExists(true);} ]
11281-
index = Index() { index.setType(parameter.isEmpty() ? null : parameter.get(0)); }
11283+
[ LOOKAHEAD({ getToken(1).kind != K_ON && getToken(1).kind != K_USING })
11284+
index = Index() ]
11285+
{
11286+
if (index == null) {
11287+
index = new Index();
11288+
}
11289+
index.setType(parameter.isEmpty() ? null : parameter.get(0));
11290+
}
1128211291
(
1128311292
LOOKAHEAD(3)(
11284-
<K_ON> table=Table()
11293+
<K_ON> [ <K_ONLY> { createIndex.setOnly(true); } ] table=Table()
1128511294
[ indexType=UsingIndexType() { index.setUsing(indexType); } ]
1128611295
)
1128711296
|
@@ -11291,7 +11300,7 @@ CreateIndex CreateIndex():
1129111300
createIndex.setIndexTypeBeforeOn(true);
1129211301
}
1129311302
]
11294-
<K_ON> table=Table()
11303+
<K_ON> [ <K_ONLY> { createIndex.setOnly(true); } ] table=Table()
1129511304
)
1129611305
)
1129711306
colNames = IndexColumnsWithParamsList()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertFalse;
15+
import static org.junit.jupiter.api.Assertions.assertNull;
16+
import static org.junit.jupiter.api.Assertions.assertTrue;
17+
18+
import net.sf.jsqlparser.JSQLParserException;
19+
import net.sf.jsqlparser.statement.create.index.CreateIndex;
20+
import org.junit.jupiter.api.Test;
21+
22+
/** PostgreSQL {@code CREATE INDEX} syntax verified against PostgreSQL 18.6. */
23+
public class PostgreSQLCreateIndexTest {
24+
25+
@Test
26+
public void testCreateIndexConcurrently() throws JSQLParserException {
27+
String sql = "CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS pg_idx_email "
28+
+ "ON pg_index_test USING btree (email)";
29+
30+
CreateIndex createIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql);
31+
32+
assertTrue(createIndex.isConcurrently());
33+
assertTrue(createIndex.isUsingIfNotExists());
34+
assertFalse(createIndex.isOnly());
35+
assertEquals("pg_idx_email", createIndex.getIndex().getName());
36+
}
37+
38+
@Test
39+
public void testCreateIndexWithOmittedNameAndOnly() throws JSQLParserException {
40+
String sql = "CREATE INDEX ON ONLY pg_index_test USING btree (id DESC)";
41+
42+
CreateIndex createIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql);
43+
44+
assertNull(createIndex.getIndex().getName());
45+
assertTrue(createIndex.isOnly());
46+
assertFalse(createIndex.isConcurrently());
47+
}
48+
49+
@Test
50+
public void testCreateNamedIndexOnOnlyTable() throws JSQLParserException {
51+
String sql = "CREATE INDEX pg_idx_only ON ONLY pg_index_test (id)";
52+
53+
CreateIndex createIndex = (CreateIndex) assertSqlCanBeParsedAndDeparsed(sql);
54+
55+
assertEquals("pg_idx_only", createIndex.getIndex().getName());
56+
assertTrue(createIndex.isOnly());
57+
}
58+
}

0 commit comments

Comments
 (0)