Skip to content

Commit dac8159

Browse files
authored
feat: model PostgreSQL temporal key and foreign key constraints (#2697)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent dba1eba commit dac8159

4 files changed

Lines changed: 288 additions & 11 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyReference.java‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import net.sf.jsqlparser.statement.ReferentialAction;
2222
import net.sf.jsqlparser.statement.ReferentialAction.Action;
2323
import net.sf.jsqlparser.statement.ReferentialAction.Type;
24-
import net.sf.jsqlparser.statement.select.PlainSelect;
2524

2625
/** The target and actions of a column- or table-level foreign-key reference. */
2726
public class ForeignKeyReference implements Serializable {
@@ -33,6 +32,7 @@ public enum MatchType {
3332
private Table table;
3433
private List<String> referencedColumnNames;
3534
private MatchType matchType;
35+
private boolean usingPeriod;
3636
private ConstraintAttributes constraintAttributes;
3737
private final Set<ReferentialAction> referentialActions = new LinkedHashSet<>(2);
3838

@@ -47,6 +47,20 @@ public void setConstraintAttributes(ConstraintAttributes constraintAttributes) {
4747
this.constraintAttributes = constraintAttributes;
4848
}
4949

50+
/** Whether the final explicitly referenced column is prefixed with PERIOD. */
51+
public boolean isUsingPeriod() {
52+
return usingPeriod;
53+
}
54+
55+
public void setUsingPeriod(boolean usingPeriod) {
56+
this.usingPeriod = usingPeriod;
57+
}
58+
59+
public ForeignKeyReference withUsingPeriod(boolean usingPeriod) {
60+
setUsingPeriod(usingPeriod);
61+
return this;
62+
}
63+
5064
public Table getTable() {
5165
return table;
5266
}
@@ -136,7 +150,17 @@ public ForeignKeyReference addReferencedColumnNames(
136150
public String toString() {
137151
StringBuilder builder = new StringBuilder("REFERENCES ").append(table);
138152
if (referencedColumnNames != null) {
139-
builder.append(PlainSelect.getStringList(referencedColumnNames, true, true));
153+
builder.append('(');
154+
for (int i = 0; i < referencedColumnNames.size(); i++) {
155+
if (i > 0) {
156+
builder.append(", ");
157+
}
158+
if (usingPeriod && i == referencedColumnNames.size() - 1) {
159+
builder.append("PERIOD ");
160+
}
161+
builder.append(referencedColumnNames.get(i));
162+
}
163+
builder.append(')');
140164
}
141165
if (matchType != null) {
142166
builder.append(" MATCH ").append(matchType);

‎src/main/java/net/sf/jsqlparser/statement/create/table/Index.java‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ public enum NullOrdering {
417417
private SortOrder sortOrder;
418418
private NullOrdering nullOrdering;
419419
private ExclusionOperator exclusionOperator;
420+
private boolean withoutOverlaps;
421+
private boolean period;
420422

421423
public String getExclusionOperator() {
422424
return exclusionOperator == null ? null : exclusionOperator.toString();
@@ -466,6 +468,34 @@ public ColumnParams(Expression expression, List<String> params) {
466468
this.expression = expression;
467469
}
468470

471+
/** Marks the final key of a PostgreSQL temporal PRIMARY KEY or UNIQUE constraint. */
472+
public boolean isWithoutOverlaps() {
473+
return withoutOverlaps;
474+
}
475+
476+
public void setWithoutOverlaps(boolean withoutOverlaps) {
477+
this.withoutOverlaps = withoutOverlaps;
478+
}
479+
480+
public ColumnParams withWithoutOverlaps(boolean withoutOverlaps) {
481+
setWithoutOverlaps(withoutOverlaps);
482+
return this;
483+
}
484+
485+
/** Marks the final referencing column of a temporal foreign key. */
486+
public boolean isPeriod() {
487+
return period;
488+
}
489+
490+
public void setPeriod(boolean period) {
491+
this.period = period;
492+
}
493+
494+
public ColumnParams withPeriod(boolean period) {
495+
setPeriod(period);
496+
return this;
497+
}
498+
469499
public String getColumnName() {
470500
return expression != null ? expression.toString() : columnName;
471501
}
@@ -569,6 +599,9 @@ public String toString() {
569599

570600
/** Renders expression keys through the caller's expression printer. */
571601
public void appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
602+
if (period) {
603+
builder.append("PERIOD ");
604+
}
572605
if (expression != null) {
573606
if (expressionParenthesized) {
574607
builder.append('(');
@@ -588,6 +621,9 @@ public void appendTo(StringBuilder builder, Consumer<Expression> expressionPrint
588621
if (exclusionOperator != null) {
589622
builder.append(" WITH ").append(exclusionOperator);
590623
}
624+
if (withoutOverlaps) {
625+
builder.append(" WITHOUT OVERLAPS");
626+
}
591627
}
592628

593629
private void appendParams(StringBuilder builder) {

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

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13835,7 +13835,7 @@ Index.ColumnParams IndexColumnWithParams(): {
1383513835
|| (Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
1383613836
&& getToken(1).kind != K_ASC && getToken(1).kind != K_DESC
1383713837
&& getToken(1).kind != K_NULLS && getToken(1).kind != K_COLLATE
13838-
&& getToken(1).kind != K_WITH) })
13838+
&& getToken(1).kind != K_WITH && getToken(1).kind != K_WITHOUT) })
1383913839
operatorClass=IndexKeyAttributeName()
1384013840
[ LOOKAHEAD(2) operatorClassParameters=PostgreSqlIndexOptions() ]
1384113841
{
@@ -13852,7 +13852,7 @@ Index.ColumnParams IndexColumnWithParams(): {
1385213852
{ if (!postgres) { columnParams.add(sortOrder.image); } } ]
1385313853
[ LOOKAHEAD(2) <K_NULLS> (nullOrdering=<K_FIRST> | nullOrdering=<K_LAST>)
1385413854
{ if (!postgres) { columnParams.add("NULLS"); columnParams.add(nullOrdering.image); } } ]
13855-
( LOOKAHEAD(2, { !Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) && getToken(1).kind != K_WITH }) parameter=CreateParameter() { columnParams.addAll(parameter); } )*
13855+
( LOOKAHEAD(2, { !Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) && getToken(1).kind != K_WITH && getToken(1).kind != K_WITHOUT }) parameter=CreateParameter() { columnParams.addAll(parameter); } )*
1385613856
{
1385713857
column = expression != null
1385813858
? new Index.ColumnParams(expression,
@@ -13913,7 +13913,11 @@ List<Index.Option> PostgreSqlIndexOptions():
1391313913
{ return options; }
1391413914
}
1391513915

13916-
List<Index.ColumnParams> IndexColumnsWithParamsList() : {
13916+
List<Index.ColumnParams> IndexColumnsWithParamsList():
13917+
{ List<Index.ColumnParams> columns; }
13918+
{ columns=IndexKeyColumns(false) { return columns; } }
13919+
13920+
List<Index.ColumnParams> IndexKeyColumns(boolean temporal) : {
1391713921
List<Index.ColumnParams> colNames = new ArrayList<Index.ColumnParams>();
1391813922
Index.ColumnParams column = null;
1391913923
}
@@ -13932,6 +13936,10 @@ List<Index.ColumnParams> IndexColumnsWithParamsList() : {
1393213936
}
1393313937
)*
1393413938

13939+
[ <K_WITHOUT> <K_OVERLAPS> {
13940+
requireDdlSyntax(temporal, "WITHOUT OVERLAPS requires a PRIMARY KEY or UNIQUE constraint");
13941+
column.setWithoutOverlaps(true);
13942+
} ]
1393513943
")"
1393613944

1393713945
{ return colNames; }
@@ -14714,7 +14722,7 @@ Index TableIndexSpec(boolean createContext):
1471414722
typeToken=<K_PRIMARY> keywordToken=<K_KEY>
1471514723
clustering=SqlServerIndexClustering()
1471614724
[ LOOKAHEAD({ clustering == null && getToken(1).kind != OPENING_BRACKET }) indexName=RelObjectName() ]
14717-
columns=IndexColumnsWithParamsList()
14725+
columns=IndexKeyColumns(true)
1471814726
TableIndexOptions(createContext, indexOptions)
1471914727
{
1472014728
index = new NamedConstraint()
@@ -14733,11 +14741,12 @@ Index TableIndexSpec(boolean createContext):
1473314741
[ LOOKAHEAD({ clustering == null && getToken(1).kind != OPENING_BRACKET
1473414742
&& getToken(1).kind != K_USING }) indexName=RelObjectName() ]
1473514743
[ using=UsingIndexType() ]
14736-
columns=IndexColumnsWithParamsList()
14744+
columns=IndexKeyColumns(true)
1473714745
TableIndexOptions(createContext, indexOptions)
1473814746
{
14739-
// MySQL ALTER preserves both the constraint symbol and the index name.
14740-
if (createContext || Dialect.MYSQL.name().equals(getAsString(Feature.dialect))) {
14747+
// PostgreSQL and MySQL ALTER preserve named constraints separately from index names.
14748+
if (createContext || Dialect.MYSQL.name().equals(getAsString(Feature.dialect))
14749+
|| Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))) {
1474114750
index = new NamedConstraint()
1474214751
.withIndexName(indexName)
1474314752
.withType(typeToken.image
@@ -15898,16 +15907,55 @@ void ReferentialActionSpec(ForeignKeyReference reference):
1589815907
}
1589915908
}
1590015909

15910+
/** Shares column/period boundaries on both sides of a foreign key. */
15911+
List<Index.ColumnParams> ForeignKeyColumns(boolean allowParameters):
15912+
{
15913+
List<Index.ColumnParams> columns = new ArrayList<Index.ColumnParams>();
15914+
Index.ColumnParams column;
15915+
}
15916+
{
15917+
"(" column=ForeignKeyColumn(allowParameters) { columns.add(column); }
15918+
( "," {
15919+
requireDdlSyntax(!column.isPeriod(), "PERIOD must mark the final foreign key column");
15920+
} column=ForeignKeyColumn(allowParameters) { columns.add(column); } )* ")"
15921+
{ return columns; }
15922+
}
15923+
15924+
Index.ColumnParams ForeignKeyColumn(boolean allowParameters):
15925+
{
15926+
boolean period = false;
15927+
String name;
15928+
List<String> parameters = null;
15929+
}
15930+
{
15931+
[ LOOKAHEAD({ isKeywordAhead("PERIOD") && getToken(2).kind != K_COMMA
15932+
&& getToken(2).kind != CLOSING_BRACKET })
15933+
ContextualKeyword("PERIOD") { period = true; } ]
15934+
name=RelObjectName()
15935+
[ parameters=CreateParameter() {
15936+
requireDdlSyntax(allowParameters && !period, "Expected a foreign key column name");
15937+
} ]
15938+
{ return new Index.ColumnParams(name, parameters).withPeriod(period); }
15939+
}
15940+
1590115941
ForeignKeyReference ForeignKeyReferenceSpec(boolean columnContext):
1590215942
{
1590315943
ForeignKeyReference reference = new ForeignKeyReference();
1590415944
ForeignKeyReference.MatchType matchType;
1590515945
Token matchToken;
1590615946
List<String> refColNames = null;
15947+
List<Index.ColumnParams> referencedColumns;
1590715948
Table fkTable;
1590815949
}
1590915950
{
15910-
<K_REFERENCES> fkTable=Table() [ LOOKAHEAD(2) refColNames=ColumnsNamesList() ]
15951+
<K_REFERENCES> fkTable=Table()
15952+
[ LOOKAHEAD(2) referencedColumns=ForeignKeyColumns(false) {
15953+
refColNames = new ArrayList<String>();
15954+
for (Index.ColumnParams column : referencedColumns) {
15955+
refColNames.add(column.getColumnName());
15956+
}
15957+
reference.setUsingPeriod(referencedColumns.get(referencedColumns.size() - 1).isPeriod());
15958+
} ]
1591115959
{
1591215960
reference.setTable(fkTable);
1591315961
reference.setReferencedColumnNames(refColNames);
@@ -15976,7 +16024,7 @@ ForeignKeyIndex ForeignKeySpec(String constraintName):
1597616024
{
1597716025
tk=<K_FOREIGN> tk2=<K_KEY>
1597816026
[ LOOKAHEAD(2) indexName=RelObjectName() { fkIndex.setIndexName(indexName); } ]
15979-
colNames = ColumnNamesWithParamsList()
16027+
colNames = ForeignKeyColumns(true)
1598016028
{
1598116029
if (constraintName != null) { fkIndex.setName(constraintName); }
1598216030
fkIndex.withType(tk.image + " " + tk2.image).withColumns(colNames);

0 commit comments

Comments
 (0)