Support SQL Server typed XML schema collections by minleejae · Pull Request #2559 · JSQLParser/JSqlParser · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create.table;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/** A SQL Server XML schema collection reference, with an optional CONTENT/DOCUMENT facet. */
public final class XmlTypeModifier implements Serializable {
public enum Kind {
CONTENT, DOCUMENT
}

private final Kind kind;
private final List<String> schemaCollection;

public XmlTypeModifier(Kind kind, List<String> schemaCollection) {
if (schemaCollection == null || schemaCollection.isEmpty() || schemaCollection.size() > 2
|| schemaCollection.stream().anyMatch(Objects::isNull)) {
throw new IllegalArgumentException(
"An XML schema collection needs a name and optional schema");
}
this.kind = kind;
this.schemaCollection = Collections.unmodifiableList(new ArrayList<>(schemaCollection));
}

/** Null preserves an omitted facet, which SQL Server interprets as CONTENT. */
public Kind getKind() {
return kind;
}

public Kind getEffectiveKind() {
return kind == null ? Kind.CONTENT : kind;
}

/** Name parts retain their original identifier quoting. */
public List<String> getSchemaCollection() {
return schemaCollection;
}

@Override
public String toString() {
return "(" + (kind == null ? "" : kind + " ") + String.join(".", schemaCollection) + ")";
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof XmlTypeModifier)) {
return false;
}
XmlTypeModifier that = (XmlTypeModifier) other;
return kind == that.kind && schemaCollection.equals(that.schemaCollection);
}

@Override
public int hashCode() {
return Objects.hash(kind, schemaCollection);
}
}
32 changes: 32 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,11 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
return token.image != null && keyword.equalsIgnoreCase(token.image);
}

private boolean isXmlTypeName(String name) {
return "XML".equalsIgnoreCase(name) || "[XML]".equalsIgnoreCase(name)
|| "\"XML\"".equalsIgnoreCase(name);
}

private boolean isMySqlTableOptionAhead() {
int kind = getToken(1).kind;
if (kind == K_ENGINE || kind == K_COLLATE || kind == K_COMMENT
Expand Down Expand Up @@ -12668,9 +12673,15 @@ ColDataType ColDataType():
Token nationalType = null;
Token varying = null;
ColDataType.TypeModifier typeModifier = null;
XmlTypeModifier xmlTypeModifier = null;
}
{
(
LOOKAHEAD({ isXmlTypeName(getToken(1).image) && "(".equals(getToken(2).image) })
( tk=<K_XML> | tk=<S_QUOTED_IDENTIFIER> )
{ colDataType.setDataType(tk.image); }
xmlTypeModifier = XmlTypeModifier() { colDataType.setXmlTypeModifier(xmlTypeModifier); }
|
(
<K_STRUCT>
"("
Expand Down Expand Up @@ -12788,6 +12799,8 @@ ColDataType ColDataType():
[ LOOKAHEAD(2) <K_CHARACTER> <K_SET> (tk=<S_IDENTIFIER> | tk=<K_BINARY>) { colDataType.setCharacterSet(tk.image); } ]

{
requireDdlSyntax(colDataType.getXmlTypeModifier() == null || argumentsStringList.isEmpty(),
"Typed XML cannot have additional type arguments");
if (argumentsStringList.size() > 0) {
colDataType.setArgumentsStringList(argumentsStringList);
// Digits-only arguments are the type's numeric parameters, e.g. mediumint(9).
Expand All @@ -12806,6 +12819,25 @@ ColDataType ColDataType():
}
}

XmlTypeModifier XmlTypeModifier():
{
XmlTypeModifier.Kind kind = null;
List<String> names = new ArrayList<String>();
String name;
Token token;
}
{
"("
[ LOOKAHEAD({ (isKeywordAhead("CONTENT") || isKeywordAhead("DOCUMENT"))
&& !".".equals(getToken(2).image) && !")".equals(getToken(2).image) })
token=<S_IDENTIFIER> { kind = XmlTypeModifier.Kind.valueOf(token.image.toUpperCase(Locale.ROOT)); }
]
name=RelObjectName() { names.add(name); }
[ "." name=RelObjectNameExt() { names.add(name); } ]
")"
{ return new XmlTypeModifier(kind, names); }
}

ColDataType.TypeModifier MySqlTypeModifier():
{
ColDataType.TypeModifier modifier;
Expand Down
Loading