diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java
index 4dc0f69f7..2496f816d 100644
--- a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java
+++ b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java
@@ -38,6 +38,7 @@ public class CreateTable implements Statement {
private DuplicateHandling duplicateHandling;
private Table likeTable;
private Table cloneTable;
+ private List
inherits;
private ColDataType ofType;
private boolean selectParenthesis;
private boolean useAsKeyword = true;
@@ -347,6 +348,27 @@ public void setPartitionBound(PartitionBound partitionBound) {
this.partitionBound = partitionBound;
}
+ /** PostgreSQL parent tables, in declaration order; null if INHERITS is absent. */
+ public List getInherits() {
+ return inherits;
+ }
+
+ public void setInherits(List inherits) {
+ this.inherits = inherits;
+ }
+
+ public CreateTable withInherits(List inherits) {
+ setInherits(inherits);
+ return this;
+ }
+
+ /** Shared rendering of the structured parent references. */
+ public void appendInheritanceTo(StringBuilder builder) {
+ if (inherits != null) {
+ builder.append(" INHERITS ").append(PlainSelect.getStringList(inherits, true, true));
+ }
+ }
+
public boolean isSelectParenthesis() {
return selectParenthesis;
}
@@ -369,6 +391,7 @@ public String toString() {
StringBuilder b = new StringBuilder();
appendCreateClause(b);
appendColumnDefinitions(b);
+ appendInheritanceTo(b);
if (partitionBound != null) {
b.append(" ").append(partitionBound);
}
diff --git a/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java b/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java
index 21a6b7361..5c6bfe7d1 100644
--- a/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java
+++ b/src/main/java/net/sf/jsqlparser/util/TableDefinitionTraversal.java
@@ -109,6 +109,9 @@ public static void visit(CreateTable table, Consumer expressions,
}
});
}
+ if (table.getInherits() != null) {
+ table.getInherits().forEach(parent -> accept(parent, tables));
+ }
accept(table.getTrailingLikeTable(), tables);
accept(table.getPartitionOf(), tables);
visit(table.getPartitioning(), expressions);
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java
index 2d26b7a10..4ca8c4425 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java
@@ -106,6 +106,7 @@ public void deParse(CreateTable createTable) {
builder.append(")");
}
+ createTable.appendInheritanceTo(builder);
if (createTable.getPartitionBound() != null) {
builder.append(' ');
createTable.getPartitionBound().appendTo(builder,
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 3b6e2548e..c034a4757 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -14984,6 +14984,8 @@ CreateTable CreateTable(boolean isUsingOrReplace):
Table partitionOfTable = null;
PartitionBound partitionBound = null;
ColDataType ofType = null;
+ Table parent;
+ List parents = new ArrayList();
boolean withData;
}
{
@@ -15008,6 +15010,8 @@ CreateTable CreateTable(boolean isUsingOrReplace):
[ LOOKAHEAD(2) partitionOfTable=Table()
{ createTable.setPartitionOf(partitionOfTable); } ]
[ LOOKAHEAD(2) (
+ LOOKAHEAD("(" ")") "(" ")" { createTable.setTableElements(tableElements); }
+ |
LOOKAHEAD(3) (
"(" tableColumn=RelObjectName() { columns.add(tableColumn); } ("," tableColumn=RelObjectName() { columns.add(tableColumn); } )* ")"
)
@@ -15024,6 +15028,10 @@ CreateTable CreateTable(boolean isUsingOrReplace):
)
)
]
+ [ LOOKAHEAD({ isKeywordAhead("INHERITS") }) ContextualKeyword("INHERITS")
+ "(" parent=Table() { parents.add(parent); }
+ ( "," parent=Table() { parents.add(parent); } )* ")"
+ { createTable.setInherits(parents); } ]
[ LOOKAHEAD({ partitionOfTable != null })
partitionBound=PostgreSqlPartitionBound()
{ createTable.setPartitionBound(partitionBound); } ]
diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableInheritanceTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableInheritanceTest.java
new file mode 100644
index 000000000..d6d697b73
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableInheritanceTest.java
@@ -0,0 +1,78 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.create;
+
+import static org.junit.jupiter.api.Assertions.*;
+import java.util.ArrayList;
+import java.util.List;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.create.table.CreateTable;
+import net.sf.jsqlparser.util.TableDefinitionTraversal;
+import net.sf.jsqlparser.util.TablesNamesFinder;
+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 PostgreSqlTableInheritanceTest {
+ @ParameterizedTest
+ @ValueSource(strings = {"parent", "public.parent", "public.parent, other.parent2",
+ "\"Odd.Schema\".\"Parent.Name\""})
+ void modelsAndVisitsParentTables(String parents) throws JSQLParserException {
+ for (String definition : new String[] {"()", "(extra INT)", "(LIKE source, extra INT)"}) {
+ CreateTable table = (CreateTable) CCJSqlParserUtil.parse(
+ "CREATE TABLE child " + definition + " INHERITS (" + parents
+ + ") WITH (fillfactor = 80)");
+ List inherited = table.getInherits();
+ assertEquals(parents.split(", ").length, inherited.size());
+ assertEquals(parents.split(", ")[0], inherited.get(0).getFullyQualifiedName());
+ List visited = new ArrayList<>();
+ TableDefinitionTraversal.visit(table, e -> {
+ }, visited::add);
+ for (Table parent : inherited) {
+ assertTrue(visited.stream().anyMatch(t -> t == parent));
+ assertTrue(new TablesNamesFinder().getTables(table)
+ .contains(parent.getFullyQualifiedName()));
+ }
+ roundTrip(table);
+ inherited.set(0, new Table("replacement"));
+ assertTrue(table.toString().contains("INHERITS (replacement"));
+ roundTrip(table);
+ }
+ }
+
+ @Test
+ void preservesEmptyDefinitionsAndCanRemoveInheritance() throws JSQLParserException {
+ CreateTable table =
+ (CreateTable) CCJSqlParserUtil.parse("CREATE TABLE child () INHERITS (parent)");
+ assertNotNull(table.getTableElements());
+ assertTrue(table.getTableElements().isEmpty());
+ table.setInherits(null);
+ assertEquals("CREATE TABLE child ()", table.toString());
+ roundTrip(table);
+ assertEquals(2, CCJSqlParserUtil.parseStatements(table + "; SELECT 1").size());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"()", "(parent,)", "(parent AS p)", "(parent + 1)"})
+ void rejectsMalformedParentLists(String parents) {
+ assertThrows(JSQLParserException.class,
+ () -> CCJSqlParserUtil.parse("CREATE TABLE child () INHERITS " + parents));
+ }
+
+ private static void roundTrip(CreateTable table) throws JSQLParserException {
+ StringBuilder out = new StringBuilder();
+ table.accept(new StatementDeParser(out));
+ assertEquals(table.toString(), out.toString());
+ assertEquals(out.toString(), CCJSqlParserUtil.parse(out.toString()).toString());
+ }
+}