Skip to content

Commit 1e0e858

Browse files
authored
feat: parse PostgreSQL DROP FOREIGN DATA WRAPPER and DROP USER MAPPING (#2872)
CREATE and ALTER of foreign-data wrappers and user mappings parsed, as did DROP SERVER, but DROP FOREIGN DATA WRAPPER and DROP USER MAPPING failed. pg_dump --clean --if-exists writes both, so such a dump could not be parsed as a script. DROP FOREIGN DATA WRAPPER is a Drop with the new object type FOREIGN_DATA_WRAPPER, so name lists, IF EXISTS and CASCADE or RESTRICT work as for DROP SERVER. A user mapping has no name of its own, so the new DropUserMapping keeps the user and the server like CreateUserMapping and AlterUserMapping. DROP USER for a user named mapping is unchanged. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 8330f6b commit 1e0e858

10 files changed

Lines changed: 184 additions & 5 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import net.sf.jsqlparser.statement.delete.Delete;
8686
import net.sf.jsqlparser.statement.drop.Drop;
8787
import net.sf.jsqlparser.statement.drop.DropPolicy;
88+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
8889
import net.sf.jsqlparser.statement.execute.Execute;
8990
import net.sf.jsqlparser.statement.export.Export;
9091
import net.sf.jsqlparser.statement.export.ExportDataStatement;
@@ -1365,6 +1366,11 @@ public <S> Void visit(AlterUserMapping statement, S context) {
13651366
return schemaOnly();
13661367
}
13671368

1369+
@Override
1370+
public <S> Void visit(DropUserMapping statement, S context) {
1371+
return schemaOnly();
1372+
}
1373+
13681374
@Override
13691375
public <S> Void visit(CreateRule statement, S context) {
13701376
return schemaOnly();

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import net.sf.jsqlparser.statement.alter.AlterTrigger;
2828
import net.sf.jsqlparser.statement.alter.AlterPolicy;
2929
import net.sf.jsqlparser.statement.drop.DropPolicy;
30+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
3031
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
3132
import net.sf.jsqlparser.statement.alter.AlterStatistics;
3233
import net.sf.jsqlparser.statement.alter.AlterRelation;
@@ -945,6 +946,14 @@ default void visit(AlterUserMapping statement) {
945946
visit(statement, null);
946947
}
947948

949+
default <S> T visit(DropUserMapping statement, S context) {
950+
return null;
951+
}
952+
953+
default void visit(DropUserMapping statement) {
954+
visit(statement, null);
955+
}
956+
948957
default <S> T visit(CreateAccessMethod statement, S context) {
949958
return null;
950959
}

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import net.sf.jsqlparser.statement.alter.AlterRoutine;
1616
import net.sf.jsqlparser.statement.alter.AlterTrigger;
1717
import net.sf.jsqlparser.statement.drop.DropPolicy;
18+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
1819
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
1920
import net.sf.jsqlparser.statement.create.fdw.CreateForeignDataWrapper;
2021
import net.sf.jsqlparser.statement.alter.AlterForeignDataWrapper;
@@ -1087,6 +1088,11 @@ public <S> T visit(AlterUserMapping statement, S context) {
10871088
return visitForeignDataStatement(statement, context);
10881089
}
10891090

1091+
@Override
1092+
public <S> T visit(DropUserMapping statement, S context) {
1093+
return null;
1094+
}
1095+
10901096
@Override
10911097
public <S> T visit(CreateRule statement, S context) {
10921098
statement.visitTables(table -> table.accept(fromItemVisitor, context));

‎src/main/java/net/sf/jsqlparser/statement/drop/Drop.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
public class Drop implements Statement {
3232

3333
public enum ObjectType {
34-
DATABASE, EVENT, FUNCTION, INDEX, PROCEDURE, SCHEMA, SEQUENCE, SERVER, TABLE, TABLESPACE, TRIGGER, VIEW, FOREIGN_TABLE, AGGREGATE, DOMAIN, EVENT_TRIGGER, GROUP, OWNED, ROLE, ROUTINE, RULE, TYPE, USER, SYNONYM, OTHER;
34+
DATABASE, EVENT, FUNCTION, INDEX, PROCEDURE, SCHEMA, SEQUENCE, SERVER, TABLE, TABLESPACE, TRIGGER, VIEW, FOREIGN_TABLE, FOREIGN_DATA_WRAPPER, AGGREGATE, DOMAIN, EVENT_TRIGGER, GROUP, OWNED, ROLE, ROUTINE, RULE, TYPE, USER, SYNONYM, OTHER;
3535

3636
/** Targets are {@link RoutineReference}s, each with an optional argument signature. */
3737
public boolean hasRoutineTargets() {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.drop;
11+
12+
import net.sf.jsqlparser.statement.Statement;
13+
import net.sf.jsqlparser.statement.StatementVisitor;
14+
15+
/**
16+
* PostgreSQL {@code DROP USER MAPPING [IF EXISTS] FOR user SERVER server}. A user mapping has no
17+
* name of its own, so it is identified by the user and the server, as in
18+
* {@link net.sf.jsqlparser.statement.create.usermapping.CreateUserMapping}.
19+
*/
20+
public class DropUserMapping implements Statement {
21+
private String user;
22+
private String server;
23+
private boolean ifExists;
24+
25+
public String getUser() {
26+
return user;
27+
}
28+
29+
public void setUser(String value) {
30+
user = value;
31+
}
32+
33+
public String getServer() {
34+
return server;
35+
}
36+
37+
public void setServer(String value) {
38+
server = value;
39+
}
40+
41+
public boolean isIfExists() {
42+
return ifExists;
43+
}
44+
45+
public void setIfExists(boolean value) {
46+
ifExists = value;
47+
}
48+
49+
public StringBuilder appendTo(StringBuilder sql) {
50+
sql.append("DROP USER MAPPING ");
51+
if (ifExists) {
52+
sql.append("IF EXISTS ");
53+
}
54+
return sql.append("FOR ").append(user).append(" SERVER ").append(server);
55+
}
56+
57+
@Override
58+
public String toString() {
59+
return appendTo(new StringBuilder()).toString();
60+
}
61+
62+
@Override
63+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
64+
return visitor.visit(this, context);
65+
}
66+
}

‎src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
import net.sf.jsqlparser.statement.delete.ParenthesedDelete;
177177
import net.sf.jsqlparser.statement.drop.Drop;
178178
import net.sf.jsqlparser.statement.drop.DropPolicy;
179+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
179180
import net.sf.jsqlparser.statement.execute.Execute;
180181
import net.sf.jsqlparser.statement.execute.ExecuteArgument;
181182
import net.sf.jsqlparser.statement.export.Export;
@@ -3247,6 +3248,12 @@ public <S> Void visit(AlterUserMapping statement, S context) {
32473248
return null;
32483249
}
32493250

3251+
@Override
3252+
public <S> Void visit(DropUserMapping statement, S context) {
3253+
// User and server names do not refer to tables.
3254+
return null;
3255+
}
3256+
32503257
@Override
32513258
public <S> Void visit(CreateAccessMethod statement, S context) {
32523259
return null;

‎src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import net.sf.jsqlparser.statement.alter.AlterTrigger;
2828
import net.sf.jsqlparser.statement.alter.AlterPolicy;
2929
import net.sf.jsqlparser.statement.drop.DropPolicy;
30+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
3031
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
3132
import net.sf.jsqlparser.statement.alter.AlterStatistics;
3233
import net.sf.jsqlparser.statement.alter.AlterRelation;
@@ -1003,6 +1004,11 @@ public <S> StringBuilder visit(AlterUserMapping statement, S context) {
10031004
expression -> expression.accept(expressionDeParser, context));
10041005
}
10051006

1007+
@Override
1008+
public <S> StringBuilder visit(DropUserMapping statement, S context) {
1009+
return statement.appendTo(builder);
1010+
}
1011+
10061012
@Override
10071013
public <S> StringBuilder visit(CreateAccessMethod statement, S context) {
10081014
return statement.appendTo(builder);

‎src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import net.sf.jsqlparser.statement.alter.AlterTablespace;
1414
import net.sf.jsqlparser.statement.alter.AlterPolicy;
1515
import net.sf.jsqlparser.statement.drop.DropPolicy;
16+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
1617
import net.sf.jsqlparser.statement.create.statistics.CreateStatistics;
1718

1819
import net.sf.jsqlparser.statement.create.accessmethod.CreateAccessMethod;
@@ -1292,6 +1293,12 @@ public <S> Void visit(AlterUserMapping statement, S context) {
12921293
return null;
12931294
}
12941295

1296+
@Override
1297+
public <S> Void visit(DropUserMapping statement, S context) {
1298+
validateFeature(Feature.drop);
1299+
return null;
1300+
}
1301+
12951302
@Override
12961303
public <S> Void visit(CreateAccessMethod statement, S context) {
12971304
validateFeature(Feature.createAccessMethod);

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4868,6 +4868,11 @@ Statement SingleStatement() :
48684868
|
48694869
LOOKAHEAD(<K_DROP> <K_POLICY>) stm = DropPolicy()
48704870
|
4871+
LOOKAHEAD({ getToken(1).kind == K_DROP && getToken(2).kind == K_USER
4872+
&& "MAPPING".equalsIgnoreCase(getToken(3).image)
4873+
&& (getToken(4).kind == K_FOR || getToken(4).kind == K_IF) })
4874+
stm = DropUserMapping()
4875+
|
48714876
stm = Drop()
48724877
|
48734878
LOOKAHEAD({ getToken(1).kind == K_REASSIGN && "OWNED".equalsIgnoreCase(getToken(2).image) })
@@ -20006,7 +20011,7 @@ Drop Drop():
2000620011
List<AlgorithmOrLockOption> indexOptions = new ArrayList<AlgorithmOrLockOption>();
2000720012
int indexOptionPosition = 0;
2000820013
boolean useTemporary = false;
20009-
boolean foreign = false;
20014+
String foreignType = null;
2001020015
boolean eventTrigger = false;
2001120016
CascadeBehavior behavior = null;
2001220017
String engine;
@@ -20015,7 +20020,11 @@ Drop Drop():
2001520020
<K_DROP>
2001620021
[ <K_MATERIALIZED> { drop.setMaterialized(true);} ]
2001720022
(
20018-
<K_FOREIGN> tk=<K_TABLE> { foreign = true; }
20023+
<K_FOREIGN> (
20024+
<K_TABLE> { foreignType = "FOREIGN TABLE"; }
20025+
|
20026+
<K_DATA> ContextualKeyword("WRAPPER") { foreignType = "FOREIGN DATA WRAPPER"; }
20027+
)
2001920028
|
2002020029
// PostgreSQL DROP OWNED BY role [, ...]
2002120030
LOOKAHEAD({ isKeywordAhead("OWNED") && getToken(2).kind == K_BY }) tk=<S_IDENTIFIER> <K_BY>
@@ -20063,7 +20072,7 @@ Drop Drop():
2006320072
|
2006420073
tk=<K_GROUP>
2006520074
)
20066-
{ drop.setType(foreign ? "FOREIGN TABLE" : eventTrigger ? "EVENT TRIGGER" : tk.image); }
20075+
{ drop.setType(foreignType != null ? foreignType : eventTrigger ? "EVENT TRIGGER" : tk.image); }
2006720076
[ LOOKAHEAD({ drop.getObjectType() == Drop.ObjectType.INDEX
2006820077
&& getToken(1).kind == K_CONCURRENTLY })
2006920078
<K_CONCURRENTLY> { drop.setConcurrently(true); } ]
@@ -25268,6 +25277,18 @@ CreateUserMapping CreateUserMapping():
2526825277
{ return statement; }
2526925278
}
2527025279

25280+
DropUserMapping DropUserMapping():
25281+
{
25282+
DropUserMapping statement = new DropUserMapping(); String name;
25283+
}
25284+
{
25285+
<K_DROP> <K_USER> ContextualKeyword("MAPPING")
25286+
[ LOOKAHEAD(2) <K_IF> <K_EXISTS> { statement.setIfExists(true); } ]
25287+
<K_FOR> name=RelObjectName() { statement.setUser(name); }
25288+
ContextualKeyword("SERVER") name=RelObjectName() { statement.setServer(name); }
25289+
{ return statement; }
25290+
}
25291+
2527125292
AlterUserMapping AlterUserMapping():
2527225293
{
2527325294
AlterUserMapping statement = new AlterUserMapping(); String name; List<ForeignDataOption> options;

‎src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlForeignDataObjectsTest.java‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
import static org.junit.jupiter.api.Assertions.*;
1313
import java.util.ArrayList;
1414
import java.util.List;
15+
import java.util.stream.Collectors;
1516
import net.sf.jsqlparser.JSQLParserException;
1617
import net.sf.jsqlparser.expression.Expression;
1718
import net.sf.jsqlparser.expression.NullValue;
1819
import net.sf.jsqlparser.expression.StringValue;
1920
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
21+
import net.sf.jsqlparser.schema.Table;
22+
import net.sf.jsqlparser.statement.CascadeBehavior;
2023
import net.sf.jsqlparser.statement.Statement;
2124
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
2225
import net.sf.jsqlparser.statement.alter.AlterForeignDataWrapper;
@@ -27,6 +30,8 @@
2730
import net.sf.jsqlparser.statement.create.server.CreateServer;
2831
import net.sf.jsqlparser.statement.create.table.ForeignDataOption;
2932
import net.sf.jsqlparser.statement.create.usermapping.CreateUserMapping;
33+
import net.sf.jsqlparser.statement.drop.Drop;
34+
import net.sf.jsqlparser.statement.drop.DropUserMapping;
3035
import net.sf.jsqlparser.statement.foreign.ForeignDataStatement;
3136
import net.sf.jsqlparser.util.TablesNamesFinder;
3237
import net.sf.jsqlparser.util.deparser.StatementDeParser;
@@ -70,6 +75,50 @@ void foreignObjectsShareOptionsAndKeepBoundaries(String sql) throws JSQLParserEx
7075
assertFalse(new Validation(List.of(FeaturesAllowed.SELECT), sql).validate().isEmpty());
7176
}
7277

78+
@ParameterizedTest
79+
@ValueSource(strings = {"DROP FOREIGN DATA WRAPPER w",
80+
"DROP FOREIGN DATA WRAPPER IF EXISTS w1, w2 CASCADE",
81+
"DROP FOREIGN DATA WRAPPER w RESTRICT",
82+
"DROP USER MAPPING FOR CURRENT_USER SERVER s",
83+
"DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER s",
84+
"DROP USER MAPPING FOR USER SERVER s",
85+
"DROP USER MAPPING FOR \"user name\" SERVER \"server name\""})
86+
void dropsForeignDataObjects(String sql) throws JSQLParserException {
87+
Statement statement = CCJSqlParserUtil.parse(sql);
88+
assertEquals(sql, statement.toString());
89+
assertTrue(new TablesNamesFinder().getTables(statement).isEmpty());
90+
roundTrip(statement);
91+
assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1").size());
92+
assertTrue(new Validation(List.of(FeaturesAllowed.DDL), sql).validate().isEmpty());
93+
assertFalse(new Validation(List.of(FeaturesAllowed.SELECT), sql).validate().isEmpty());
94+
}
95+
96+
@Test
97+
void dropStatementsKeepObjectKindAndMappingKey() throws JSQLParserException {
98+
Drop wrapper = (Drop) CCJSqlParserUtil.parse(
99+
"DROP FOREIGN DATA WRAPPER IF EXISTS w1, w2 CASCADE");
100+
assertEquals(Drop.ObjectType.FOREIGN_DATA_WRAPPER, wrapper.getObjectType());
101+
assertEquals(List.of("w1", "w2"),
102+
wrapper.getNames().stream().map(Table::getFullyQualifiedName)
103+
.collect(Collectors.toList()));
104+
assertTrue(wrapper.isIfExists());
105+
assertEquals(CascadeBehavior.CASCADE, wrapper.getBehavior());
106+
107+
DropUserMapping mapping = (DropUserMapping) CCJSqlParserUtil.parse(
108+
"DROP USER MAPPING IF EXISTS FOR CURRENT_USER SERVER s");
109+
assertTrue(mapping.isIfExists());
110+
assertEquals("CURRENT_USER", mapping.getUser());
111+
assertEquals("s", mapping.getServer());
112+
mapping.setIfExists(false);
113+
mapping.setServer("s2");
114+
assertEquals("DROP USER MAPPING FOR CURRENT_USER SERVER s2", mapping.toString());
115+
roundTrip(mapping);
116+
117+
// MAPPING followed by neither FOR nor IF is still a user name
118+
Drop user = (Drop) CCJSqlParserUtil.parse("DROP USER mapping");
119+
assertEquals(Drop.ObjectType.USER, user.getObjectType());
120+
}
121+
73122
@Test
74123
void omittedAndRemovedHandlersStayDistinct() throws JSQLParserException {
75124
CreateForeignDataWrapper create = (CreateForeignDataWrapper) CCJSqlParserUtil.parse(
@@ -210,7 +259,9 @@ public <S> String visit(CreateUserMapping statement, S context) {
210259
"ALTER SERVER s OPTIONS(SET x)", "ALTER SERVER s OPTIONS(DROP x 'y')",
211260
"ALTER SERVER s OPTIONS()", "ALTER SERVER s OPTIONS(host = 'localhost')",
212261
"CREATE SERVER s TYPE NULL FOREIGN DATA WRAPPER w", "ALTER SERVER s VERSION 18",
213-
"ALTER SERVER s RENAME TO s2 OPTIONS(x 'y')"})
262+
"ALTER SERVER s RENAME TO s2 OPTIONS(x 'y')",
263+
"DROP USER MAPPING FOR u", "DROP USER MAPPING FOR u SERVER s CASCADE",
264+
"DROP USER MAPPING IF EXISTS u SERVER s"})
214265
void rejectsWrongObjectAndOptionForms(String sql) {
215266
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql));
216267
}

0 commit comments

Comments
 (0)