Skip to content

Commit c52b59a

Browse files
committed
Merge master and resolve PostgreSQL routine grammar conflicts
2 parents f386a48 + 521a27f commit c52b59a

26 files changed

Lines changed: 1843 additions & 127 deletions

src/main/java/net/sf/jsqlparser/expression/Function.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import net.sf.jsqlparser.statement.select.Limit;
1818
import net.sf.jsqlparser.statement.select.OrderByElement;
1919

20+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
21+
import net.sf.jsqlparser.statement.select.PlainSelect;
22+
2023
import java.io.Serializable;
2124
import java.util.ArrayList;
2225
import java.util.Arrays;
@@ -46,6 +49,7 @@ public class Function extends ASTNodeAccessImpl implements Expression {
4649
private KeepExpression keep = null;
4750
private String onOverflowTruncate = null;
4851
private String extraKeyword = null;
52+
private List<ColumnDefinition> resultColumnDefinitions;
4953

5054
/**
5155
* Generic keyword arguments captured inside function parentheses, e.g.
@@ -55,6 +59,23 @@ public class Function extends ASTNodeAccessImpl implements Expression {
5559
*/
5660
private List<KeywordArgument> keywordArguments = null;
5761

62+
/** Column definitions supplied for a record-returning function inside ROWS FROM. */
63+
public List<ColumnDefinition> getResultColumnDefinitions() {
64+
return resultColumnDefinitions;
65+
}
66+
67+
public void setResultColumnDefinitions(List<ColumnDefinition> resultColumnDefinitions) {
68+
this.resultColumnDefinitions = resultColumnDefinitions;
69+
}
70+
71+
public StringBuilder appendResultColumnDefinitionsTo(StringBuilder builder) {
72+
if (resultColumnDefinitions != null) {
73+
builder.append(" AS ")
74+
.append(PlainSelect.getStringList(resultColumnDefinitions, true, true));
75+
}
76+
return builder;
77+
}
78+
5879
public Function() {}
5980

6081
public Function(String name, Expression... parameters) {
@@ -446,7 +467,8 @@ public String toString() {
446467
ans = "{fn " + ans + "}";
447468
}
448469

449-
return ans;
470+
return resultColumnDefinitions == null ? ans
471+
: appendResultColumnDefinitionsTo(new StringBuilder(ans)).toString();
450472
}
451473

452474
public Function withAttribute(Expression attribute) {

src/main/java/net/sf/jsqlparser/expression/LambdaExpression.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010
package net.sf.jsqlparser.expression;
1111

1212
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
13+
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
1314
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1415

1516
import java.util.ArrayList;
1617
import java.util.Collections;
1718
import java.util.List;
19+
import java.util.function.Consumer;
1820

1921
public class LambdaExpression extends ASTNodeAccessImpl implements Expression {
2022
private List<String> identifiers;
2123
private Expression expression;
24+
private boolean parenthesized;
2225

2326
public LambdaExpression(String identifier, Expression expression) {
2427
this.identifiers = Collections.singletonList(identifier);
@@ -36,7 +39,18 @@ public static LambdaExpression from(ExpressionList<? extends Expression> express
3639
for (Expression variable : expressionList) {
3740
identifiers.add(variable.toString());
3841
}
39-
return new LambdaExpression(identifiers, expression);
42+
return new LambdaExpression(identifiers, expression)
43+
.setParenthesized(expressionList instanceof ParenthesedExpressionList);
44+
}
45+
46+
/** Whether the parameter list was explicitly parenthesized, including a single parameter. */
47+
public boolean isParenthesized() {
48+
return parenthesized;
49+
}
50+
51+
public LambdaExpression setParenthesized(boolean parenthesized) {
52+
this.parenthesized = parenthesized;
53+
return this;
4054
}
4155

4256
public List<String> getIdentifiers() {
@@ -58,7 +72,11 @@ public LambdaExpression setExpression(Expression expression) {
5872
}
5973

6074
public StringBuilder appendTo(StringBuilder builder) {
61-
if (identifiers.size() == 1) {
75+
return appendTo(builder, builder::append);
76+
}
77+
78+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
79+
if (identifiers.size() == 1 && !parenthesized) {
6280
builder.append(identifiers.get(0));
6381
} else {
6482
int i = 0;
@@ -68,7 +86,9 @@ public StringBuilder appendTo(StringBuilder builder) {
6886
}
6987
builder.append(" )");
7088
}
71-
return builder.append(" -> ").append(expression);
89+
builder.append(" -> ");
90+
expressionPrinter.accept(expression);
91+
return builder;
7292
}
7393

7494
@Override

src/main/java/net/sf/jsqlparser/schema/Table.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,15 +521,16 @@ public Table getResolvedTable() {
521521
}
522522

523523
/**
524-
* Sets resolved table.
524+
* Stores a detached copy of the resolved table's identifier. Reference-specific flags and
525+
* clauses are not copied.
525526
*
526527
* @param resolvedTable the resolved table
527528
* @return this table
528529
*/
529530
public Table setResolvedTable(Table resolvedTable) {
530-
// clone, not reference
531531
if (resolvedTable != null) {
532-
this.resolvedTable = new Table(resolvedTable.getFullyQualifiedName());
532+
this.resolvedTable = new Table();
533+
resolvedTable.copyIdentifierPartsTo(this.resolvedTable);
533534
}
534535
return this;
535536
}
@@ -577,11 +578,19 @@ public static Table[] setUnsetCatalogAndSchema(String currentCatalogName,
577578
return tables;
578579
}
579580

581+
/** Copies the table identifier, table-variable flag and resolved table identifier. */
580582
@Override
581583
public Table clone() {
582-
Table clone = new Table(this.getFullyQualifiedName());
584+
Table clone = new Table();
585+
copyIdentifierPartsTo(clone);
583586
clone.setTableVariable(tableVariable);
584587
clone.setResolvedTable(this.resolvedTable != null ? this.resolvedTable.clone() : null);
585588
return clone;
586589
}
590+
591+
/** Copies identifier components and separators, leaving the target's other state unchanged. */
592+
private void copyIdentifierPartsTo(Table target) {
593+
target.partItems = new ArrayList<>(partItems);
594+
target.partDelimiters = new ArrayList<>(partDelimiters);
595+
}
587596
}

src/main/java/net/sf/jsqlparser/statement/Block.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12+
import java.util.function.Consumer;
13+
1214
public class Block implements Statement {
1315
private boolean hasSemicolonAfterEnd = false;
1416

@@ -36,9 +38,13 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
3638
}
3739

3840
public StringBuilder appendTo(StringBuilder builder) {
41+
return appendTo(builder, builder::append);
42+
}
43+
44+
public StringBuilder appendTo(StringBuilder builder, Consumer<Statements> statementsPrinter) {
3945
builder.append("BEGIN\n");
4046
if (statements != null) {
41-
builder.append(statements);
47+
statementsPrinter.accept(statements);
4248
}
4349
builder.append("END");
4450
if (hasSemicolonAfterEnd) {

src/main/java/net/sf/jsqlparser/statement/IfElseStatement.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package net.sf.jsqlparser.statement;
1212

1313
import java.util.Objects;
14+
import java.util.function.Consumer;
1415

1516
import net.sf.jsqlparser.expression.Expression;
1617

@@ -65,12 +66,21 @@ public void setUsingSemicolonForIfStatement(boolean usingSemicolonForIfStatement
6566
}
6667

6768
public StringBuilder appendTo(StringBuilder builder) {
68-
builder.append("IF ").append(condition).append(" ").append(ifStatement)
69-
.append(usingSemicolonForIfStatement ? ";" : "");
69+
return appendTo(builder, builder::append, builder::append);
70+
}
71+
72+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter,
73+
Consumer<Statement> statementPrinter) {
74+
builder.append("IF ");
75+
expressionPrinter.accept(condition);
76+
builder.append(' ');
77+
statementPrinter.accept(ifStatement);
78+
builder.append(usingSemicolonForIfStatement ? ";" : "");
7079

7180
if (elseStatement != null) {
72-
builder.append(" ELSE ").append(elseStatement)
73-
.append(usingSemicolonForElseStatement ? ";" : "");
81+
builder.append(" ELSE ");
82+
statementPrinter.accept(elseStatement);
83+
builder.append(usingSemicolonForElseStatement ? ";" : "");
7484
}
7585
return builder;
7686
}

src/main/java/net/sf/jsqlparser/statement/Statements.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.io.Serializable;
1313
import java.util.ArrayList;
1414
import java.util.List;
15+
import java.util.function.Consumer;
1516

1617
public class Statements extends ArrayList<Statement> implements Serializable {
1718

@@ -34,17 +35,30 @@ public <E extends Statement> E get(Class<E> type, int index) {
3435
return type.cast(get(index));
3536
}
3637

37-
@Override
38-
public String toString() {
39-
StringBuilder b = new StringBuilder();
38+
/**
39+
* Appends each statement using the supplied renderer and the list's separators. Blocks and
40+
* IF/ELSE statements retain their own semicolon settings. Null entries produced by error
41+
* recovery retain the legacy "null;" placeholder without invoking the renderer.
42+
*/
43+
public StringBuilder appendTo(StringBuilder builder, Consumer<Statement> statementPrinter) {
4044
for (Statement stmt : this) {
41-
// IfElseStatements and Blocks control the Semicolons by themselves
42-
if (stmt instanceof IfElseStatement || stmt instanceof Block) {
43-
b.append(stmt).append("\n");
45+
if (stmt == null) {
46+
builder.append("null");
4447
} else {
45-
b.append(stmt).append(";\n");
48+
statementPrinter.accept(stmt);
49+
}
50+
// IfElseStatements and Blocks control the Semicolons by themselves
51+
if (!(stmt instanceof IfElseStatement || stmt instanceof Block)) {
52+
builder.append(';');
4653
}
54+
builder.append('\n');
4755
}
48-
return b.toString();
56+
return builder;
57+
}
58+
59+
@Override
60+
public String toString() {
61+
StringBuilder builder = new StringBuilder();
62+
return appendTo(builder, builder::append).toString();
4963
}
5064
}

src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,27 @@ public ColDataType() {
5454
}
5555

5656
public ColDataType(String dataType, int precision, int scale) {
57-
this.dataType = dataType;
57+
this(dataType);
58+
setNumericParameters(precision < 0 ? null : Integer.valueOf(precision),
59+
scale < 0 ? null : Integer.valueOf(scale));
60+
}
61+
62+
/**
63+
* Creates a parameterized type, using {@code null} for an omitted parameter. Unlike the legacy
64+
* primitive constructor, this accepts negative scales, including {@code -1}.
65+
*/
66+
public static ColDataType fromNumericParameters(String dataType, Integer precision,
67+
Integer scale) {
68+
ColDataType type = new ColDataType(dataType);
69+
type.setNumericParameters(precision, scale);
70+
return type;
71+
}
5872

59-
if (precision >= 0) {
73+
private void setNumericParameters(Integer precision, Integer scale) {
74+
if (precision != null) {
6075
this.precision = precision;
6176
this.dataType += " (" + (precision == Integer.MAX_VALUE ? "MAX" : precision);
62-
if (scale >= 0) {
77+
if (scale != null) {
6378
this.scale = scale;
6479
this.dataType += ", " + scale;
6580
}
@@ -211,8 +226,8 @@ public void setPrecision(Integer precision) {
211226
}
212227

213228
/**
214-
* The second numeric type parameter, e.g. {@code 2} for {@code DECIMAL(10, 2)}. Returns
215-
* {@code null} when absent.
229+
* The second numeric type parameter, e.g. {@code 2} for {@code DECIMAL(10, 2)} or {@code -3}
230+
* for PostgreSQL {@code NUMERIC(2, -3)}. Returns {@code null} when absent.
216231
*/
217232
public Integer getScale() {
218233
return scale;

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

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -217,36 +217,55 @@ public String getType() {
217217
return type;
218218
}
219219

220+
/**
221+
* Sets the rendered type and refreshes its classification, including when replacing an existing
222+
* type. Null and unrecognized types reset the classification to {@link Kind#OTHER}.
223+
*/
220224
public void setType(String string) {
221225
type = string;
222-
if (kind == Kind.OTHER && string != null) {
223-
String normalized = string.toUpperCase(java.util.Locale.ROOT);
224-
if (normalized.startsWith("PRIMARY")) {
225-
kind = Kind.PRIMARY_KEY;
226-
} else if (normalized.startsWith("UNIQUE")) {
227-
kind = Kind.UNIQUE;
228-
} else if (normalized.startsWith("FULLTEXT")) {
229-
kind = Kind.FULLTEXT;
230-
} else if (normalized.startsWith("SPATIAL")) {
231-
kind = Kind.SPATIAL;
232-
} else if (normalized.startsWith("FOREIGN")) {
233-
kind = Kind.FOREIGN_KEY;
234-
} else if (normalized.startsWith("CHECK")) {
235-
kind = Kind.CHECK;
236-
} else if (normalized.startsWith("EXCLUDE")) {
237-
kind = Kind.EXCLUDE;
238-
} else if (normalized.equals("DEFAULT")) {
239-
kind = Kind.DEFAULT;
240-
} else if (normalized.contains("INDEX") || normalized.contains("KEY")) {
241-
kind = Kind.INDEX;
242-
}
226+
kind = classifyType(string);
227+
}
228+
229+
private static Kind classifyType(String type) {
230+
if (type == null) {
231+
return Kind.OTHER;
232+
}
233+
String normalized = type.trim().toUpperCase(java.util.Locale.ROOT);
234+
String keyword = normalized.split("\\s+", 2)[0];
235+
switch (keyword) {
236+
case "PRIMARY":
237+
return Kind.PRIMARY_KEY;
238+
case "UNIQUE":
239+
return Kind.UNIQUE;
240+
case "FULLTEXT":
241+
return Kind.FULLTEXT;
242+
case "SPATIAL":
243+
return Kind.SPATIAL;
244+
case "FOREIGN":
245+
return Kind.FOREIGN_KEY;
246+
case "CHECK":
247+
return Kind.CHECK;
248+
case "EXCLUDE":
249+
return Kind.EXCLUDE;
250+
case "DEFAULT":
251+
return Kind.DEFAULT;
252+
default:
253+
return normalized.equals("INDEX") || normalized.equals("KEY")
254+
|| normalized.endsWith(" INDEX") || normalized.endsWith(" KEY")
255+
? Kind.INDEX
256+
: Kind.OTHER;
243257
}
244258
}
245259

246260
public Kind getKind() {
247261
return kind;
248262
}
249263

264+
/**
265+
* Sets classification metadata without changing the rendered type. This also supports index
266+
* declarations whose keyword is stored separately. A subsequent {@link #setType(String)}
267+
* derives the classification from the new type again.
268+
*/
250269
public void setKind(Kind kind) {
251270
this.kind = kind;
252271
}

0 commit comments

Comments
 (0)