-
Notifications
You must be signed in to change notification settings - Fork 25.6k
SQL: Add xDBC and CLI support. QA CSV specs #68966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f6ebe6c
190860e
663382a
e518fa2
73eacba
06f4e5e
ea020b0
b7055a2
623d579
78076c9
93cf78b
1e33046
323af93
c4d0e01
c9cdcd3
abe2bc2
9dae2dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| package org.elasticsearch.xpack.sql.jdbc; | ||
|
|
||
| import java.sql.Array; | ||
| import java.sql.ResultSet; | ||
| import java.sql.SQLException; | ||
| import java.sql.SQLFeatureNotSupportedException; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import static org.elasticsearch.xpack.sql.jdbc.TypeUtils.baseType; | ||
|
|
||
| public class JdbcArray implements Array { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should have package visibility to avoid instantiation outside the package.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, thanks, fixed. |
||
|
|
||
| private final EsType type; | ||
| private final List<?> values; | ||
|
|
||
| public JdbcArray(EsType type, List<?> values) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass in the base type separately instead of determining it from the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to pass the base type. |
||
| this.type = type; | ||
| this.values = values; | ||
| } | ||
| @Override | ||
| public String getBaseTypeName() throws SQLException { | ||
| return baseType(type).getName(); | ||
| } | ||
|
|
||
| @Override | ||
| public int getBaseType() throws SQLException { | ||
| return baseType(type).getVendorTypeNumber(); | ||
| } | ||
|
|
||
| @Override | ||
| public Object getArray() throws SQLException { | ||
| return values.toArray(); | ||
| } | ||
|
|
||
| @Override | ||
| public Object getArray(Map<String, Class<?>> map) throws SQLException { | ||
| if (map == null || map.isEmpty()) { | ||
| return getArray(); | ||
| } | ||
| throw new SQLFeatureNotSupportedException("getArray with non-empty Map not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public Object getArray(long index, int count) throws SQLException { | ||
| if (index < 1 || index > Integer.MAX_VALUE) { | ||
| throw new SQLException("Index value [" + index + "] out of range [1, " + Integer.MAX_VALUE + "]"); | ||
| } | ||
| if (count < 0) { | ||
| throw new SQLException("Illegal negative count [" + count + "]"); | ||
| } | ||
| int index0 = (int) index - 1; // 0-based index | ||
| int available = index0 < values.size() ? values.size() - index0 : 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you double check the logic here, because I'm not sure it's correct. From what I can see
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right, it could, but should not, as it is 1-based index.
Correct. The out of range exception you exemplified below was triggered due to the incorrect initial check, |
||
| return available > 0 ? | ||
| Arrays.copyOfRange(values.toArray(), index0, index0 + Math.min(count, available)): | ||
| new Object[0]; | ||
| } | ||
|
|
||
| @Override | ||
| public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException { | ||
| if (map == null || map.isEmpty()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small preference, it's better to do the validation and error checking upfront as oppose to last.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated it. |
||
| return getArray(index, count); | ||
| } | ||
| throw new SQLFeatureNotSupportedException("getArray with non-empty Map not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public ResultSet getResultSet() throws SQLException { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A follow-up should implement this method - typically by returning a ResultSet that contains two columns - index and value.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ResultSet methods added in #69512. |
||
| throw new SQLFeatureNotSupportedException("Array as ResultSet not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException { | ||
| throw new SQLFeatureNotSupportedException("Array as ResultSet not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public ResultSet getResultSet(long index, int count) throws SQLException { | ||
| throw new SQLFeatureNotSupportedException("Array as ResultSet not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException { | ||
| throw new SQLFeatureNotSupportedException("Array as ResultSet not supported"); | ||
| } | ||
|
|
||
| @Override | ||
| public void free() throws SQLException { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once this method is called, all other methods should return an exception which is not the case.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, on another look, that phrasing is mandatory. Check added. |
||
| // nop | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,8 +27,10 @@ | |
| import java.time.Period; | ||
| import java.time.ZoneOffset; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.Calendar; | ||
| import java.util.GregorianCalendar; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.function.Function; | ||
|
|
||
|
|
@@ -46,6 +48,7 @@ | |
| import static org.elasticsearch.xpack.sql.jdbc.EsType.TIME; | ||
| import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.asDateTimeField; | ||
| import static org.elasticsearch.xpack.sql.jdbc.JdbcDateUtils.timeAsTime; | ||
| import static org.elasticsearch.xpack.sql.jdbc.TypeUtils.baseType; | ||
|
|
||
| /** | ||
| * Conversion utilities for conversion of JDBC types to Java type and back | ||
|
|
@@ -263,6 +266,29 @@ static Object convert(Object v, EsType columnType, String typeString) throws SQL | |
| } | ||
| case IP: | ||
| return v.toString(); | ||
| case BOOLEAN_ARRAY: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs to be improved |
||
| case BYTE_ARRAY: | ||
| case SHORT_ARRAY: | ||
| case INTEGER_ARRAY: | ||
| case LONG_ARRAY: | ||
| case DOUBLE_ARRAY: | ||
| case FLOAT_ARRAY: | ||
| case HALF_FLOAT_ARRAY: | ||
| case SCALED_FLOAT_ARRAY: | ||
| case KEYWORD_ARRAY: | ||
| case TEXT_ARRAY: | ||
| case DATETIME_ARRAY: | ||
| case IP_ARRAY: | ||
| case BINARY_ARRAY: | ||
| case GEO_SHAPE_ARRAY: | ||
| case GEO_POINT_ARRAY: | ||
| case SHAPE_ARRAY: | ||
| List<Object> values = new ArrayList<>(); | ||
| for (Object o : (List<?>) v) { | ||
| // null value expects a NULL type in the converter | ||
| values.add(o == null ? null : convert(o, baseType(columnType), typeString.substring(0, "_ARRAY".length()))); | ||
| } | ||
| return values; | ||
| default: | ||
| throw new SQLException("Unexpected column type [" + typeString + "]"); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| import java.sql.SQLFeatureNotSupportedException; | ||
| import java.sql.SQLType; | ||
| import java.sql.Timestamp; | ||
| import java.sql.Types; | ||
| import java.time.Duration; | ||
| import java.time.LocalDateTime; | ||
| import java.time.Period; | ||
|
|
@@ -176,4 +177,22 @@ static EsType of(Class<? extends Object> clazz) throws SQLException { | |
| } | ||
| return dataType; | ||
| } | ||
|
|
||
| static EsType baseType(EsType type) throws SQLException { | ||
| String typeName = type.getName(); | ||
| return isArray(type) | ||
| ? of(typeName.substring(0, typeName.length() - "_ARRAY".length()).toLowerCase(Locale.ROOT)) | ||
| : type; | ||
| } | ||
|
|
||
| static EsType arrayOf(EsType type) throws SQLException { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this method here, since it's only used in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes. ( |
||
| if (isArray(type)) { | ||
| throw new SQLException("multidimentional array types not supported"); | ||
| } | ||
| return ENUM_NAME_TO_TYPE.get(type.name().toLowerCase(Locale.ROOT) + "_array"); | ||
| } | ||
|
|
||
| static boolean isArray(EsType type) { | ||
| return type.getVendorTypeNumber() == Types.ARRAY; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| package org.elasticsearch.xpack.sql.jdbc; | ||
|
|
||
| import org.elasticsearch.test.ESTestCase; | ||
|
|
||
| import java.sql.Array; | ||
| import java.sql.SQLException; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| import static java.util.Arrays.asList; | ||
| import static java.util.Collections.emptyList; | ||
| import static org.elasticsearch.xpack.sql.jdbc.TypeUtils.baseType; | ||
|
|
||
| public class JdbcArrayTests extends ESTestCase { | ||
|
|
||
| static final List<EsType> ARRAY_TYPES = Arrays.stream(EsType.values()).filter(TypeUtils::isArray).collect(Collectors.toList()); | ||
|
|
||
| public void testMetaData() throws Exception { | ||
| for (EsType arrayType : ARRAY_TYPES) { | ||
| Array array = new JdbcArray(arrayType, emptyList()); | ||
|
|
||
| assertEquals(baseType(arrayType).getVendorTypeNumber().intValue(), array.getBaseType()); | ||
| assertEquals(baseType(arrayType).getName(), array.getBaseTypeName()); | ||
| } | ||
| } | ||
|
|
||
| public void testGetArray() throws SQLException { | ||
| List<Long> expected = randomList(1, 10, ESTestCase::randomLong); | ||
| Array array = new JdbcArray(EsType.LONG_ARRAY, expected); | ||
|
|
||
| List<?> actual = asList((Object[]) array.getArray()); | ||
| assertEquals(expected, actual); | ||
| } | ||
|
|
||
| public void testArraySlicing() throws SQLException { | ||
| List<Integer> values = IntStream.rangeClosed(0, 9).boxed().collect(Collectors.toList()); | ||
| Array array = new JdbcArray(EsType.INTEGER_ARRAY, values); | ||
|
|
||
| Object[] empty = (Object[]) array.getArray(11, 2); | ||
| assertEquals(0, empty.length); | ||
|
|
||
| Object[] edgeSingleton = (Object[]) array.getArray(10, 2); | ||
| assertEquals(9, edgeSingleton[0]); | ||
|
|
||
| Object[] midSingleton = (Object[]) array.getArray(5, 1); | ||
| assertEquals(4, midSingleton[0]); | ||
|
|
||
| Object[] contained = (Object[]) array.getArray(4, 3); | ||
| assertEquals(asList(3, 4, 5), asList(contained)); | ||
|
|
||
| Object[] overlapping = (Object[]) array.getArray(9, 3); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this test class my assumption about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, thanks, fixed. |
||
| assertEquals(asList(8, 9), asList(overlapping)); | ||
|
|
||
| SQLException sqle = expectThrows(SQLException.class, () -> array.getArray(0, 9)); | ||
| assertEquals("Index value [0] out of range [1, 2147483647]", sqle.getMessage()); | ||
|
|
||
| sqle = expectThrows(SQLException.class, () -> array.getArray(Integer.MAX_VALUE + 1L, 9)); | ||
| assertEquals("Index value [2147483648] out of range [1, 2147483647]", sqle.getMessage()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import java.time.LocalDateTime; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.Calendar; | ||
| import java.util.Collections; | ||
| import java.util.Date; | ||
| import java.util.Locale; | ||
| import java.util.Map; | ||
|
|
@@ -585,6 +586,13 @@ public void testThrownExceptionsWhenSettingByteArrayValuesToEsTypes() throws SQL | |
| assertEquals("Conversion from type [byte[]] to [INTERVAL_DAY_TO_MINUTE] not supported", sqle.getMessage()); | ||
| } | ||
|
|
||
| public void testThrownExceptionWhenSettingArrayValue() throws SQLException { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thx! |
||
| JdbcPreparedStatement jps = createJdbcPreparedStatement(); | ||
| JdbcArray array = new JdbcArray(LONG, Collections.emptyList()); | ||
| SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setArray(1, array)); | ||
| assertEquals("Objects of type [java.sql.Array] are not supported", sqle.getMessage()); | ||
| } | ||
|
|
||
| private JdbcPreparedStatement createJdbcPreparedStatement() throws SQLException { | ||
| return new JdbcPreparedStatement(null, JdbcConfiguration.create("jdbc:es://l:1", null, 0), "?"); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed with @bpintea to refactor this to use only a generic
ARRAYtype.