Skip to content

Commit 012f194

Browse files
committed
Merge master and keep PostgreSQL replication DDL independently mergeable
2 parents cd47a3c + d45f796 commit 012f194

51 files changed

Lines changed: 3583 additions & 107 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ jobs:
6464
strategy:
6565
fail-fast: false
6666
matrix:
67-
# windows-latest disabled: see #<issue> — record the reason, not just the comment
68-
os: [ ubuntu-latest, macos-latest ]
67+
os: [ ubuntu-latest, windows-latest, macos-latest ]
6968
steps:
7069
- uses: actions/checkout@v5
7170
with:

.mvn/jvm.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Dfile.encoding=UTF-8

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@
426426
<goals>
427427
<goal>jjtree-javacc</goal>
428428
</goals>
429+
<!--
430+
GRAMMAR_ENCODING governs reading only: JJTree writes the intermediate
431+
.jj with the JVM default charset, which mangles the grammar's non-ASCII
432+
character classes unless file.encoding is UTF-8 (see .mvn/jvm.config).
433+
-->
429434
<configuration>
430435
<javaccCmdLineArgs>
431436
<arg>-CODE_GENERATOR:"Java"</arg>
@@ -564,11 +569,13 @@
564569
@{jacocoArgLine} is resolved late, after jacoco:prepare-agent has run.
565570
Without it the JaCoCo agent would be dropped and coverage reports empty.
566571
-->
572+
<!-- Forked test JVMs do not inherit .mvn/jvm.config, so pin the encoding here too. -->
567573
<argLine>
568574
@{jacocoArgLine}
569575
--add-opens=java.base/java.lang=ALL-UNNAMED
570576
--add-opens=java.base/java.util=ALL-UNNAMED
571577
-Xmx2G -Xms800m -Xss4m
578+
-Dfile.encoding=UTF-8
572579
</argLine>
573580
</configuration>
574581
</plugin>

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -713,13 +713,7 @@ public <S> T visit(VariableAssignment variableAssignment, S context) {
713713

714714
@Override
715715
public <S> T visit(XMLSerializeExpr xmlSerializeExpr, S context) {
716-
ArrayList<Expression> subExpressions = new ArrayList<>();
717-
718-
subExpressions.add(xmlSerializeExpr.getExpression());
719-
for (OrderByElement orderByElement : xmlSerializeExpr.getOrderByElements()) {
720-
subExpressions.add(orderByElement.getExpression());
721-
}
722-
return visitExpressions(xmlSerializeExpr, context, subExpressions);
716+
return visitExpressions(xmlSerializeExpr, context, xmlSerializeExpr.getExpressions());
723717
}
724718

725719
@Override

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

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
*/
1010
package net.sf.jsqlparser.expression;
1111

12+
import java.util.ArrayList;
1213
import java.util.List;
14+
import java.util.function.Consumer;
1315

1416
import static java.util.stream.Collectors.joining;
1517

@@ -19,9 +21,19 @@
1921

2022
public class XMLSerializeExpr extends ASTNodeAccessImpl implements Expression {
2123

24+
public enum SerializationMode {
25+
CONTENT, DOCUMENT
26+
}
27+
2228
private Expression expression;
2329
private List<OrderByElement> orderByElements;
2430
private ColDataType dataType;
31+
private SerializationMode serializationMode;
32+
private StringValue encoding;
33+
private StringValue version;
34+
private Boolean indent;
35+
private LongValue indentSize;
36+
private Boolean showDefaults;
2537

2638
@Override
2739
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
@@ -52,11 +64,141 @@ public void setDataType(ColDataType dataType) {
5264
this.dataType = dataType;
5365
}
5466

67+
/** Null retains the legacy XMLAGG(XMLTEXT(...)) form and its existing expression getter. */
68+
public SerializationMode getSerializationMode() {
69+
return serializationMode;
70+
}
71+
72+
public void setSerializationMode(SerializationMode serializationMode) {
73+
this.serializationMode = serializationMode;
74+
}
75+
76+
public StringValue getEncoding() {
77+
return encoding;
78+
}
79+
80+
public void setEncoding(StringValue encoding) {
81+
this.encoding = encoding;
82+
}
83+
84+
public StringValue getVersion() {
85+
return version;
86+
}
87+
88+
public void setVersion(StringValue version) {
89+
this.version = version;
90+
}
91+
92+
/** Null preserves omission, true is INDENT, and false is NO INDENT. */
93+
public Boolean getIndent() {
94+
return indent;
95+
}
96+
97+
public void setIndent(Boolean indent) {
98+
this.indent = indent;
99+
}
100+
101+
public LongValue getIndentSize() {
102+
return indentSize;
103+
}
104+
105+
public void setIndentSize(LongValue indentSize) {
106+
this.indentSize = indentSize;
107+
}
108+
109+
/** Null preserves omission, true is SHOW DEFAULTS, and false is HIDE DEFAULTS. */
110+
public Boolean getShowDefaults() {
111+
return showDefaults;
112+
}
113+
114+
public void setShowDefaults(Boolean showDefaults) {
115+
this.showDefaults = showDefaults;
116+
}
117+
118+
/** Shared child discovery for expression, table-name and validation visitors. */
119+
public List<Expression> getExpressions() {
120+
List<Expression> result = new ArrayList<>();
121+
if (expression != null) {
122+
result.add(expression);
123+
}
124+
if (orderByElements != null) {
125+
for (OrderByElement orderBy : orderByElements) {
126+
result.add(orderBy.getExpression());
127+
}
128+
}
129+
if (encoding != null) {
130+
result.add(encoding);
131+
}
132+
if (version != null) {
133+
result.add(version);
134+
}
135+
if (indentSize != null) {
136+
result.add(indentSize);
137+
}
138+
return result;
139+
}
140+
141+
/** Render both forms without bypassing custom expression or ORDER BY deparsers. */
142+
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> expressionWriter,
143+
Consumer<List<OrderByElement>> orderByWriter) {
144+
validateOptions();
145+
sql.append("xmlserialize(");
146+
if (serializationMode == null) {
147+
sql.append("xmlagg(xmltext(");
148+
expressionWriter.accept(expression);
149+
sql.append(")");
150+
if (orderByElements != null) {
151+
orderByWriter.accept(orderByElements);
152+
}
153+
sql.append(") AS ").append(dataType);
154+
} else {
155+
sql.append(serializationMode).append(" ");
156+
expressionWriter.accept(expression);
157+
if (dataType != null) {
158+
sql.append(" AS ").append(dataType);
159+
}
160+
if (encoding != null) {
161+
sql.append(" ENCODING ");
162+
expressionWriter.accept(encoding);
163+
}
164+
if (version != null) {
165+
sql.append(" VERSION ");
166+
expressionWriter.accept(version);
167+
}
168+
if (indent != null) {
169+
sql.append(indent ? " INDENT" : " NO INDENT");
170+
if (indent && indentSize != null) {
171+
sql.append(" SIZE = ");
172+
expressionWriter.accept(indentSize);
173+
}
174+
}
175+
if (showDefaults != null) {
176+
sql.append(showDefaults ? " SHOW DEFAULTS" : " HIDE DEFAULTS");
177+
}
178+
}
179+
return sql.append(")");
180+
}
181+
182+
public void validateOptions() {
183+
if (indentSize != null && (!Boolean.TRUE.equals(indent) || indentSize.getValue() < 0)) {
184+
throw new IllegalArgumentException(
185+
"An indentation size requires INDENT and must be nonnegative");
186+
}
187+
if (serializationMode == null && (encoding != null || version != null || indent != null
188+
|| indentSize != null || showDefaults != null)) {
189+
throw new IllegalArgumentException("Serialization options require CONTENT or DOCUMENT");
190+
}
191+
if (serializationMode != null && orderByElements != null && !orderByElements.isEmpty()) {
192+
throw new IllegalArgumentException(
193+
"ORDER BY belongs inside the serialized XMLAGG expression");
194+
}
195+
}
196+
55197
@Override
56198
public String toString() {
57-
return "xmlserialize(xmlagg(xmltext(" + expression + ")"
58-
+ (orderByElements != null ? " ORDER BY " + orderByElements.stream()
59-
.map(OrderByElement::toString).collect(joining(", ")) : "")
60-
+ ") AS " + dataType + ")";
199+
StringBuilder sql = new StringBuilder();
200+
return appendTo(sql, sql::append, orderBy -> sql.append(" ORDER BY ")
201+
.append(orderBy.stream().map(OrderByElement::toString).collect(joining(", "))))
202+
.toString();
61203
}
62204
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,10 @@ public enum Feature {
561561
createSequence,
562562
/** Publication and subscription definitions. */
563563
createPublication, alterPublication, createSubscription, alterSubscription,
564+
/**
565+
* Structured type, domain and extension statements.
566+
*/
567+
createType, alterType, createDomain, alterDomain, createExtension, alterExtension,
564568
/**
565569
* SQL "CREATE SYNONYM" statement is allowed
566570
*

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

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
package net.sf.jsqlparser.statement;
1111

1212
import java.io.Serializable;
13+
import java.util.ArrayList;
1314
import java.util.LinkedHashMap;
1415
import java.util.List;
1516
import java.util.Locale;
16-
import java.util.stream.Collectors;
1717
import net.sf.jsqlparser.schema.Table;
1818

1919
/**
@@ -22,7 +22,8 @@
2222
public class ExplainStatement implements Statement {
2323
private String keyword;
2424
private Statement statement;
25-
private LinkedHashMap<OptionType, Option> options;
25+
private List<Option> options = new ArrayList<>();
26+
private boolean parenthesizedOptions;
2627
private Table table;
2728

2829
public ExplainStatement(String keyword) {
@@ -42,7 +43,7 @@ public ExplainStatement(String keyword, Statement statement, List<Option> option
4243
this.keyword = keyword;
4344
setStatement(statement);
4445

45-
initializeOptions(optionList);
46+
setOptionList(optionList);
4647
}
4748

4849
public ExplainStatement(Statement statement) {
@@ -72,29 +73,59 @@ public ExplainStatement setStatement(Statement statement) {
7273
}
7374

7475
public LinkedHashMap<OptionType, Option> getOptions() {
75-
return options == null ? null : new LinkedHashMap<>(options);
76+
if (options.isEmpty()) {
77+
return null;
78+
}
79+
LinkedHashMap<OptionType, Option> result = new LinkedHashMap<>();
80+
for (Option option : options) {
81+
result.put(option.getType(), option);
82+
}
83+
return result;
84+
}
85+
86+
/** Ordered options, including repetitions; the returned list is a defensive copy. */
87+
public List<Option> getOptionList() {
88+
return new ArrayList<>(options);
7689
}
7790

91+
public void setOptionList(List<Option> optionList) {
92+
options = optionList == null ? new ArrayList<>() : new ArrayList<>(optionList);
93+
}
94+
95+
public boolean isParenthesizedOptions() {
96+
return parenthesizedOptions;
97+
}
98+
99+
public ExplainStatement setParenthesizedOptions(boolean parenthesizedOptions) {
100+
this.parenthesizedOptions = parenthesizedOptions;
101+
return this;
102+
}
103+
104+
/** Adds an option, or replaces the last existing option of the same type. */
78105
public void addOption(Option option) {
79-
if (options == null) {
80-
options = new LinkedHashMap<>();
106+
for (int i = options.size() - 1; i >= 0; i--) {
107+
if (options.get(i).getType() == option.getType()) {
108+
options.set(i, option);
109+
return;
110+
}
81111
}
82-
83-
options.put(option.getType(), option);
112+
options.add(option);
84113
}
85114

86115
/**
87-
* Returns the first option that matches this optionType
116+
* Returns the last option that matches this optionType.
88117
*
89118
* @param optionType the option type to retrieve an Option for
90-
* @return an option of that type, or null. In case of duplicate options, the first found option
119+
* @return an option of that type, or null. In case of duplicate options, the last found option
91120
* will be returned.
92121
*/
93122
public Option getOption(OptionType optionType) {
94-
if (options == null) {
95-
return null;
123+
for (int i = options.size() - 1; i >= 0; i--) {
124+
if (options.get(i).getType() == optionType) {
125+
return options.get(i);
126+
}
96127
}
97-
return options.get(optionType);
128+
return null;
98129
}
99130

100131
public String getKeyword() {
@@ -112,12 +143,7 @@ public String toString() {
112143
if (table != null) {
113144
builder.append(" ").append(table);
114145
} else {
115-
if (options != null) {
116-
builder.append(" ");
117-
builder.append(options.values().stream().map(Option::formatOption)
118-
.collect(Collectors.joining(" ")));
119-
}
120-
146+
appendOptionsTo(builder);
121147
builder.append(" ");
122148
if (statement != null) {
123149
builder.append(statement);
@@ -132,17 +158,25 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
132158
return statementVisitor.visit(this, context);
133159
}
134160

135-
private void initializeOptions(List<Option> optionList) {
136-
if (optionList != null && !optionList.isEmpty()) {
137-
options = new LinkedHashMap<>();
138-
for (Option o : optionList) {
139-
options.put(o.getType(), o);
161+
/** Shared by SQL rendering and statement deparsers; includes the leading separator. */
162+
public StringBuilder appendOptionsTo(StringBuilder builder) {
163+
if (!options.isEmpty()) {
164+
builder.append(parenthesizedOptions ? " (" : " ");
165+
for (int i = 0; i < options.size(); i++) {
166+
if (i > 0) {
167+
builder.append(parenthesizedOptions ? ", " : " ");
168+
}
169+
builder.append(options.get(i).formatOption());
170+
}
171+
if (parenthesizedOptions) {
172+
builder.append(")");
140173
}
141174
}
175+
return builder;
142176
}
143177

144178
public enum OptionType {
145-
ANALYZE, VERBOSE, COSTS, BUFFERS, FORMAT, PLAN, PLAN_FOR;
179+
ANALYZE, VERBOSE, COSTS, BUFFERS, FORMAT, PLAN, PLAN_FOR, TIMING, SUMMARY, SETTINGS, WAL, GENERIC_PLAN, SERIALIZE, MEMORY;
146180

147181
public static OptionType from(String type) {
148182
return Enum.valueOf(OptionType.class, type.toUpperCase(Locale.ROOT));
@@ -171,7 +205,7 @@ public void setValue(String value) {
171205
}
172206

173207
public String formatOption() {
174-
return type.name().replace("_", " ") + (value != null
208+
return (type == OptionType.PLAN_FOR ? "PLAN FOR" : type.name()) + (value != null
175209
? " " + value
176210
: "");
177211
}

0 commit comments

Comments
 (0)