Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
enhanced parser, added unit tests
some questions remain, see ticket
  • Loading branch information
ckunki committed Sep 8, 2022
1 parent f4829ba commit 0cd5691
Show file tree
Hide file tree
Showing 14 changed files with 514 additions and 230 deletions.
2 changes: 1 addition & 1 deletion error_code_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ error-tags:
VSCOMJAVA:
packages:
- com.exasol
highest-index: 35
highest-index: 40
23 changes: 17 additions & 6 deletions src/main/java/com/exasol/adapter/request/PushDownRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,36 @@
import com.exasol.ExaMetadata;
import com.exasol.adapter.AdapterCallExecutor;
import com.exasol.adapter.AdapterException;
import com.exasol.adapter.metadata.SchemaMetadataInfo;
import com.exasol.adapter.metadata.TableMetadata;
import com.exasol.adapter.metadata.*;
import com.exasol.adapter.sql.SqlStatement;

/**
* This class represents a request that tells a Virtual Schema Adapter to push a SQL statement down to the external data
* source
* source.
*/
public class PushDownRequest extends AbstractAdapterRequest {
private final SqlStatement select;
private final List<TableMetadata> involvedTablesMetadata;
private final List<DataType> selectListDataTypes;

/**
* Create a new request of type {@link PushDownRequest}
* Create a new request of type {@link PushDownRequest}.
*
* @param schemaMetadataInfo schema metadata
* @param select SQL statement to be pushed down to the external data source
* @param involvedTablesMetadata tables involved in the push-down request
* @param selectListDataType expected data types for the result set
*/
public PushDownRequest(final SchemaMetadataInfo schemaMetadataInfo, final SqlStatement select,
final List<TableMetadata> involvedTablesMetadata) {
final List<TableMetadata> involvedTablesMetadata, final List<DataType> selectListDataType) {
super(schemaMetadataInfo, AdapterRequestType.PUSHDOWN);
this.select = select;
this.involvedTablesMetadata = involvedTablesMetadata;
this.selectListDataTypes = selectListDataType;
}

/**
* Get the <code>SELECT</code> statement that should be pushed down to the external data source
* Get the <code>SELECT</code> statement that should be pushed down to the external data source.
*
* @return <code>SELECT</code> statement
*/
Expand All @@ -49,6 +51,15 @@ public List<TableMetadata> getInvolvedTablesMetadata() {
return this.involvedTablesMetadata;
}

/**
* Get the expected data types for the result set.
*
* @return expected data types for the result set
*/
public List<DataType> getSelectListDataTypes() {
return this.selectListDataTypes;
}

@Override
public String executeWith(final AdapterCallExecutor adapterCallExecutor, final ExaMetadata metadata)
throws AdapterException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Logger;

import com.exasol.errorreporting.ExaError;

Expand All @@ -17,7 +16,6 @@
* Abstract base class for parsers reading fragments of the Virtual Schema requests.
*/
class AbstractRequestParser {
private static final Logger LOGGER = Logger.getLogger(AbstractRequestParser.class.getName());

/**
* Create a JSON reader for raw request data.
Expand All @@ -36,7 +34,7 @@ protected JsonReader createJsonReader(final String rawRequest) {

/**
* Read the properties from the schema metadata.
*
*
* @param jsonSchemaMetadataInfo json schema metadata info
* @return parsed Properties.
*/
Expand Down Expand Up @@ -85,7 +83,6 @@ private void addProperty(final Map<String, String> properties, final Entry<Strin
+ "Supported types are strings, booleans, numbers and NULL.")
.parameter("type", type).toString());
}
LOGGER.finer(() -> "Parsed property: \"" + key + "\" = \"" + stringValue + "\"");
properties.put(key, stringValue);
}
}
45 changes: 28 additions & 17 deletions src/main/java/com/exasol/adapter/request/parser/DataTypeParser.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package com.exasol.adapter.request.parser;

import static com.exasol.adapter.request.parser.DataTypeProperty.*;

import java.util.List;
import java.util.stream.Collectors;

import com.exasol.adapter.metadata.DataType;
import com.exasol.adapter.request.parser.DataTypeProperty.*;
import com.exasol.adapter.metadata.DataType.ExaCharset;
import com.exasol.errorreporting.ExaError;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;

public class DataTypeParser {

private static StringProperty TYPE = new StringProperty("type");
private static IntProperty PRECISION = new IntProperty("precision");
private static IntProperty SCALE = new IntProperty("scale");
private static IntProperty SIZE = new IntProperty("size");
private static CharsetProperty CHARSET = new CharsetProperty("characterSet");
private static IntProperty FRACTION = new IntProperty("fraction"); // TODO: verify!
private static BooleanProperty WITH_LOCAL_TIMEZONE = new BooleanProperty("withlocaltimezone"); // TODO: verify!

public static DataTypeParser create() {
return new DataTypeParser();
}
Expand All @@ -33,30 +28,46 @@ public List<DataType> parse(final JsonArray jsonArray) {
}

private DataType datatype(final JsonObject entry) {
if (!entry.containsKey(TYPE.key)) {
throw new DataTypeParserException(ExaError.messageBuilder("E-VSCOMJAVA-40") //
.message("Unspecified datatype in {{json}}.", entry.toString()) //
.ticketMitigation().toString());
}
switch (TYPE.get(entry)) {
case "DECIMAL":
// should we accept at least if both precision *and* scale missing and use default value -1 for both?
return DataType.createDecimal(PRECISION.get(entry), SCALE.get(entry));
case "DOUBLE":
return DataType.createDouble();
case "VARCHAR":
return DataType.createVarChar(SIZE.get(entry), CHARSET.get(entry));
return DataType.createVarChar(SIZE.get(entry), CHARSET.get(entry, ExaCharset.UTF8));
case "CHAR":
return DataType.createChar(SIZE.get(entry), CHARSET.get(entry));
return DataType.createChar(SIZE.get(entry), CHARSET.get(entry, ExaCharset.UTF8));
case "DATE":
return DataType.createDate();
case "TIMESTAMP":
return DataType.createTimestamp(WITH_LOCAL_TIMEZONE.get(entry));
return DataType.createTimestamp(WITH_LOCAL_TIMEZONE.get(entry, false));
case "BOOLEAN":
return DataType.createBool();
case "GEOMETRY":
return DataType.createGeometry(SCALE.get(entry));
return DataType.createGeometry(SCALE.get(entry, 0));
case "HASHTYPE":
return DataType.createHashtype(BYTESIZE.get(entry, 16));
case "INTERVAL":
return DataType.createIntervalDaySecond(PRECISION.get(entry), FRACTION.get(entry));
case "UNSUPPORTED":
// fall through
return DataType.createIntervalDaySecond(PRECISION.get(entry, 2), FRACTION.get(entry, 3));
case "UNSUPPORTED": // fall through
default:
return null;
throw new DataTypeParserException(ExaError.messageBuilder("E-VSCOMJAVA-37") //
.message("Unsupported datatype {{datatype}}.", TYPE.get(entry)) //
.ticketMitigation().toString());
}
}

public static class DataTypeParserException extends RuntimeException {
private static final long serialVersionUID = 1L;

public DataTypeParserException(final String message) {
super(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
package com.exasol.adapter.request.parser;

import com.exasol.adapter.metadata.DataType.ExaCharset;
import com.exasol.adapter.request.parser.DataTypeParser.DataTypeParserException;
import com.exasol.errorreporting.ExaError;

import jakarta.json.JsonObject;

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");
// 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!

protected final String key;
private final DataTypeProperty.JsonGetter<T> getter;

Expand All @@ -14,7 +30,26 @@ class DataTypeProperty<T> {
}

public T get(final JsonObject json) {
return this.getter.apply(json, this.key);
return get(json, null);
}

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) {
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());
}
}
if (defaultValue != null) {
return defaultValue;
}
throw new DataTypeParserException(ExaError.messageBuilder("E-VSCOMJAVA-36") //
.message("Datatype {{datatype}}: Missing property {{property}}.", TYPE.get(json), this.key) //
.ticketMitigation().toString());
}

@FunctionalInterface
Expand Down Expand Up @@ -46,7 +81,10 @@ private static ExaCharset getCharset(final JsonObject json, final String key) {
case "UTF8":
return ExaCharset.UTF8;
default:
throw new RuntimeException();
throw new DataTypeParserException(ExaError.messageBuilder("E-VSCOMJAVA-38") //
.message("Datatype {{datatype}}: Unsupported charset {{charset}}.", TYPE.get(json),
json.getString(key)) //
.ticketMitigation().toString());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ private AbstractAdapterRequest parsePushdownRequest(final JsonObject root, final
final SqlStatement statement = parsePushdownStatement(root);
final List<TableMetadata> involvedTables = parseInvolvedTables(root);
final List<DataType> dataTypes = parseDataTypes(root);
// TODO: add expected return types to PushDownRequest
return new PushDownRequest(metadataInfo, statement, involvedTables);
return new PushDownRequest(metadataInfo, statement, involvedTables, dataTypes);
}

private List<DataType> parseDataTypes(final JsonObject root) {
if (!root.containsKey(SELECT_LIST_DATATYPES_KEY)) {
return Collections.emptyList();
}
return DataTypeParser.create().parse(root.getJsonArray(SELECT_LIST_DATATYPES_KEY));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void testExecutePushDownRequest() throws AdapterException {
final PushDownResponse expectedResponse = PushDownResponse.builder().pushDownSql("SELECT * FROM FOOBAR")
.build();
when(this.mockAdapter.pushdown(any(), any())).thenReturn(expectedResponse);
final String response = this.adapterCallExecutor.executeAdapterCall(new PushDownRequest(null, null, null),
final String response = this.adapterCallExecutor.executeAdapterCall(new PushDownRequest(null, null, null, null),
null);
assertEquals("{\"type\":\"pushdown\",\"sql\":\"SELECT * FROM FOOBAR\"}", response);
verify(this.mockAdapter).pushdown(any(), any(PushDownRequest.class));
Expand Down
Loading

0 comments on commit 0cd5691

Please sign in to comment.