Skip to content
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

Feature/249 evaluate expected resultset datatype #250

Merged
merged 10 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ This is one of the modules of Virtual Schemas Adapters. The libraries provided b

A Virtual Schema adapter is basically a [UDF](https://docs.exasol.com/database_concepts/udf_scripts.htm). The Exasol core database communicates with this UDF using JSON strings. There are different types of messages, that define the API for a virtual Schema adapter ([protocol reference](doc/development/api/virtual_schema_api.md)). This repository wraps this JSON API with a Java API to facilitate the implementation of Virtual Schema adapters in Java.

Please note that the artifact name changed from "virtualschema-common" to "virtual-schema-common-java". First to unify the naming schemes, second to make sure the new adapters do not accidentally use the old line of libraries.

## Information for Users

* [Changelog](doc/changes/changelog.md)
Expand Down
12 changes: 10 additions & 2 deletions doc/changes/changes_16.0.0.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# Common module of Exasol Virtual Schemas Adapters 16.0.0, released 2022-??-??

Code name:
Code name: Evaluate expected resultset datatypes

## Summary

Starting with major version 8 Exasol database uses the capabilities reported by each virtual schema to provide select list data types for each push down request. Based on this information the JDBC virtual schemas no longer need to infer the data types of the result set by inspecting its values. Instead the JDBC virtual schemas can now use the information provided by the database.

This create a list of benefits
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This create a list of benefits
This has the following benefits:

* Improved performance of queries to virtual schema by avoiding one query for each push down
* Enhanced accuracy of data type mapping
* Simplified data type mapping which is easier to extend
* Support for additional use cases

## Features

* ISSUE_NUMBER: description
* #249: Evaluate expected resultset datatypes

## Dependency Updates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,32 @@
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;

/**
* Starting with major version 8 Exasol database uses the capabilities reported by each virtual schema to provide select
* list data types for each push down request. Based on this information the JDBC virtual schemas no longer need to
* infer the data types of the result set by inspecting its values. Instead the JDBC virtual schemas can now use the
* information provided by the database.
*
* <p>
* Class {@link DataTypeParser} parses the data types from json.
* </p>
*/
public class DataTypeParser {

/**
* @return new instance of {@link DataTypeParser}
*/
public static DataTypeParser create() {
return new DataTypeParser();
}

private DataTypeParser() {
}

/**
* @param jsonArray {@link JsonArray} containing the data types to parse
* @return list of parsed data types
*/
public List<DataType> parse(final JsonArray jsonArray) {
return jsonArray.getValuesAs(JsonObject.class).stream() //
.map(this::datatype) //
Expand Down Expand Up @@ -63,11 +80,29 @@ private DataType datatype(final JsonObject entry) {
}
}

/**
* Signal an error during parsing data types from json.
*/
public static class DataTypeParserException extends RuntimeException {
private static final long serialVersionUID = 1L;

/**
* Create a new instance of {@link DataTypeParserException}
*
* @param message message of the exception
*/
public DataTypeParserException(final String message) {
super(message);
}

/**
* Create a new instance of {@link DataTypeParserException}
*
* @param message message of the exception
* @param exception inner exception being the cause of the current exception
*/
public DataTypeParserException(final String message, final Exception exception) {
super(message, exception);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@

class DataTypeProperty<T> {

static StringProperty TYPE = new StringProperty("type");
static IntProperty PRECISION = new IntProperty("precision");
static IntProperty SCALE = new IntProperty("scale");
static IntProperty SIZE = new IntProperty("size");
static CharsetProperty CHARSET = new CharsetProperty("characterSet");
static final StringProperty TYPE = new StringProperty("type");
static final IntProperty PRECISION = new IntProperty("precision");
static final IntProperty SCALE = new IntProperty("scale");
static final IntProperty SIZE = new IntProperty("size");
static final CharsetProperty CHARSET = new CharsetProperty("characterSet");
// These can only be verified by using exasol-virtual-schema
// as most other virtual schemas do not support data types using any of these properties

// TODO: verify with data type TIMESTAMP!
static BooleanProperty WITH_LOCAL_TIMEZONE = new BooleanProperty("withlocaltimezone");
static IntProperty FRACTION = new IntProperty("fraction"); // TODO: verify with data type INTERVAL!
static IntProperty BYTESIZE = new IntProperty("byteSize"); // TODO: verify with data type HASHTYPE!
// to do: verify with data type TIMESTAMP!
static final BooleanProperty WITH_LOCAL_TIMEZONE = new BooleanProperty("withLocalTimeZone");
static final IntProperty FRACTION = new IntProperty("fraction"); // to do: verify with data type INTERVAL!
static final IntProperty BYTESIZE = new IntProperty("byteSize"); // to do: verify with data type HASHTYPE!

protected final String key;
private final DataTypeProperty.JsonGetter<T> getter;
Expand All @@ -37,11 +37,11 @@ public T get(final JsonObject json, final T defaultValue) {
if (json.containsKey(this.key)) {
try {
return this.getter.apply(json, this.key);
} catch (final Exception e) {
} catch (final Exception exception) {
throw new DataTypeParserException(ExaError.messageBuilder("E-VSCOMJAVA-39") //
.message("Datatype {{datatype}}, property {{property}}: Illegal value {{value}}.", //
TYPE.get(json), this.key, json.get(this.key))
.ticketMitigation().toString());
.ticketMitigation().toString(), exception);
}
}
if (defaultValue != null) {
Expand Down
9 changes: 8 additions & 1 deletion src/test/java/com/exasol/adapter/metadata/DataTypeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@

import org.junit.jupiter.api.Test;

import nl.jqno.equalsverifier.EqualsVerifier;

class DataTypeTest {
@Test
void testEqualsAndHashContract() {
EqualsVerifier.simple().forClass(DataType.class).verify();
}

@Test
void createDecimal() {
final DataType dataType = DataType.createDecimal(10, 2);
Expand Down Expand Up @@ -111,7 +118,7 @@ void testCreateMaximumSizeChar() {
void createHashtype() {
final DataType dataType = DataType.createHashtype(16);
assertAll(() -> assertThat(dataType.getByteSize(), equalTo(16)),
() -> assertThat(dataType.toString(), equalTo("HASHTYPE(16 byte)")));
() -> assertThat(dataType.toString(), equalTo("HASHTYPE(16 byte)")));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class DataTypeParserTest {
@ValueSource(strings = { "UNKNOWN", "abcdef" })
@NullSource
void unknownDatatype_ThrowsException(final String name) {
final Exception e = assertThrows(DataTypeParserException.class,
() -> parse(JsonEntry.array(group(entry("type", name)))));
final JsonEntry data = JsonEntry.array(group(entry("type", name)));
final Exception e = assertThrows(DataTypeParserException.class, () -> parse(data));
assertThat(e.getMessage(), startsWith("E-VSCOMJAVA-37: Unsupported datatype '" + name + "'"));
}

@Test
void missingType_ThrowsException() {
final Exception e = assertThrows(DataTypeParserException.class,
() -> parse(JsonEntry.array(group(entry("no_type", "DECIMAL")))));
final JsonEntry data = JsonEntry.array(group(entry("no_type", "DECIMAL")));
final Exception e = assertThrows(DataTypeParserException.class, () -> parse(data));
assertThat(e.getMessage(), startsWith("E-VSCOMJAVA-40: Unspecified datatype"));
}

Expand Down Expand Up @@ -78,7 +78,7 @@ void date() {
void timestamp() {
verifySingle(DataType.createTimestamp(true), group( //
entry("type", "TIMESTAMP"), //
entry("withlocaltimezone", true)));
entry("withLocalTimeZone", true)));
}

@Test
Expand Down Expand Up @@ -166,7 +166,7 @@ void illegalPropertyValue_ThrowsException() {
entry("characterSet", "xxx")), //
"VARCHAR", "characterSet", "\"xxx\"");
verifyIllegalPropertyValue("VARCHAR", "size", "abc");
verifyIllegalPropertyValue("TIMESTAMP", "withlocaltimezone", 123);
verifyIllegalPropertyValue("TIMESTAMP", "withLocalTimeZone", 123);
verifyIllegalPropertyValue("HASHTYPE", "byteSize", true);
verifyIllegalPropertyValue("GEOMETRY", "scale", false);
verifyIllegalPropertyValue("INTERVAL", "precision", "p");
Expand All @@ -191,7 +191,7 @@ void multipleDatatypes() {
group(entry("type", "DOUBLE")), //
group(entry("type", "DATE")), //
group(entry("type", "TIMESTAMP"), //
entry("withlocaltimezone", true)), //
entry("withLocalTimeZone", true)), //
group(entry("type", "BOOLEAN")), //
group(entry("type", "GEOMETRY"), //
entry("scale", 42)), //
Expand Down Expand Up @@ -230,16 +230,18 @@ private void verifyIllegalPropertyValue(final String datatype, final String prop

private void verifyIllegalPropertyValue(final JsonParent builder, final String datatype, final String property,
final String value) {
final Exception e = assertThrows(DataTypeParserException.class, () -> parse(array(builder)));
assertThat(e.getMessage(), startsWith("E-VSCOMJAVA-40: Datatype '" + datatype + "', property '" + property
final JsonEntry data = array(builder);
final Exception e = assertThrows(DataTypeParserException.class, () -> parse(data));
assertThat(e.getMessage(), startsWith("E-VSCOMJAVA-39: Datatype '" + datatype + "', property '" + property
+ "': Illegal value " + value + "."));
}

// --------------------------------------------
// test utilities

private void verifyMissingRequiredProperty(final JsonParent builder, final String datatype, final String property) {
final Exception e = assertThrows(DataTypeParserException.class, () -> parse(array(builder)));
final JsonEntry data = array(builder);
final Exception e = assertThrows(DataTypeParserException.class, () -> parse(data));
assertThat(e.getMessage(),
startsWith("E-VSCOMJAVA-36: Datatype '" + datatype + "': Missing property '" + property));
}
Expand All @@ -249,7 +251,7 @@ private void verifySingle(final DataType expected, final JsonEntry builder) {
assertThat(actual, equalTo(expected));
ckunki marked this conversation as resolved.
Show resolved Hide resolved
}

private List<DataType> parse(final JsonParent builder) {
private List<DataType> parse(final JsonEntry builder) {
return DataTypeParser.create().parse(readArray(builder.render()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
import com.exasol.adapter.metadata.DataType;
import com.exasol.adapter.metadata.TableMetadata;
import com.exasol.adapter.request.*;
import com.exasol.adapter.request.parser.json.JsonEntry;
import com.exasol.adapter.request.parser.json.JsonKeyValue;
import com.exasol.adapter.request.parser.json.*;

class RequestParserTest {
private static final JsonKeyValue SCHEMA_METADATA_INFO = JsonEntry.entry("schemaMetadataInfo",
Expand Down Expand Up @@ -76,31 +75,9 @@ void unsupportedPropertyType() {
assertThat(exception.getMessage(), containsString("E-VSCOMJAVA-7"));
}

private static final JsonEntry PUSHDOWN_REQUEST = entry("pushdownRequest", group( //
entry("type", "select"), //
entry("from", group( //
entry("name", "FOO"), //
entry("type", "table") //
))));

private static final JsonEntry INVOLVED_TABLES = entry("involvedTables", array(group( //
entry("name", "FOO"), //
entry("columns", array(group( //
entry("name", "BAR"), //
entry("dataType", group( //
entry("precision", 18), //
entry("scale", 0), //
entry("type", "DECIMAL") //
))))))));

@Test
void pushDownRequest() {
final String rawRequest = JsonEntry.group( //
entry("type", "pushdown"), //
PUSHDOWN_REQUEST, //
INVOLVED_TABLES, //
SCHEMA_METADATA_INFO).render();
final AdapterRequest request = this.parser.parse(rawRequest);
void classicPushDownRequest() {
final AdapterRequest request = this.parser.parse(createPushDownRequest().render());
assertThat("Request class", request, instanceOf(PushDownRequest.class));
final List<TableMetadata> involvedTables = ((PushDownRequest) request).getInvolvedTablesMetadata();
final List<DataType> selectListDataTypes = ((PushDownRequest) request).getSelectListDataTypes();
Expand All @@ -112,16 +89,13 @@ void pushDownRequest() {

@Test
void pushDownRequestWithSelectListDataTypes() {
final String rawRequest = JsonEntry.group( //
entry("type", "pushdown"), //
PUSHDOWN_REQUEST, //
final String rawRequest = createPushDownRequest().withChild( //
entry("selectListDataTypes", array( //
group(entry("type", "DECIMAL"), //
entry("precision", 9), //
entry("scale", 10)), //
group(entry("type", "DOUBLE")))), //
INVOLVED_TABLES, //
SCHEMA_METADATA_INFO).render();
group(entry("type", "DOUBLE"))))) //
.render();
final AdapterRequest request = this.parser.parse(rawRequest);
final List<DataType> selectListDataTypes = ((PushDownRequest) request).getSelectListDataTypes();
assertAll(() -> assertThat(request.getType(), equalTo(AdapterRequestType.PUSHDOWN)),
Expand Down Expand Up @@ -158,4 +132,25 @@ void requestWithoutSchemaMetadata() {
final AdapterRequest request = this.parser.parse(rawRequest);
assertThat(request.getVirtualSchemaName(), equalTo("UNKNOWN"));
}

private JsonParent createPushDownRequest() {
return JsonEntry.group( //
entry("type", "pushdown"), //
entry("pushdownRequest", group( //
entry("type", "select"), //
entry("from", group( //
entry("name", "FOO"), //
entry("type", "table") //
)))), //
entry("involvedTables", array(group( //
entry("name", "FOO"), //
entry("columns", array(group( //
entry("name", "BAR"), //
entry("dataType", group( //
entry("precision", 18), //
entry("scale", 0), //
entry("type", "DECIMAL") //
)))))))), //
SCHEMA_METADATA_INFO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import com.exasol.adapter.request.parser.json.JsonKeyValue.Complex;
import com.exasol.adapter.request.parser.json.JsonKeyValue.Simple;
import com.exasol.adapter.request.parser.json.JsonParent.Array;
import com.exasol.adapter.request.parser.json.JsonParent.Group;
import com.exasol.adapter.request.parser.json.JsonParent.JsonArray;
import com.exasol.adapter.request.parser.json.JsonParent.JsonGroup;

public interface JsonEntry {

public static Group group(final JsonEntry... children) {
return new Group(children);
public static JsonGroup group(final JsonEntry... children) {
return new JsonGroup(children);
}

public static Array array(final JsonEntry... children) {
return new Array(children);
public static JsonArray array(final JsonEntry... children) {
return new JsonArray(children);
}

public static JsonKeyValue entry(final String key, final String value) {
Expand Down
Loading