Skip to content

Commit faaf084

Browse files
committed
SQL: SYS COLUMNS returns ODBC specific schema (#35870)
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 (cherry picked from commit d291b08)
1 parent adcdc0d commit faaf084

File tree

6 files changed

+138
-32
lines changed

6 files changed

+138
-32
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public static void operation(PlanExecutor planExecutor, SqlQueryRequest request,
6363
// The configuration is always created however when dealing with the next page, only the timeouts are relevant
6464
// the rest having default values (since the query is already created)
6565
Configuration cfg = new Configuration(request.timeZone(), request.fetchSize(), request.requestTimeout(), request.pageTimeout(),
66-
request.filter());
67-
66+
request.filter(), request.mode());
67+
6868
// mode() shouldn't be null
6969
QueryMetric metric = QueryMetric.from(request.mode(), request.clientId());
7070
planExecutor.metrics().total(metric);

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ protected void doExecute(SqlTranslateRequest request, ActionListener<SqlTranslat
4545

4646
planExecutor.metrics().translate();
4747
Configuration cfg = new Configuration(request.timeZone(), request.fetchSize(),
48-
request.requestTimeout(), request.pageTimeout(), request.filter());
48+
request.requestTimeout(), request.pageTimeout(), request.filter(), request.mode());
4949

5050
planExecutor.searchSource(cfg, request.query(), request.params(), ActionListener.wrap(
5151
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
}
@@ -74,7 +138,23 @@ private static Object bufferLength(List<?> list) {
74138
return list.get(7);
75139
}
76140

141+
private static Object decimalPrecision(List<?> list) {
142+
return list.get(8);
143+
}
144+
77145
private static Object radix(List<?> list) {
78146
return list.get(9);
79147
}
80-
}
148+
149+
private static Object nullable(List<?> list) {
150+
return list.get(10);
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
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
1717
import org.elasticsearch.xpack.sql.parser.SqlParser;
1818
import org.elasticsearch.xpack.sql.plan.logical.command.Command;
19+
import org.elasticsearch.xpack.sql.session.Configuration;
1920
import org.elasticsearch.xpack.sql.session.SqlSession;
2021
import org.elasticsearch.xpack.sql.stats.Metrics;
2122
import org.elasticsearch.xpack.sql.type.DataType;
@@ -53,18 +54,18 @@ private Tuple<Command, SqlSession> sql(String sql) {
5354
return Void.TYPE;
5455
}).when(resolver).resolveAsSeparateMappings(any(), any(), any());
5556

56-
SqlSession session = new SqlSession(null, null, null, resolver, null, null, null, null);
57+
SqlSession session = new SqlSession(Configuration.DEFAULT, null, null, resolver, null, null, null, null);
5758
return new Tuple<>(cmd, session);
5859
}
5960

6061
public void testSysTypes() throws Exception {
6162
Command cmd = sql("SYS TYPES").v1();
6263

6364
List<String> names = asList("BYTE", "LONG", "BINARY", "NULL", "INTEGER", "SHORT", "HALF_FLOAT", "SCALED_FLOAT", "FLOAT", "DOUBLE",
64-
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE",
65-
"INTERVAL_YEAR", "INTERVAL_MONTH", "INTERVAL_DAY", "INTERVAL_HOUR", "INTERVAL_MINUTE", "INTERVAL_SECOND",
66-
"INTERVAL_YEAR_TO_MONTH", "INTERVAL_DAY_TO_HOUR", "INTERVAL_DAY_TO_MINUTE", "INTERVAL_DAY_TO_SECOND",
67-
"INTERVAL_HOUR_TO_MINUTE", "INTERVAL_HOUR_TO_SECOND", "INTERVAL_MINUTE_TO_SECOND",
65+
"KEYWORD", "TEXT", "IP", "BOOLEAN", "DATE",
66+
"INTERVAL_YEAR", "INTERVAL_MONTH", "INTERVAL_DAY", "INTERVAL_HOUR", "INTERVAL_MINUTE", "INTERVAL_SECOND",
67+
"INTERVAL_YEAR_TO_MONTH", "INTERVAL_DAY_TO_HOUR", "INTERVAL_DAY_TO_MINUTE", "INTERVAL_DAY_TO_SECOND",
68+
"INTERVAL_HOUR_TO_MINUTE", "INTERVAL_HOUR_TO_SECOND", "INTERVAL_MINUTE_TO_SECOND",
6869
"UNSUPPORTED", "OBJECT", "NESTED");
6970

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

0 commit comments

Comments
 (0)