Skip to content

Commit ed6c499

Browse files
authored
feat: parse SQL large-object type names through shared column types (#2617)
1 parent 2aa0d8d commit ed6c499

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,6 +1714,21 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
17141714
}
17151715
}
17161716

1717+
private boolean isLargeObjectTypeAhead() {
1718+
int offset = 1;
1719+
if (isKeywordAhead("NATIONAL")) {
1720+
offset++;
1721+
if (!"CHARACTER".equalsIgnoreCase(getToken(offset).image)) {
1722+
return false;
1723+
}
1724+
}
1725+
String name = getToken(offset).image;
1726+
return ("CHARACTER".equalsIgnoreCase(name) || "CHAR".equalsIgnoreCase(name)
1727+
|| "NCHAR".equalsIgnoreCase(name) || "BINARY".equalsIgnoreCase(name))
1728+
&& "LARGE".equalsIgnoreCase(getToken(offset + 1).image)
1729+
&& "OBJECT".equalsIgnoreCase(getToken(offset + 2).image);
1730+
}
1731+
17171732
/**
17181733
* Extracts the numeric precision embedded in a DT_ZONE token image such as
17191734
* "TIMESTAMP(3) WITH TIME ZONE", or null when the image carries no parameter.
@@ -13600,6 +13615,22 @@ ColDataType DataType():
1360013615
}
1360113616
}
1360213617

13618+
/** Shared SQL large-object names; length and array suffixes stay in ColDataType. */
13619+
String LargeObjectTypeName():
13620+
{
13621+
Token national = null;
13622+
Token type;
13623+
Token large;
13624+
Token object;
13625+
}
13626+
{
13627+
[ LOOKAHEAD(<S_IDENTIFIER>) national=<S_IDENTIFIER> ]
13628+
( type=<K_CHARACTER> | type=<K_CHAR> | type=<K_BINARY> | type=<DATA_TYPE> )
13629+
large=<S_IDENTIFIER> object=<S_IDENTIFIER>
13630+
{ return (national == null ? "" : national.image + " ")
13631+
+ type.image + " " + large.image + " " + object.image; }
13632+
}
13633+
1360313634
ColDataType ColDataType():
1360413635
{
1360513636
ColDataType colDataType = new ColDataType();
@@ -13626,6 +13657,9 @@ ColDataType ColDataType():
1362613657
}
1362713658
{
1362813659
(
13660+
LOOKAHEAD({ isLargeObjectTypeAhead() }) type=LargeObjectTypeName()
13661+
{ colDataType.setDataType(type); }
13662+
|
1362913663
LOOKAHEAD({ isXmlTypeName(getToken(1).image) && "(".equals(getToken(2).image) })
1363013664
( tk=<K_XML> | tk=<S_QUOTED_IDENTIFIER> )
1363113665
{ colDataType.setDataType(tk.image); }
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.parser;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
15+
16+
import java.util.ArrayList;
17+
import java.util.Arrays;
18+
import java.util.List;
19+
import net.sf.jsqlparser.JSQLParserException;
20+
import net.sf.jsqlparser.expression.CastExpression;
21+
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
22+
import net.sf.jsqlparser.expression.StringValue;
23+
import net.sf.jsqlparser.statement.create.table.ColDataType;
24+
import net.sf.jsqlparser.statement.select.PlainSelect;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.ValueSource;
28+
29+
class LargeObjectTypeTest {
30+
@ParameterizedTest
31+
@ValueSource(strings = {"CHARACTER LARGE OBJECT", "CHAR LARGE OBJECT", "NCHAR LARGE OBJECT",
32+
"NATIONAL CHARACTER LARGE OBJECT", "BINARY LARGE OBJECT", "character large object"})
33+
void sharesTypeNameAndLengthAcrossTypeFragmentsCastsAndColumns(String name) throws Exception {
34+
ColDataType type = CCJSqlParserUtil.parseColDataType(name + "(3)");
35+
assertEquals(name, type.getDataType());
36+
assertEquals(3, type.getPrecision());
37+
assertEquals(Arrays.asList("3"), type.getArgumentsStringList());
38+
assertEquals(name, CCJSqlParserUtil.parseColDataType(name).getDataType());
39+
PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(
40+
"SELECT CAST('abc' AS " + name + "(3)) FROM t");
41+
CastExpression cast = (CastExpression) select.getSelectItem(0).getExpression();
42+
assertEquals(name, cast.getColDataType().getDataType());
43+
assertEquals(3, cast.getColDataType().getPrecision());
44+
assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (payload " + name + "(3) NOT NULL)");
45+
assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD payload " + name + "(3)");
46+
assertEquals(type.toString(),
47+
CCJSqlParserUtil.parseColDataType(type.toString()).toString());
48+
}
49+
50+
@Test
51+
void parsesTheDownstreamConditionWithoutRewritingLiteralContents() throws Exception {
52+
String sql = "(status IN (CAST('hi' AS CHARACTER LARGE OBJECT(2)), "
53+
+ "CAST('low' AS CHARACTER LARGE OBJECT(3))))";
54+
var condition = CCJSqlParserUtil.parseCondExpression(sql, false);
55+
List<String> literals = new ArrayList<>();
56+
condition.accept(new ExpressionVisitorAdapter<Void>() {
57+
@Override
58+
public <S> Void visit(StringValue value, S context) {
59+
literals.add(value.getValue());
60+
return null;
61+
}
62+
}, null);
63+
assertEquals(Arrays.asList("hi", "low"), literals);
64+
assertEquals(condition.toString(),
65+
CCJSqlParserUtil.parseCondExpression(condition.toString(), false).toString());
66+
assertSqlCanBeParsedAndDeparsed("SELECT CAST('CHARACTER LARGE OBJECT' "
67+
+ "AS CHARACTER LARGE OBJECT(22)) FROM t");
68+
}
69+
70+
@Test
71+
void acceptsTokenWhitespaceWithoutChangingIdentifiersOrExistingTypes() throws Exception {
72+
assertEquals("CHARACTER LARGE OBJECT",
73+
CCJSqlParserUtil.parseColDataType("CHARACTER /* comment */ LARGE\nOBJECT")
74+
.getDataType());
75+
for (String type : Arrays.asList("CHARACTER VARYING(3)", "NATIONAL CHARACTER(3)",
76+
"CLOB", "BLOB", "\"CHARACTER LARGE OBJECT\"", "app.large")) {
77+
ColDataType parsed = CCJSqlParserUtil.parseColDataType(type);
78+
assertEquals(parsed.toString(),
79+
CCJSqlParserUtil.parseColDataType(parsed.toString()).toString());
80+
}
81+
assertSqlCanBeParsedAndDeparsed("SELECT large, object FROM t");
82+
}
83+
84+
@ParameterizedTest
85+
@ValueSource(strings = {"CHARACTER LARGE", "CHARACTER LARGE OTHER", "INT LARGE OBJECT"})
86+
void rejectsIncompleteOrNonCharacterTypeNames(String type) {
87+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseColDataType(type));
88+
}
89+
}

0 commit comments

Comments
 (0)