Skip to content

Commit 1bfede8

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 e27929f commit 1bfede8

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
@@ -152,6 +152,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
152152
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
153153
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
154154
| **PostgreSQL session statements** | `DISCARD` · `LISTEN` · `UNLISTEN` · `SHOW`/`RESET TIME ZONE`/`TRANSACTION ISOLATION LEVEL`/`SESSION AUTHORIZATION` · `COPY` options from before 9.0 |
155+
| **PostgreSQL foreign schema import** | `IMPORT FOREIGN SCHEMA … [LIMIT TO \| EXCEPT (…)] FROM SERVER … INTO … [OPTIONS (…)]` |
155156
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
156157
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
157158
| **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
@@ -670,6 +670,12 @@ public enum Feature {
670670
createSequence, createAccessMethod, createAccessMethodTable,
671671
/** Publication and subscription definitions. */
672672
createForeignDataWrapper, alterForeignDataWrapper, createServer, alterServer, createUserMapping, alterUserMapping,
673+
/**
674+
* PostgreSQL "IMPORT FOREIGN SCHEMA" statement is allowed
675+
*
676+
* @see net.sf.jsqlparser.statement.foreign.ImportForeignSchema
677+
*/
678+
importForeignSchema,
673679

674680
createRule, notifyStatement, createTextSearchConfiguration, alterTextSearchConfiguration, createCollation, alterCollation,
675681

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
import net.sf.jsqlparser.statement.drop.DropUserMapping;
9191
import net.sf.jsqlparser.statement.execute.Execute;
9292
import net.sf.jsqlparser.statement.export.Export;
93+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
9394
import net.sf.jsqlparser.statement.export.ExportDataStatement;
9495
import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges;
9596
import net.sf.jsqlparser.statement.grant.Grant;
@@ -1474,6 +1475,11 @@ public <S> Void visit(AlterUserMapping statement, S context) {
14741475
return schemaOnly();
14751476
}
14761477

1478+
@Override
1479+
public <S> Void visit(ImportForeignSchema statement, S context) {
1480+
return schemaOnly();
1481+
}
1482+
14771483
@Override
14781484
public <S> Void visit(DropUserMapping statement, S context) {
14791485
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
@@ -86,6 +86,7 @@
8686
import net.sf.jsqlparser.statement.drop.Drop;
8787
import net.sf.jsqlparser.statement.execute.Execute;
8888
import net.sf.jsqlparser.statement.export.Export;
89+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
8990
import net.sf.jsqlparser.statement.grant.Grant;
9091
import net.sf.jsqlparser.statement.imprt.Import;
9192
import net.sf.jsqlparser.statement.insert.Insert;
@@ -1024,6 +1025,14 @@ default void visit(AlterUserMapping statement) {
10241025
visit(statement, null);
10251026
}
10261027

1028+
default <S> T visit(ImportForeignSchema statement, S context) {
1029+
return null;
1030+
}
1031+
1032+
default void visit(ImportForeignSchema statement) {
1033+
visit(statement, null);
1034+
}
1035+
10271036
default <S> T visit(DropUserMapping statement, S context) {
10281037
return null;
10291038
}

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

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

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

@@ -1146,6 +1147,11 @@ public <S> T visit(AlterUserMapping statement, S context) {
11461147
return visitForeignDataStatement(statement, context);
11471148
}
11481149

1150+
@Override
1151+
public <S> T visit(ImportForeignSchema statement, S context) {
1152+
return visitForeignDataStatement(statement, context);
1153+
}
1154+
11491155
@Override
11501156
public <S> T visit(DropUserMapping statement, S context) {
11511157
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
@@ -186,6 +186,7 @@
186186
import net.sf.jsqlparser.statement.execute.Execute;
187187
import net.sf.jsqlparser.statement.execute.ExecuteArgument;
188188
import net.sf.jsqlparser.statement.export.Export;
189+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
189190
import net.sf.jsqlparser.statement.export.ExportDataStatement;
190191
import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges;
191192
import net.sf.jsqlparser.statement.grant.Grant;
@@ -3376,6 +3377,13 @@ public <S> Void visit(AlterUserMapping statement, S context) {
33763377
return null;
33773378
}
33783379

3380+
@Override
3381+
public <S> Void visit(ImportForeignSchema statement, S context) {
3382+
// LIMIT TO and EXCEPT name tables of the remote schema, not relations of this database.
3383+
statement.visitExpressions(expression -> expression.accept(this, context));
3384+
return null;
3385+
}
3386+
33793387
@Override
33803388
public <S> Void visit(DropUserMapping statement, S context) {
33813389
// 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
@@ -121,6 +121,7 @@
121121
import net.sf.jsqlparser.statement.drop.Drop;
122122
import net.sf.jsqlparser.statement.execute.Execute;
123123
import net.sf.jsqlparser.statement.export.Export;
124+
import net.sf.jsqlparser.statement.foreign.ImportForeignSchema;
124125
import net.sf.jsqlparser.statement.grant.Grant;
125126
import net.sf.jsqlparser.statement.imprt.Import;
126127
import net.sf.jsqlparser.statement.insert.Insert;
@@ -1058,6 +1059,12 @@ public <S> StringBuilder visit(AlterUserMapping statement, S context) {
10581059
expression -> expression.accept(expressionDeParser, context));
10591060
}
10601061

1062+
@Override
1063+
public <S> StringBuilder visit(ImportForeignSchema statement, S context) {
1064+
return statement.appendTo(builder,
1065+
expression -> expression.accept(expressionDeParser, context));
1066+
}
1067+
10611068
@Override
10621069
public <S> StringBuilder visit(DropUserMapping statement, S context) {
10631070
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
@@ -118,7 +118,8 @@ public enum PostgresqlVersion implements Version {
118118
Feature.createTablespace, Feature.alterTablespace,
119119
Feature.createForeignDataWrapper, Feature.alterForeignDataWrapper,
120120
Feature.createServer, Feature.alterServer, Feature.createUserMapping,
121-
Feature.alterUserMapping,
121+
Feature.alterUserMapping, // https://www.postgresql.org/docs/current/sql-importforeignschema.html
122+
Feature.importForeignSchema,
122123
Feature.createTextSearchConfiguration, Feature.alterTextSearchConfiguration,
123124
Feature.createRule, Feature.notifyStatement,
124125
Feature.createCollation, Feature.alterCollation,

0 commit comments

Comments
 (0)