Skip to content

Commit cda6f49

Browse files
committed
feat: parse PostgreSQL IMPORT FOREIGN SCHEMA
Add ImportForeignSchema for IMPORT FOREIGN SCHEMA remote [{LIMIT TO | EXCEPT} (table, ...)] FROM SERVER server INTO local [OPTIONS (...)], read with the PostgreSQL dialect or without one. The table list reuses the relation targets of LOCK and TRUNCATE, and OPTIONS the generic options of the other foreign-data statements. The EXASOL IMPORT statement and other dialects are unchanged. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent cf057dd commit cda6f49

22 files changed

Lines changed: 623 additions & 9 deletions

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
154154
| **MySQL resource groups** | `CREATE`/`ALTER`/`DROP RESOURCE GROUP` · `SET RESOURCE GROUP` |
155155
| **PostgreSQL session statements** | `DISCARD` · `LISTEN` · `UNLISTEN` · `SHOW`/`RESET TIME ZONE`/`TRANSACTION ISOLATION LEVEL`/`SESSION AUTHORIZATION` · `COPY` options from before 9.0 |
156156
| **MySQL server administration** | `FLUSH` · `KILL` · `RESET PERSIST` · `HANDLER … OPEN`/`READ`/`CLOSE` |
157+
| **PostgreSQL foreign schema import** | `IMPORT FOREIGN SCHEMA … [LIMIT TO \| EXCEPT (…)] FROM SERVER … INTO … [OPTIONS (…)]` |
157158
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
158159
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
159160
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,12 @@ public enum Feature {
702702
createSequence, createAccessMethod, createAccessMethodTable,
703703
/** Publication and subscription definitions. */
704704
createForeignDataWrapper, alterForeignDataWrapper, createServer, alterServer, createUserMapping, alterUserMapping,
705+
/**
706+
* PostgreSQL "IMPORT FOREIGN SCHEMA" statement is allowed
707+
*
708+
* @see net.sf.jsqlparser.statement.foreign.ImportForeignSchema
709+
*/
710+
importForeignSchema,
705711

706712
createRule, notifyStatement, createTextSearchConfiguration, alterTextSearchConfiguration, createCollation, alterCollation,
707713

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import net.sf.jsqlparser.statement.drop.DropUserMapping;
9292
import net.sf.jsqlparser.statement.execute.Execute;
9393
import net.sf.jsqlparser.statement.export.Export;
94+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
9495
import net.sf.jsqlparser.statement.export.ExportDataStatement;
9596
import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges;
9697
import net.sf.jsqlparser.statement.grant.Grant;
@@ -1537,6 +1538,11 @@ public <S> Void visit(AlterUserMapping statement, S context) {
15371538
return schemaOnly();
15381539
}
15391540

1541+
@Override
1542+
public <S> Void visit(ImportForeignSchema statement, S context) {
1543+
return schemaOnly();
1544+
}
1545+
15401546
@Override
15411547
public <S> Void visit(DropUserMapping statement, S context) {
15421548
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
@@ -87,6 +87,7 @@
8787
import net.sf.jsqlparser.statement.drop.Drop;
8888
import net.sf.jsqlparser.statement.execute.Execute;
8989
import net.sf.jsqlparser.statement.export.Export;
90+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
9091
import net.sf.jsqlparser.statement.grant.Grant;
9192
import net.sf.jsqlparser.statement.imprt.Import;
9293
import net.sf.jsqlparser.statement.insert.Insert;
@@ -1079,6 +1080,14 @@ default void visit(AlterUserMapping statement) {
10791080
visit(statement, null);
10801081
}
10811082

1083+
default <S> T visit(ImportForeignSchema statement, S context) {
1084+
return null;
1085+
}
1086+
1087+
default void visit(ImportForeignSchema statement) {
1088+
visit(statement, null);
1089+
}
1090+
10821091
default <S> T visit(DropUserMapping statement, S context) {
10831092
return null;
10841093
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import net.sf.jsqlparser.statement.notify.ListenStatement;
3030
import net.sf.jsqlparser.statement.notify.NotifyStatement;
3131
import net.sf.jsqlparser.statement.foreign.ForeignDataStatement;
32+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
3233

3334
import net.sf.jsqlparser.statement.create.accessmethod.CreateAccessMethod;
3435

@@ -1188,6 +1189,11 @@ public <S> T visit(AlterUserMapping statement, S context) {
11881189
return visitForeignDataStatement(statement, context);
11891190
}
11901191

1192+
@Override
1193+
public <S> T visit(ImportForeignSchema statement, S context) {
1194+
return visitForeignDataStatement(statement, context);
1195+
}
1196+
11911197
@Override
11921198
public <S> T visit(DropUserMapping statement, S context) {
11931199
return null;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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.foreign;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Objects;
15+
import java.util.function.Consumer;
16+
import net.sf.jsqlparser.expression.Expression;
17+
import net.sf.jsqlparser.schema.TableReference;
18+
import net.sf.jsqlparser.statement.StatementVisitor;
19+
20+
/**
21+
* PostgreSQL {@code IMPORT FOREIGN SCHEMA remote_schema [{LIMIT TO | EXCEPT} (table [, ...])] FROM
22+
* SERVER server INTO local_schema [OPTIONS (option 'value' [, ...])]}, which creates foreign tables
23+
* in the local schema for the tables of a schema on the foreign server.
24+
*
25+
* @see <a href="https://www.postgresql.org/docs/18/sql-importforeignschema.html">IMPORT FOREIGN
26+
* SCHEMA</a>
27+
*/
28+
public class ImportForeignSchema extends ForeignDataStatement {
29+
private String remoteSchema;
30+
private Qualification qualification;
31+
private final List<TableReference> tables = new ArrayList<>();
32+
private String server;
33+
private String localSchema;
34+
35+
/** How the table list restricts the import. */
36+
public enum Qualification {
37+
/** Import only the listed tables. */
38+
LIMIT_TO("LIMIT TO"),
39+
/** Import every table but the listed ones. */
40+
EXCEPT("EXCEPT");
41+
42+
private final String sql;
43+
44+
Qualification(String sql) {
45+
this.sql = sql;
46+
}
47+
48+
@Override
49+
public String toString() {
50+
return sql;
51+
}
52+
}
53+
54+
public String getRemoteSchema() {
55+
return remoteSchema;
56+
}
57+
58+
public void setRemoteSchema(String remoteSchema) {
59+
this.remoteSchema = remoteSchema;
60+
}
61+
62+
public ImportForeignSchema withRemoteSchema(String remoteSchema) {
63+
setRemoteSchema(remoteSchema);
64+
return this;
65+
}
66+
67+
/** The kind of the table list, or null when the whole remote schema is imported. */
68+
public Qualification getQualification() {
69+
return qualification;
70+
}
71+
72+
/**
73+
* The tables of the remote schema that LIMIT TO or EXCEPT lists; empty without a qualification.
74+
* The list is a read-only snapshot, change it with {@link #setTables(Qualification, List)}.
75+
*/
76+
public List<TableReference> getTables() {
77+
return List.copyOf(tables);
78+
}
79+
80+
/**
81+
* Restricts the import to the listed tables or excludes them.
82+
*
83+
* @param qualification the kind of the list, null to import the whole remote schema
84+
* @param tables the listed tables; at least one with a qualification, none without
85+
*/
86+
public void setTables(Qualification qualification, List<TableReference> tables) {
87+
List<TableReference> copy = tables == null ? List.of() : List.copyOf(tables);
88+
if (qualification == null != copy.isEmpty()) {
89+
throw new IllegalArgumentException(
90+
"LIMIT TO and EXCEPT need a table list, and a table list needs one of them");
91+
}
92+
this.qualification = qualification;
93+
this.tables.clear();
94+
this.tables.addAll(copy);
95+
}
96+
97+
public ImportForeignSchema withTables(Qualification qualification,
98+
List<TableReference> tables) {
99+
setTables(qualification, tables);
100+
return this;
101+
}
102+
103+
public String getServer() {
104+
return server;
105+
}
106+
107+
public void setServer(String server) {
108+
this.server = server;
109+
}
110+
111+
public ImportForeignSchema withServer(String server) {
112+
setServer(server);
113+
return this;
114+
}
115+
116+
public String getLocalSchema() {
117+
return localSchema;
118+
}
119+
120+
public void setLocalSchema(String localSchema) {
121+
this.localSchema = localSchema;
122+
}
123+
124+
public ImportForeignSchema withLocalSchema(String localSchema) {
125+
setLocalSchema(localSchema);
126+
return this;
127+
}
128+
129+
@Override
130+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
131+
return visitor.visit(this, context);
132+
}
133+
134+
@Override
135+
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> printer) {
136+
sql.append("IMPORT FOREIGN SCHEMA ").append(Objects.requireNonNull(remoteSchema));
137+
if (qualification != null) {
138+
sql.append(' ').append(qualification).append(" (");
139+
for (int i = 0; i < tables.size(); i++) {
140+
if (i > 0) {
141+
sql.append(", ");
142+
}
143+
tables.get(i).appendTo(sql);
144+
}
145+
sql.append(')');
146+
}
147+
sql.append(" FROM SERVER ").append(Objects.requireNonNull(server));
148+
sql.append(" INTO ").append(Objects.requireNonNull(localSchema));
149+
appendOptionsTo(sql, printer);
150+
return sql;
151+
}
152+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
import net.sf.jsqlparser.statement.execute.Execute;
189189
import net.sf.jsqlparser.statement.execute.ExecuteArgument;
190190
import net.sf.jsqlparser.statement.export.Export;
191+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
191192
import net.sf.jsqlparser.statement.export.ExportDataStatement;
192193
import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges;
193194
import net.sf.jsqlparser.statement.grant.Grant;
@@ -3457,6 +3458,13 @@ public <S> Void visit(AlterUserMapping statement, S context) {
34573458
return null;
34583459
}
34593460

3461+
@Override
3462+
public <S> Void visit(ImportForeignSchema statement, S context) {
3463+
// LIMIT TO and EXCEPT name tables of the remote schema, not relations of this database.
3464+
statement.visitExpressions(expression -> expression.accept(this, context));
3465+
return null;
3466+
}
3467+
34603468
@Override
34613469
public <S> Void visit(DropUserMapping statement, S context) {
34623470
// User and server names do not refer to tables.

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
import net.sf.jsqlparser.statement.drop.Drop;
123123
import net.sf.jsqlparser.statement.execute.Execute;
124124
import net.sf.jsqlparser.statement.export.Export;
125+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
125126
import net.sf.jsqlparser.statement.grant.Grant;
126127
import net.sf.jsqlparser.statement.imprt.Import;
127128
import net.sf.jsqlparser.statement.insert.Insert;
@@ -1084,6 +1085,12 @@ public <S> StringBuilder visit(AlterUserMapping statement, S context) {
10841085
expression -> expression.accept(expressionDeParser, context));
10851086
}
10861087

1088+
@Override
1089+
public <S> StringBuilder visit(ImportForeignSchema statement, S context) {
1090+
return statement.appendTo(builder,
1091+
expression -> expression.accept(expressionDeParser, context));
1092+
}
1093+
10871094
@Override
10881095
public <S> StringBuilder visit(DropUserMapping statement, S context) {
10891096
return statement.appendTo(builder);

‎src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
177177
* all DDL {@link Feature}'s
178178
*/
179179
public static final FeaturesAllowed DDL =
180-
new FeaturesAllowed("DDL").add(CREATE, ALTER, DROP).unmodifyable();
180+
new FeaturesAllowed("DDL", Feature.importForeignSchema).add(CREATE, ALTER, DROP)
181+
.unmodifyable();
181182
private static final String SEPERATOR = " + ";
182183
private Set<String> names = new LinkedHashSet<>();
183184
private Set<Feature> features = new HashSet<>();

‎src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ public enum PostgresqlVersion implements Version {
119119
Feature.createTablespace, Feature.alterTablespace,
120120
Feature.createForeignDataWrapper, Feature.alterForeignDataWrapper,
121121
Feature.createServer, Feature.alterServer, Feature.createUserMapping,
122-
Feature.alterUserMapping,
122+
Feature.alterUserMapping, // https://www.postgresql.org/docs/current/sql-importforeignschema.html
123+
Feature.importForeignSchema,
123124
Feature.createTextSearchConfiguration, Feature.alterTextSearchConfiguration,
124125
Feature.createRule, Feature.notifyStatement,
125126
Feature.createCollation, Feature.alterCollation,

0 commit comments

Comments
 (0)