Skip to content

Commit 98b6dff

Browse files
committed
SQL: SYS COLUMNS returns ODBC specific schema
Due to some unresolvable type conflict between the expected definition in JDBC vs ODBC, the driver mode is now passed over so that certain command can change their results accordingly (in this case SYS COLUMNS) Fix #35376
1 parent 43d6ec8 commit 98b6dff

File tree

6 files changed

+137
-31
lines changed

6 files changed

+137
-31
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plan/logical/command/sys/SysColumns.java

+34-16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.xpack.sql.expression.Attribute;
1212
import org.elasticsearch.xpack.sql.expression.predicate.regex.LikePattern;
1313
import org.elasticsearch.xpack.sql.plan.logical.command.Command;
14+
import org.elasticsearch.xpack.sql.proto.Mode;
1415
import org.elasticsearch.xpack.sql.session.Rows;
1516
import org.elasticsearch.xpack.sql.session.SchemaRowSet;
1617
import org.elasticsearch.xpack.sql.session.SqlSession;
@@ -58,21 +59,29 @@ protected NodeInfo<SysColumns> info() {
5859

5960
@Override
6061
public List<Attribute> output() {
62+
return output(false);
63+
}
64+
65+
private List<Attribute> output(boolean odbcCompatible) {
66+
// https://github.com/elastic/elasticsearch/issues/35376
67+
// ODBC expects some fields as SHORT while JDBC as Integer
68+
// which causes conversion issues and CCE
69+
DataType clientBasedType = odbcCompatible ? SHORT : INTEGER;
6170
return asList(keyword("TABLE_CAT"),
6271
keyword("TABLE_SCHEM"),
6372
keyword("TABLE_NAME"),
6473
keyword("COLUMN_NAME"),
65-
field("DATA_TYPE", INTEGER),
74+
field("DATA_TYPE", clientBasedType),
6675
keyword("TYPE_NAME"),
6776
field("COLUMN_SIZE", INTEGER),
6877
field("BUFFER_LENGTH", INTEGER),
69-
field("DECIMAL_DIGITS", INTEGER),
70-
field("NUM_PREC_RADIX", INTEGER),
71-
field("NULLABLE", INTEGER),
78+
field("DECIMAL_DIGITS", clientBasedType),
79+
field("NUM_PREC_RADIX", clientBasedType),
80+
field("NULLABLE", clientBasedType),
7281
keyword("REMARKS"),
7382
keyword("COLUMN_DEF"),
74-
field("SQL_DATA_TYPE", INTEGER),
75-
field("SQL_DATETIME_SUB", INTEGER),
83+
field("SQL_DATA_TYPE", clientBasedType),
84+
field("SQL_DATETIME_SUB", clientBasedType),
7685
field("CHAR_OCTET_LENGTH", INTEGER),
7786
field("ORDINAL_POSITION", INTEGER),
7887
keyword("IS_NULLABLE"),
@@ -88,11 +97,13 @@ public List<Attribute> output() {
8897

8998
@Override
9099
public void execute(SqlSession session, ActionListener<SchemaRowSet> listener) {
100+
boolean isOdbcClient = session.settings().mode() == Mode.ODBC;
101+
List<Attribute> output = output(isOdbcClient);
91102
String cluster = session.indexResolver().clusterName();
92103

93104
// bail-out early if the catalog is present but differs
94105
if (Strings.hasText(catalog) && !cluster.equals(catalog)) {
95-
listener.onResponse(Rows.empty(output()));
106+
listener.onResponse(Rows.empty(output));
96107
return;
97108
}
98109

@@ -104,15 +115,15 @@ public void execute(SqlSession session, ActionListener<SchemaRowSet> listener) {
104115
session.indexResolver().resolveAsSeparateMappings(idx, regex, ActionListener.wrap(esIndices -> {
105116
List<List<?>> rows = new ArrayList<>();
106117
for (EsIndex esIndex : esIndices) {
107-
fillInRows(cluster, esIndex.name(), esIndex.mapping(), null, rows, columnMatcher);
118+
fillInRows(cluster, esIndex.name(), esIndex.mapping(), null, rows, columnMatcher, isOdbcClient);
108119
}
109120

110-
listener.onResponse(Rows.of(output(), rows));
121+
listener.onResponse(Rows.of(output, rows));
111122
}, listener::onFailure));
112123
}
113124

114125
static void fillInRows(String clusterName, String indexName, Map<String, EsField> mapping, String prefix, List<List<?>> rows,
115-
Pattern columnMatcher) {
126+
Pattern columnMatcher, boolean isOdbcClient) {
116127
int pos = 0;
117128
for (Map.Entry<String, EsField> entry : mapping.entrySet()) {
118129
pos++; // JDBC is 1-based so we start with 1 here
@@ -128,24 +139,24 @@ static void fillInRows(String clusterName, String indexName, Map<String, EsField
128139
null,
129140
indexName,
130141
name,
131-
type.sqlType.getVendorTypeNumber(),
142+
odbcCompatible(type.sqlType.getVendorTypeNumber(), isOdbcClient),
132143
type.esType.toUpperCase(Locale.ROOT),
133144
type.displaySize,
134145
// TODO: is the buffer_length correct?
135146
type.size,
136147
// no DECIMAL support
137148
null,
138-
DataTypes.metaSqlRadix(type),
149+
odbcCompatible(DataTypes.metaSqlRadix(type), isOdbcClient),
139150
// everything is nullable
140-
DatabaseMetaData.columnNullable,
151+
odbcCompatible(DatabaseMetaData.columnNullable, isOdbcClient),
141152
// no remarks
142153
null,
143154
// no column def
144155
null,
145156
// SQL_DATA_TYPE apparently needs to be same as DATA_TYPE except for datetime and interval data types
146-
DataTypes.metaSqlDataType(type),
157+
odbcCompatible(DataTypes.metaSqlDataType(type), isOdbcClient),
147158
// SQL_DATETIME_SUB ?
148-
DataTypes.metaSqlDateTimeSub(type),
159+
odbcCompatible(DataTypes.metaSqlDateTimeSub(type), isOdbcClient),
149160
// char octet length
150161
type.isString() || type == DataType.BINARY ? type.size : null,
151162
// position
@@ -160,11 +171,18 @@ static void fillInRows(String clusterName, String indexName, Map<String, EsField
160171
));
161172
}
162173
if (field.getProperties() != null) {
163-
fillInRows(clusterName, indexName, field.getProperties(), name, rows, columnMatcher);
174+
fillInRows(clusterName, indexName, field.getProperties(), name, rows, columnMatcher, isOdbcClient);
164175
}
165176
}
166177
}
167178

179+
private static Object odbcCompatible(Integer value, boolean isOdbcClient) {
180+
if (isOdbcClient && value != null) {
181+
return Short.valueOf(value.shortValue());
182+
}
183+
return value;
184+
}
185+
168186
@Override
169187
public int hashCode() {
170188
return Objects.hash(catalog, index, pattern, columnPattern);

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TransportSqlQueryAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static void operation(PlanExecutor planExecutor, SqlQueryRequest request,
5757
// The configuration is always created however when dealing with the next page, only the timeouts are relevant
5858
// the rest having default values (since the query is already created)
5959
Configuration cfg = new Configuration(request.timeZone(), request.fetchSize(), request.requestTimeout(), request.pageTimeout(),
60-
request.filter());
60+
request.filter(), request.mode());
6161

6262
if (Strings.hasText(request.cursor()) == false) {
6363
planExecutor.sql(cfg, request.query(), request.params(),

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/TransportSqlTranslateAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected void doExecute(Task task, SqlTranslateRequest request, ActionListener<
3939
sqlLicenseChecker.checkIfSqlAllowed(request.mode());
4040

4141
Configuration cfg = new Configuration(request.timeZone(), request.fetchSize(),
42-
request.requestTimeout(), request.pageTimeout(), request.filter());
42+
request.requestTimeout(), request.pageTimeout(), request.filter(), request.mode());
4343

4444
planExecutor.searchSource(cfg, request.query(), request.params(), ActionListener.wrap(
4545
searchSourceBuilder -> listener.onResponse(new SqlTranslateResponse(searchSourceBuilder)), listener::onFailure));

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/session/Configuration.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,32 @@
88
import org.elasticsearch.common.Nullable;
99
import org.elasticsearch.common.unit.TimeValue;
1010
import org.elasticsearch.index.query.QueryBuilder;
11+
import org.elasticsearch.xpack.sql.proto.Mode;
1112
import org.elasticsearch.xpack.sql.proto.Protocol;
1213

1314
import java.util.TimeZone;
1415

1516
// Typed object holding properties for a given action
1617
public class Configuration {
1718
public static final Configuration DEFAULT = new Configuration(TimeZone.getTimeZone("UTC"),
18-
Protocol.FETCH_SIZE, Protocol.REQUEST_TIMEOUT, Protocol.PAGE_TIMEOUT, null);
19+
Protocol.FETCH_SIZE, Protocol.REQUEST_TIMEOUT, Protocol.PAGE_TIMEOUT, null, Mode.PLAIN);
1920

20-
private TimeZone timeZone;
21-
private int pageSize;
22-
private TimeValue requestTimeout;
23-
private TimeValue pageTimeout;
21+
private final TimeZone timeZone;
22+
private final int pageSize;
23+
private final TimeValue requestTimeout;
24+
private final TimeValue pageTimeout;
25+
private final Mode mode;
2426

2527
@Nullable
2628
private QueryBuilder filter;
2729

28-
public Configuration(TimeZone tz, int pageSize, TimeValue requestTimeout, TimeValue pageTimeout, QueryBuilder filter) {
30+
public Configuration(TimeZone tz, int pageSize, TimeValue requestTimeout, TimeValue pageTimeout, QueryBuilder filter, Mode mode) {
2931
this.timeZone = tz;
3032
this.pageSize = pageSize;
3133
this.requestTimeout = requestTimeout;
3234
this.pageTimeout = pageTimeout;
3335
this.filter = filter;
36+
this.mode = mode == null ? Mode.PLAIN : mode;
3437
}
3538

3639
public TimeZone timeZone() {
@@ -52,4 +55,8 @@ public TimeValue pageTimeout() {
5255
public QueryBuilder filter() {
5356
return filter;
5457
}
58+
59+
public Mode mode() {
60+
return mode;
61+
}
5562
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plan/logical/command/sys/SysColumnsTests.java

+82-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class SysColumnsTests extends ESTestCase {
1616

1717
public void testSysColumns() {
1818
List<List<?>> rows = new ArrayList<>();
19-
SysColumns.fillInRows("test", "index", TypesTests.loadMapping("mapping-multi-field-variation.json", true), null, rows, null);
19+
SysColumns.fillInRows("test", "index", TypesTests.loadMapping("mapping-multi-field-variation.json", true), null, rows, null, false);
2020
assertEquals(16, rows.size());
2121
assertEquals(24, rows.get(0).size());
2222

@@ -58,6 +58,70 @@ public void testSysColumns() {
5858
assertEquals(Integer.MAX_VALUE, bufferLength(row));
5959
}
6060

61+
public void testSysColumnsInOdbcMode() {
62+
List<List<?>> rows = new ArrayList<>();
63+
SysColumns.fillInRows("test", "index", TypesTests.loadMapping("mapping-multi-field-variation.json", true), null, rows, null, true);
64+
assertEquals(16, rows.size());
65+
assertEquals(24, rows.get(0).size());
66+
67+
List<?> row = rows.get(0);
68+
assertEquals("bool", name(row));
69+
assertEquals((short) Types.BOOLEAN, sqlType(row));
70+
assertEquals(null, radix(row));
71+
assertEquals(1, bufferLength(row));
72+
73+
row = rows.get(1);
74+
assertEquals("int", name(row));
75+
assertEquals((short) Types.INTEGER, sqlType(row));
76+
assertEquals(Short.class, radix(row).getClass());
77+
assertEquals(4, bufferLength(row));
78+
assertNull(decimalPrecision(row));
79+
assertEquals(Short.class, nullable(row).getClass());
80+
assertEquals(Short.class, sqlDataType(row).getClass());
81+
assertEquals(Short.class, sqlDataTypeSub(row).getClass());
82+
83+
row = rows.get(2);
84+
assertEquals("text", name(row));
85+
assertEquals((short) Types.VARCHAR, sqlType(row));
86+
assertEquals(null, radix(row));
87+
assertEquals(Integer.MAX_VALUE, bufferLength(row));
88+
assertNull(decimalPrecision(row));
89+
assertEquals(Short.class, nullable(row).getClass());
90+
assertEquals(Short.class, sqlDataType(row).getClass());
91+
assertEquals(Short.class, sqlDataTypeSub(row).getClass());
92+
93+
row = rows.get(4);
94+
assertEquals("date", name(row));
95+
assertEquals((short) Types.TIMESTAMP, sqlType(row));
96+
assertEquals(null, radix(row));
97+
assertEquals(24, precision(row));
98+
assertEquals(8, bufferLength(row));
99+
assertNull(decimalPrecision(row));
100+
assertEquals(Short.class, nullable(row).getClass());
101+
assertEquals(Short.class, sqlDataType(row).getClass());
102+
assertEquals(Short.class, sqlDataTypeSub(row).getClass());
103+
104+
row = rows.get(7);
105+
assertEquals("some.dotted", name(row));
106+
assertEquals((short) Types.STRUCT, sqlType(row));
107+
assertEquals(null, radix(row));
108+
assertEquals(-1, bufferLength(row));
109+
assertNull(decimalPrecision(row));
110+
assertEquals(Short.class, nullable(row).getClass());
111+
assertEquals(Short.class, sqlDataType(row).getClass());
112+
assertEquals(Short.class, sqlDataTypeSub(row).getClass());
113+
114+
row = rows.get(15);
115+
assertEquals("some.ambiguous.normalized", name(row));
116+
assertEquals((short) Types.VARCHAR, sqlType(row));
117+
assertEquals(null, radix(row));
118+
assertEquals(Integer.MAX_VALUE, bufferLength(row));
119+
assertNull(decimalPrecision(row));
120+
assertEquals(Short.class, nullable(row).getClass());
121+
assertEquals(Short.class, sqlDataType(row).getClass());
122+
assertEquals(Short.class, sqlDataTypeSub(row).getClass());
123+
}
124+
61125
private static Object name(List<?> list) {
62126
return list.get(3);
63127
}
@@ -77,4 +141,20 @@ private static Object bufferLength(List<?> list) {
77141
private static Object radix(List<?> list) {
78142
return list.get(9);
79143
}
80-
}
144+
145+
private static Object nullable(List<?> list) {
146+
return list.get(10);
147+
}
148+
149+
private static Object decimalPrecision(List<?> list) {
150+
return list.get(8);
151+
}
152+
153+
private static Object sqlDataType(List<?> list) {
154+
return list.get(13);
155+
}
156+
157+
private static Object sqlDataTypeSub(List<?> list) {
158+
return list.get(14);
159+
}
160+
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/plan/logical/command/sys/SysParserTests.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
1616
import org.elasticsearch.xpack.sql.parser.SqlParser;
1717
import org.elasticsearch.xpack.sql.plan.logical.command.Command;
18+
import org.elasticsearch.xpack.sql.session.Configuration;
1819
import org.elasticsearch.xpack.sql.session.SqlSession;
1920
import org.elasticsearch.xpack.sql.type.DataType;
2021
import org.elasticsearch.xpack.sql.type.EsField;
@@ -50,18 +51,18 @@ private Tuple<Command, SqlSession> sql(String sql) {
5051
return Void.TYPE;
5152
}).when(resolver).resolveAsSeparateMappings(any(), any(), any());
5253

53-
SqlSession session = new SqlSession(null, null, null, resolver, null, null, null);
54+
SqlSession session = new SqlSession(Configuration.DEFAULT, null, null, resolver, null, null, null);
5455
return new Tuple<>(cmd, session);
5556
}
5657

5758
public void testSysTypes() throws Exception {
5859
Command cmd = sql("SYS TYPES").v1();
5960

6061
List<String> names = asList("BYTE", "LONG", "BINARY", "NULL", "INTEGER", "SHORT", "HALF_FLOAT", "SCALED_FLOAT", "FLOAT", "DOUBLE",
61-
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE",
62-
"INTERVAL_YEAR", "INTERVAL_MONTH", "INTERVAL_DAY", "INTERVAL_HOUR", "INTERVAL_MINUTE", "INTERVAL_SECOND",
63-
"INTERVAL_YEAR_TO_MONTH", "INTERVAL_DAY_TO_HOUR", "INTERVAL_DAY_TO_MINUTE", "INTERVAL_DAY_TO_SECOND",
64-
"INTERVAL_HOUR_TO_MINUTE", "INTERVAL_HOUR_TO_SECOND", "INTERVAL_MINUTE_TO_SECOND",
62+
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE",
63+
"INTERVAL_YEAR", "INTERVAL_MONTH", "INTERVAL_DAY", "INTERVAL_HOUR", "INTERVAL_MINUTE", "INTERVAL_SECOND",
64+
"INTERVAL_YEAR_TO_MONTH", "INTERVAL_DAY_TO_HOUR", "INTERVAL_DAY_TO_MINUTE", "INTERVAL_DAY_TO_SECOND",
65+
"INTERVAL_HOUR_TO_MINUTE", "INTERVAL_HOUR_TO_SECOND", "INTERVAL_MINUTE_TO_SECOND",
6566
"UNSUPPORTED", "OBJECT", "NESTED");
6667

6768
cmd.execute(null, ActionListener.wrap(r -> {

0 commit comments

Comments
 (0)