diff --git a/doc/changes/changes_18.0.2.md b/doc/changes/changes_18.0.2.md
index edbd4893..e7ed6481 100644
--- a/doc/changes/changes_18.0.2.md
+++ b/doc/changes/changes_18.0.2.md
@@ -25,6 +25,8 @@ Code name: Improve code quality
* #308: Fixed `SqlLimit` so `limit` and `offset` stay non-negative for the full object lifetime by making the node immutable and aligning the validation message with the accepted zero values.
* #307: Fixed `PushdownSqlRenderer` so `HASHTYPE` data types include `bytesize` in rendered pushdown SQL JSON.
* #312: Fixed logging utility resource and handler lifecycle issues by closing version metadata streams, falling back when the thread context class loader is missing, defaulting absent version properties to `UNKNOWN`, closing replaced root log handlers, and resetting closed remote socket handlers before reuse.
+* #318: Fixed several small naming, Javadoc, message, and parsing robustness issues, including locale-independent SQL parser uppercasing and trimmed telemetry property parsing.
+* Fixed `ColumnMetadata.toString()` so `defaultValue` quotes stay balanced even when no comment is set.
* #314: Fixed `SqlFunctionAggregateListagg.Behavior` so LISTAGG overflow behavior is null-safe, value-based, and constructed immutably by the parser.
## Dependency Updates
diff --git a/src/main/java/com/exasol/adapter/AdapterContext.java b/src/main/java/com/exasol/adapter/AdapterContext.java
index b1059868..4218a54c 100644
--- a/src/main/java/com/exasol/adapter/AdapterContext.java
+++ b/src/main/java/com/exasol/adapter/AdapterContext.java
@@ -13,7 +13,7 @@ public class AdapterContext {
/**
* Create a new AdapterContext.
*
- * @param telemetryClient telemetry client to be used by the adapter for reportin feature usage.
+ * @param telemetryClient telemetry client to be used by the adapter for reporting feature usage.
*/
public AdapterContext(final TelemetryClient telemetryClient) {
this.telemetryClient = telemetryClient;
@@ -26,7 +26,7 @@ public AdapterContext(final TelemetryClient telemetryClient) {
*
* - Class {@link AdapterCallExecutor} already reports the following events: {@code createVirtualSchema}, {@code dropVirtualSchema},
* {@code refreshVirtualSchema}, {@code setProperties}. The adapter must not report these events again
- * - Adapters must keep not report high frequency features, e.g. for every pushdown.
+ * - Adapters must not report high frequency features, e.g. for every pushdown.
*
*
* @return telemetry client
diff --git a/src/main/java/com/exasol/adapter/metadata/ColumnMetadata.java b/src/main/java/com/exasol/adapter/metadata/ColumnMetadata.java
index 43dec6a0..a1dba2f5 100644
--- a/src/main/java/com/exasol/adapter/metadata/ColumnMetadata.java
+++ b/src/main/java/com/exasol/adapter/metadata/ColumnMetadata.java
@@ -136,9 +136,10 @@ public String toString() {
if (this.hasDefault()) {
builder.append(", defaultValue=\"");
builder.append(this.defaultValue);
+ builder.append("\"");
}
if (this.hasComment()) {
- builder.append("\", comment=\"");
+ builder.append(", comment=\"");
builder.append(this.comment);
builder.append("\"");
}
diff --git a/src/main/java/com/exasol/adapter/request/AdapterTelemetryConfiguration.java b/src/main/java/com/exasol/adapter/request/AdapterTelemetryConfiguration.java
index c6d9b626..73a9eec1 100644
--- a/src/main/java/com/exasol/adapter/request/AdapterTelemetryConfiguration.java
+++ b/src/main/java/com/exasol/adapter/request/AdapterTelemetryConfiguration.java
@@ -26,7 +26,8 @@ private AdapterTelemetryConfiguration(final boolean telemetryDisabled) {
*/
public static AdapterTelemetryConfiguration parseFromProperties(final Map properties) {
final String telemetryPropertyValue = properties.get(TELEMETRY_PROPERTY);
- final boolean telemetryDisabled = telemetryPropertyValue != null && !Boolean.parseBoolean(telemetryPropertyValue);
+ final boolean telemetryDisabled = telemetryPropertyValue != null
+ && !Boolean.parseBoolean(telemetryPropertyValue.trim());
return new AdapterTelemetryConfiguration(telemetryDisabled);
}
diff --git a/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java b/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java
index d9d00b9d..8fb2f6bd 100644
--- a/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java
+++ b/src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java
@@ -144,7 +144,7 @@ private SqlGroupBy parseGroupBy(
private boolean hasAggregateFunction(final List nodesList) {
// Stack is less efficient than ArrayDeque, but ArrayDeque doesn't support null elements.
@java.lang.SuppressWarnings("java:S1149")
- Stack expressions = new Stack<>();
+ final Stack expressions = new Stack<>();
expressions.addAll(nodesList);
while (!expressions.isEmpty()) {
final SqlNode expression = expressions.pop();
@@ -199,82 +199,82 @@ public SqlNode parseExpression(final JsonObject expression) {
final String typeName = expression.getString(TYPE, "");
final SqlNodeType type = fromTypeName(typeName);
switch (type) {
- case SELECT:
- return parseSelect(expression);
- case TABLE:
- return parseTable(expression);
- case JOIN:
- return parseJoin(expression);
- case COLUMN:
- return parseColumn(expression);
- case LITERAL_NULL:
- return parseLiteralNull();
- case LITERAL_BOOL:
- return parseLiteralBool(expression);
- case LITERAL_DATE:
- return parseLiteralDate(expression);
- case LITERAL_TIMESTAMP:
- return parseLiteralTimestamp(expression);
- case LITERAL_TIMESTAMPUTC:
- return parseLiteralTimestamputc(expression);
- case LITERAL_DOUBLE:
- return parseLiteralDouble(expression);
- case LITERAL_EXACTNUMERIC:
- return parseLiteralExactNumeric(expression);
- case LITERAL_STRING:
- return parseLiteralString(expression);
- case LITERAL_INTERVAL:
- return parseLiteralInterval(expression);
- case PREDICATE_AND:
- return parsePredicateAnd(expression);
- case PREDICATE_OR:
- return parsePredicateOr(expression);
- case PREDICATE_NOT:
- return parsePredicateNot(expression);
- case PREDICATE_EQUAL:
- return parsePredicateEqual(expression);
- case PREDICATE_NOTEQUAL:
- return parsePredicateNotEqual(expression);
- case PREDICATE_LESS:
- return parsePredicateLess(expression);
- case PREDICATE_LESSEQUAL:
- return parsePredicateLessEqual(expression);
- case PREDICATE_LIKE:
- return parsePredicateLike(expression);
- case PREDICATE_LIKE_REGEXP:
- return parsePredicateLikeRegexp(expression);
- case PREDICATE_BETWEEN:
- return parsePredicateBetween(expression);
- case PREDICATE_IN_CONSTLIST:
- return parsePredicateInConstlist(expression);
- case PREDICATE_IS_JSON:
- return parsePredicateIsJson(expression);
- case PREDICATE_IS_NOT_JSON:
- return parsePredicateIsNotJson(expression);
- case PREDICATE_IS_NULL:
- return parsePredicateIsNull(expression);
- case PREDICATE_IS_NOT_NULL:
- return parsePredicateIsNotNull(expression);
- case FUNCTION_SCALAR:
- return parseFunctionScalar(expression);
- case FUNCTION_SCALAR_EXTRACT:
- return parseFunctionScalarExtract(expression);
- case FUNCTION_SCALAR_CASE:
- return parseFunctionScalarCase(expression);
- case FUNCTION_SCALAR_CAST:
- return parseFunctionScalarCast(expression);
- case FUNCTION_SCALAR_JSON_VALUE:
- return parseFunctionScalarJsonValue(expression);
- case FUNCTION_AGGREGATE:
- return parseFunctionAggregate(expression);
- case FUNCTION_AGGREGATE_GROUP_CONCAT:
- return parseFunctionAggregateGroupConcat(expression);
- case FUNCTION_AGGREGATE_LISTAGG:
- return parseFunctionAggregateListagg(expression);
- default:
- throw new IllegalArgumentException(ExaError.messageBuilder("E-VSCOMJAVA-8") //
- .message("Unknown node type: {{typeName}}") //
- .parameter("typeName", typeName).toString());
+ case SELECT:
+ return parseSelect(expression);
+ case TABLE:
+ return parseTable(expression);
+ case JOIN:
+ return parseJoin(expression);
+ case COLUMN:
+ return parseColumn(expression);
+ case LITERAL_NULL:
+ return parseLiteralNull();
+ case LITERAL_BOOL:
+ return parseLiteralBool(expression);
+ case LITERAL_DATE:
+ return parseLiteralDate(expression);
+ case LITERAL_TIMESTAMP:
+ return parseLiteralTimestamp(expression);
+ case LITERAL_TIMESTAMPUTC:
+ return parseLiteralTimestamputc(expression);
+ case LITERAL_DOUBLE:
+ return parseLiteralDouble(expression);
+ case LITERAL_EXACTNUMERIC:
+ return parseLiteralExactNumeric(expression);
+ case LITERAL_STRING:
+ return parseLiteralString(expression);
+ case LITERAL_INTERVAL:
+ return parseLiteralInterval(expression);
+ case PREDICATE_AND:
+ return parsePredicateAnd(expression);
+ case PREDICATE_OR:
+ return parsePredicateOr(expression);
+ case PREDICATE_NOT:
+ return parsePredicateNot(expression);
+ case PREDICATE_EQUAL:
+ return parsePredicateEqual(expression);
+ case PREDICATE_NOTEQUAL:
+ return parsePredicateNotEqual(expression);
+ case PREDICATE_LESS:
+ return parsePredicateLess(expression);
+ case PREDICATE_LESSEQUAL:
+ return parsePredicateLessEqual(expression);
+ case PREDICATE_LIKE:
+ return parsePredicateLike(expression);
+ case PREDICATE_LIKE_REGEXP:
+ return parsePredicateLikeRegexp(expression);
+ case PREDICATE_BETWEEN:
+ return parsePredicateBetween(expression);
+ case PREDICATE_IN_CONSTLIST:
+ return parsePredicateInConstlist(expression);
+ case PREDICATE_IS_JSON:
+ return parsePredicateIsJson(expression);
+ case PREDICATE_IS_NOT_JSON:
+ return parsePredicateIsNotJson(expression);
+ case PREDICATE_IS_NULL:
+ return parsePredicateIsNull(expression);
+ case PREDICATE_IS_NOT_NULL:
+ return parsePredicateIsNotNull(expression);
+ case FUNCTION_SCALAR:
+ return parseFunctionScalar(expression);
+ case FUNCTION_SCALAR_EXTRACT:
+ return parseFunctionScalarExtract(expression);
+ case FUNCTION_SCALAR_CASE:
+ return parseFunctionScalarCase(expression);
+ case FUNCTION_SCALAR_CAST:
+ return parseFunctionScalarCast(expression);
+ case FUNCTION_SCALAR_JSON_VALUE:
+ return parseFunctionScalarJsonValue(expression);
+ case FUNCTION_AGGREGATE:
+ return parseFunctionAggregate(expression);
+ case FUNCTION_AGGREGATE_GROUP_CONCAT:
+ return parseFunctionAggregateGroupConcat(expression);
+ case FUNCTION_AGGREGATE_LISTAGG:
+ return parseFunctionAggregateListagg(expression);
+ default:
+ throw new IllegalArgumentException(ExaError.messageBuilder("E-VSCOMJAVA-8") //
+ .message("Unknown node type: {{typeName}}") //
+ .parameter("typeName", typeName).toString());
}
}
@@ -463,49 +463,49 @@ private List collectInvolvedTables(final SqlNode from) {
while (!nodes.isEmpty()) {
final SqlNode node = nodes.pop();
switch (node.getType()) {
- case TABLE:
- involvedTables.add((SqlTable) node);
- break;
- case JOIN:
- nodes.add(((SqlJoin) node).getRight());
- nodes.add(((SqlJoin) node).getLeft());
- break;
- default:
- throw new IllegalStateException(ExaError.messageBuilder("E-VSCOMJAVA-10")
- .message("Encountered illegal SqlNodeType during collection involved tables: {{nodeType}}")
- .parameter("nodeType", node.getType()).toString());
+ case TABLE:
+ involvedTables.add((SqlTable) node);
+ break;
+ case JOIN:
+ nodes.add(((SqlJoin) node).getRight());
+ nodes.add(((SqlJoin) node).getLeft());
+ break;
+ default:
+ throw new IllegalStateException(ExaError.messageBuilder("E-VSCOMJAVA-10")
+ .message("Encountered illegal SqlNodeType during collection involved tables: {{nodeType}}")
+ .parameter("nodeType", node.getType()).toString());
}
}
return involvedTables;
}
private DataType getDataType(final JsonObject dataType) {
- final String typeName = dataType.getString(TYPE).toUpperCase();
+ final String typeName = dataType.getString(TYPE).toUpperCase(Locale.ROOT);
switch (typeName) {
- case "DECIMAL":
- return DataType.createDecimal(dataType.getInt(PRECISION), dataType.getInt(SCALE));
- case "DOUBLE":
- return DataType.createDouble();
- case "VARCHAR":
- return getVarchar(dataType);
- case "CHAR":
- return getChar(dataType);
- case "BOOLEAN":
- return DataType.createBool();
- case "DATE":
- return DataType.createDate();
- case "TIMESTAMP":
- return getTimestamp(dataType);
- case "INTERVAL":
- return getInterval(dataType);
- case "GEOMETRY":
- return getGeometry(dataType);
- case "HASHTYPE":
- return getHashtype(dataType);
- default:
- throw new IllegalArgumentException(ExaError.messageBuilder("E-VSCOMJAVA-11")
- .message("Unsupported data type encountered: {{typeName}}.") //
- .parameter("typeName", typeName).toString());
+ case "DECIMAL":
+ return DataType.createDecimal(dataType.getInt(PRECISION), dataType.getInt(SCALE));
+ case "DOUBLE":
+ return DataType.createDouble();
+ case "VARCHAR":
+ return getVarchar(dataType);
+ case "CHAR":
+ return getChar(dataType);
+ case "BOOLEAN":
+ return DataType.createBool();
+ case "DATE":
+ return DataType.createDate();
+ case "TIMESTAMP":
+ return getTimestamp(dataType);
+ case "INTERVAL":
+ return getInterval(dataType);
+ case "GEOMETRY":
+ return getGeometry(dataType);
+ case "HASHTYPE":
+ return getHashtype(dataType);
+ default:
+ throw new IllegalArgumentException(ExaError.messageBuilder("E-VSCOMJAVA-11")
+ .message("Unsupported data type encountered: {{typeName}}.") //
+ .parameter("typeName", typeName).toString());
}
}
@@ -545,7 +545,7 @@ private SqlNode parsePredicateBetween(final JsonObject exp) {
private SqlNode parsePredicateIsJson(final JsonObject jsonExpression) {
final SqlNode expression = parseExpression(jsonExpression.getJsonObject(EXPRESSION));
final TypeConstraints typeConstraint = TypeConstraints
- .valueOf(jsonExpression.getString(TYPE_CONSTRAINT).toUpperCase());
+ .valueOf(jsonExpression.getString(TYPE_CONSTRAINT).toUpperCase(Locale.ROOT));
final KeyUniquenessConstraint keyUniquenessConstraint = KeyUniquenessConstraint
.of(jsonExpression.getString(KEY_UNIQUENESS_CONSTRAINT));
return new SqlPredicateIsJson(expression, typeConstraint, keyUniquenessConstraint);
@@ -554,7 +554,7 @@ private SqlNode parsePredicateIsJson(final JsonObject jsonExpression) {
private SqlNode parsePredicateIsNotJson(final JsonObject jsonExpression) {
final SqlNode expression = parseExpression(jsonExpression.getJsonObject(EXPRESSION));
final TypeConstraints typeConstraint = TypeConstraints
- .valueOf(jsonExpression.getString(TYPE_CONSTRAINT).toUpperCase());
+ .valueOf(jsonExpression.getString(TYPE_CONSTRAINT).toUpperCase(Locale.ROOT));
final KeyUniquenessConstraint keyUniquenessConstraint = KeyUniquenessConstraint
.of(jsonExpression.getString(KEY_UNIQUENESS_CONSTRAINT));
return new SqlPredicateIsNotJson(expression, typeConstraint, keyUniquenessConstraint);
@@ -681,7 +681,8 @@ private SqlNode parseFunctionAggregateListagg(final JsonObject expression) {
private Behavior parseOverflowBehavior(final JsonObject expression) {
final JsonObject overflowBehaviorJson = expression.getJsonObject(OVERFLOW_BEHAVIOUR);
- final BehaviorType behaviorType = BehaviorType.valueOf(overflowBehaviorJson.getString(TYPE).toUpperCase());
+ final BehaviorType behaviorType = BehaviorType
+ .valueOf(overflowBehaviorJson.getString(TYPE).toUpperCase(Locale.ROOT));
if (behaviorType == BehaviorType.TRUNCATE) {
final TruncationType truncationType = TruncationType
.parseTruncationType(overflowBehaviorJson.getString(TRUNCATION_TYPE));
@@ -699,28 +700,28 @@ private Behavior parseOverflowBehavior(final JsonObject expression) {
* Mapping from join type name (as in json api) to enum
*/
private static JoinType fromJoinTypeName(final String typeName) {
- return Enum.valueOf(JoinType.class, typeName.toUpperCase());
+ return Enum.valueOf(JoinType.class, typeName.toUpperCase(Locale.ROOT));
}
/**
* Mapping from scalar function name (as in json api) to enum
*/
private static ScalarFunction fromScalarFunctionName(final String functionName) {
- return Enum.valueOf(ScalarFunction.class, functionName.toUpperCase());
+ return Enum.valueOf(ScalarFunction.class, functionName.toUpperCase(Locale.ROOT));
}
/**
* Mapping from aggregate function name (as in json api) to enum
*/
private static AggregateFunction fromAggregationFunctionName(final String functionName) {
- return Enum.valueOf(AggregateFunction.class, functionName.toUpperCase());
+ return Enum.valueOf(AggregateFunction.class, functionName.toUpperCase(Locale.ROOT));
}
/**
* Mapping from type name (as in json api) to enum
*/
private static SqlNodeType fromTypeName(final String typeName) {
- return Enum.valueOf(SqlNodeType.class, typeName.toUpperCase());
+ return Enum.valueOf(SqlNodeType.class, typeName.toUpperCase(Locale.ROOT));
}
private TableMetadata findInvolvedTableMetadata(final String tableName) {
diff --git a/src/main/java/com/exasol/adapter/request/parser/RequestParser.java b/src/main/java/com/exasol/adapter/request/parser/RequestParser.java
index 29ea2ad8..85623561 100644
--- a/src/main/java/com/exasol/adapter/request/parser/RequestParser.java
+++ b/src/main/java/com/exasol/adapter/request/parser/RequestParser.java
@@ -84,7 +84,7 @@ private AbstractAdapterRequest parseRefreshRequest(final JsonObject root, final
}
private AbstractAdapterRequest parsePushdownRequest(final JsonObject root, final SchemaMetadataInfo metadataInfo) {
- final JsonObject pushdownJson = root.getJsonObject(PUSHDOW_REQUEST_KEY);
+ final JsonObject pushdownJson = root.getJsonObject(PUSHDOWN_REQUEST_KEY);
final List involvedTables = parseInvolvedTables(root);
final SqlStatement statement = parsePushdownStatement(pushdownJson, involvedTables);
final List dataTypes = parseDataTypes(pushdownJson);
diff --git a/src/main/java/com/exasol/adapter/request/parser/RequestParserConstants.java b/src/main/java/com/exasol/adapter/request/parser/RequestParserConstants.java
index 845290a6..a55380f8 100644
--- a/src/main/java/com/exasol/adapter/request/parser/RequestParserConstants.java
+++ b/src/main/java/com/exasol/adapter/request/parser/RequestParserConstants.java
@@ -11,7 +11,7 @@ final class RequestParserConstants {
static final String ADAPTER_NOTES_KEY = "adapterNotes";
static final String PROPERTIES_KEY = "properties";
static final String SCHEMA_NAME_KEY = "name";
- static final String PUSHDOW_REQUEST_KEY = "pushdownRequest";
+ static final String PUSHDOWN_REQUEST_KEY = "pushdownRequest";
static final String SCHEMA_METADATA_INFO_KEY = "schemaMetadataInfo";
static final String INVOLVED_TABLES_KEY = "involvedTables";
static final String SELECT_LIST_DATATYPES_KEY = "selectListDataTypes";
@@ -24,4 +24,4 @@ final class RequestParserConstants {
private RequestParserConstants() {
// prevent instantiation
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java b/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java
index bd296bc0..ea44c6f4 100644
--- a/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java
+++ b/src/main/java/com/exasol/adapter/request/renderer/PushdownSqlRenderer.java
@@ -34,7 +34,9 @@ public JsonValue render(final SqlNode node) {
return node.accept(new ConvertVisitor());
} catch (final AdapterException exception) {
throw new IllegalStateException(ExaError.messageBuilder("F-VSCOMJAVA-34")
- .message("n unexpected error occurred during request serialization.").ticketMitigation().toString(),
+ .message("An unexpected error occurred during request serialization: {{error message}}.")
+ .parameter("error message", exception.getMessage())
+ .ticketMitigation().toString(),
exception);
}
}
@@ -159,46 +161,46 @@ private JsonObject render(final DataType dataType) {
final DataType.ExaDataType type = dataType.getExaDataType();
builder.add(TYPE, type.name().toUpperCase());
switch (type) {
- case DECIMAL:
- builder.add(PRECISION, dataType.getPrecision());
- builder.add(SCALE, dataType.getScale());
- break;
- case VARCHAR:
- case CHAR:
- builder.add(CHARACTER_SET, dataType.getCharset().name().toLowerCase());
- builder.add(SIZE, dataType.getSize());
- break;
- case TIMESTAMP:
- builder.add(WITH_LOCAL_TIME_ZONE, dataType.isWithLocalTimezone());
- break;
- case INTERVAL:
- builder.add(PRECISION, dataType.getPrecision());
- builder.add(FROM_TO, render(dataType.getIntervalType()));
- if (dataType.getIntervalType().equals(DataType.IntervalType.DAY_TO_SECOND)) {
- builder.add(FRACTION, dataType.getIntervalFraction());
- }
- break;
- case GEOMETRY:
- builder.add(SRID, dataType.getGeometrySrid());
- break;
- case HASHTYPE:
- builder.add(BYTE_SIZE, dataType.getByteSize());
- break;
- default:
- break;
+ case DECIMAL:
+ builder.add(PRECISION, dataType.getPrecision());
+ builder.add(SCALE, dataType.getScale());
+ break;
+ case VARCHAR:
+ case CHAR:
+ builder.add(CHARACTER_SET, dataType.getCharset().name().toLowerCase());
+ builder.add(SIZE, dataType.getSize());
+ break;
+ case TIMESTAMP:
+ builder.add(WITH_LOCAL_TIME_ZONE, dataType.isWithLocalTimezone());
+ break;
+ case INTERVAL:
+ builder.add(PRECISION, dataType.getPrecision());
+ builder.add(FROM_TO, render(dataType.getIntervalType()));
+ if (dataType.getIntervalType().equals(DataType.IntervalType.DAY_TO_SECOND)) {
+ builder.add(FRACTION, dataType.getIntervalFraction());
+ }
+ break;
+ case GEOMETRY:
+ builder.add(SRID, dataType.getGeometrySrid());
+ break;
+ case HASHTYPE:
+ builder.add(BYTE_SIZE, dataType.getByteSize());
+ break;
+ default:
+ break;
}
return builder.build();
}
private String render(final DataType.IntervalType intervalType) {
switch (intervalType) {
- case DAY_TO_SECOND:
- return "DAY TO SECONDS";
- case YEAR_TO_MONTH:
- return "YEAR TO MONTH";
- default:
- throw new IllegalStateException(ExaError.messageBuilder("F-VSCOMJAVA-35")
- .message("Unimplemented interval type {{type}}.", intervalType).ticketMitigation().toString());
+ case DAY_TO_SECOND:
+ return "DAY TO SECONDS";
+ case YEAR_TO_MONTH:
+ return "YEAR TO MONTH";
+ default:
+ throw new IllegalStateException(ExaError.messageBuilder("F-VSCOMJAVA-35")
+ .message("Unimplemented interval type {{type}}.", intervalType).ticketMitigation().toString());
}
}
diff --git a/src/test/java/com/exasol/adapter/metadata/ColumnMetadataTest.java b/src/test/java/com/exasol/adapter/metadata/ColumnMetadataTest.java
index ffced623..d6044476 100644
--- a/src/test/java/com/exasol/adapter/metadata/ColumnMetadataTest.java
+++ b/src/test/java/com/exasol/adapter/metadata/ColumnMetadataTest.java
@@ -132,6 +132,12 @@ void assertToStringWithOptionalFields() {
}
+ @Test
+ void assertToStringWithDefaultValueWithoutComment() {
+ assertThat(this.builder.defaultValue("default").build().toString(),
+ equalTo("ColumnMetadata{name=\"COLUMN_NAME\", adapterNotes=\"\", type=DOUBLE, isNullable=true, isIdentity=false, defaultValue=\"default\"}"));
+ }
+
@Test
void testEqualsAndHashContract() {
EqualsVerifier.forClass(ColumnMetadata.class).verify();
diff --git a/src/test/java/com/exasol/adapter/request/AdapterTelemetryConfigurationTest.java b/src/test/java/com/exasol/adapter/request/AdapterTelemetryConfigurationTest.java
index f27ef3eb..c4b61fd8 100644
--- a/src/test/java/com/exasol/adapter/request/AdapterTelemetryConfigurationTest.java
+++ b/src/test/java/com/exasol/adapter/request/AdapterTelemetryConfigurationTest.java
@@ -5,10 +5,11 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.provider.*;
class AdapterTelemetryConfigurationTest {
private final Map properties = new HashMap<>();
@@ -38,6 +39,21 @@ void testTelemetryIsDisabledWhenPropertyHasNonBooleanValue() {
assertThat(createConfiguration().isTelemetryDisabled(), equalTo(true));
}
+ @ParameterizedTest
+ @MethodSource("trimmedTelemetryValues")
+ void testTelemetryPropertyValueIsTrimmedBeforeParsing(final String value, final boolean expectedDisabled) {
+ this.properties.put(AdapterTelemetryConfiguration.TELEMETRY_PROPERTY, value);
+ assertThat(createConfiguration().isTelemetryDisabled(), equalTo(expectedDisabled));
+ }
+
+ private static Stream trimmedTelemetryValues() {
+ return Stream.of(
+ Arguments.of(" true ", false),
+ Arguments.of(" false ", true),
+ Arguments.of("\ttrue\t", false),
+ Arguments.of("\nfalse\n", true));
+ }
+
private AdapterTelemetryConfiguration createConfiguration() {
return AdapterTelemetryConfiguration.parseFromProperties(properties);
}
diff --git a/src/test/java/com/exasol/adapter/request/parser/PushDownSqlParserTest.java b/src/test/java/com/exasol/adapter/request/parser/PushDownSqlParserTest.java
index 25df0e89..f0884211 100644
--- a/src/test/java/com/exasol/adapter/request/parser/PushDownSqlParserTest.java
+++ b/src/test/java/com/exasol/adapter/request/parser/PushDownSqlParserTest.java
@@ -506,6 +506,27 @@ void testParsePredicateIsNull() {
() -> assertThat(expression.getValue(), equalTo(0.0)));
}
+ @Test
+ void testParsePredicateIsNullUsesLocaleIndependentUppercaseConversion() {
+ final Locale defaultLocale = Locale.getDefault();
+ try {
+ Locale.setDefault(Locale.forLanguageTag("tr"));
+ final String sqlAsJson = "{" //
+ + " \"type\" : \"predicate_is_null\", " //
+ + " \"expression\" : { " //
+ + " \"type\" : \"literal_double\", " //
+ + " \"value\" : \"0.0\" " //
+ + " } " //
+ + "}";
+ final JsonObject jsonObject = createJsonObjectFromString(sqlAsJson);
+ final SqlPredicateIsNull sqlPredicateIsNull = (SqlPredicateIsNull) this.defaultParser
+ .parseExpression(jsonObject);
+ assertThat(sqlPredicateIsNull.getType(), equalTo(PREDICATE_IS_NULL));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
@Test
void testParsePredicateIsNotNull() {
final String sqlAsJson = "{" //
diff --git a/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java b/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java
index bb1ed61c..b64d0f91 100644
--- a/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java
+++ b/src/test/java/com/exasol/adapter/request/renderer/PushdownSqlRendererTest.java
@@ -1,8 +1,15 @@
package com.exasol.adapter.request.renderer;
-import static com.exasol.adapter.metadata.DataType.createDecimal;
-import static com.exasol.adapter.metadata.DataType.createVarChar;
+import static com.exasol.adapter.metadata.DataType.*;
import static com.exasol.adapter.metadata.DataType.ExaCharset.UTF8;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.StringReader;
@@ -17,10 +24,11 @@
import org.junit.jupiter.params.provider.ValueSource;
import org.skyscreamer.jsonassert.JSONAssert;
+import com.exasol.adapter.AdapterException;
import com.exasol.adapter.metadata.ColumnMetadata;
import com.exasol.adapter.metadata.TableMetadata;
import com.exasol.adapter.request.parser.PushdownSqlParser;
-import com.exasol.adapter.sql.SqlNode;
+import com.exasol.adapter.sql.*;
import jakarta.json.*;
@@ -901,4 +909,86 @@ void testParseGroupByWithSingleGroupColumnName() throws JSONException {
+ "}";
assertParseAndRenderGeneratesSameJson(sqlAsJson);
}
+
+ @Test
+ void testRenderWrapsAdapterException() throws AdapterException {
+ final SqlNode failingNode = mock(SqlNode.class);
+ when(failingNode.accept(any())).thenThrow(new AdapterException("Adapter error"));
+ final PushdownSqlRenderer renderer = new PushdownSqlRenderer();
+ final IllegalStateException exception = assertThrows(IllegalStateException.class, () -> renderer.render(failingNode));
+ assertAll(() -> assertThat(exception.getMessage(), equalTo(
+ "F-VSCOMJAVA-34: An unexpected error occurred during request serialization: 'Adapter error'. "
+ + "This is an internal error that should not happen. Please report it by opening a GitHub issue.")),
+ () -> assertThat(exception.getCause(), instanceOf(AdapterException.class)));
+ }
+
+ @Test
+ void testRenderFunctionAggregateWithDistinct() {
+ final SqlFunctionAggregate node = new SqlFunctionAggregate(AggregateFunction.SUM,
+ List.of(new SqlLiteralString("1")), true);
+ final JsonObject rendered = renderToJsonObject(node);
+
+ assertAll(() -> assertThat(rendered.getString("type"), equalTo("function_aggregate")),
+ () -> assertThat(rendered.getString("name"), equalTo("SUM")),
+ () -> assertThat(rendered.getBoolean("distinct"), equalTo(true)));
+ }
+
+ @Test
+ void testRenderFunctionAggregateGroupConcatWithoutDistinct() {
+ final SqlFunctionAggregateGroupConcat node = SqlFunctionAggregateGroupConcat.builder(new SqlLiteralString("x"))
+ .separator(new SqlLiteralString(", ")).build();
+ final JsonObject rendered = renderToJsonObject(node);
+
+ assertAll(() -> assertThat(rendered.getString("type"), equalTo("function_aggregate_group_concat")),
+ () -> assertThat(rendered.getString("name"), equalTo("GROUP_CONCAT")),
+ () -> assertThat(rendered.containsKey("distinct"), equalTo(false)),
+ () -> assertThat(rendered.getJsonObject("separator").getString("type"), equalTo("literal_string")),
+ () -> assertThat(rendered.getJsonObject("separator").getString("value"), equalTo(", ")));
+ }
+
+ @Test
+ void testRenderDataTypeDecimal() {
+ final JsonObject rendered = renderToJsonObject(new SqlFunctionScalarCast(createDecimal(9, 2),
+ new SqlLiteralString("dummy")));
+
+ assertAll(() -> assertThat(rendered.getJsonObject("dataType").getString("type"), equalTo("DECIMAL")),
+ () -> assertThat(rendered.getJsonObject("dataType").getJsonNumber("precision").intValue(),
+ equalTo(9)),
+ () -> assertThat(rendered.getJsonObject("dataType").getJsonNumber("scale").intValue(), equalTo(2)));
+ }
+
+ @Test
+ void testRenderDataTypeTimestamp() {
+ final JsonObject rendered = renderToJsonObject(new SqlFunctionScalarCast(createTimestamp(true, 5),
+ new SqlLiteralString("dummy")));
+
+ assertAll(() -> assertThat(rendered.getJsonObject("dataType").getString("type"), equalTo("TIMESTAMP")),
+ () -> assertThat(rendered.getJsonObject("dataType").getBoolean("withLocalTimeZone"),
+ equalTo(true)));
+ }
+
+ @Test
+ void testRenderDataTypeGeometry() {
+ final JsonObject rendered = renderToJsonObject(new SqlFunctionScalarCast(createGeometry(4326),
+ new SqlLiteralString("dummy")));
+
+ assertAll(() -> assertThat(rendered.getJsonObject("dataType").getString("type"), equalTo("GEOMETRY")),
+ () -> assertThat(rendered.getJsonObject("dataType").getJsonNumber("srid").intValue(),
+ equalTo(4326)));
+ }
+
+ @Test
+ void testRenderDataTypeIntervalYearToMonth() {
+ final JsonObject rendered = renderToJsonObject(new SqlFunctionScalarCast(createIntervalYearMonth(7),
+ new SqlLiteralString("dummy")));
+
+ assertAll(() -> assertThat(rendered.getJsonObject("dataType").getString("type"), equalTo("INTERVAL")),
+ () -> assertThat(rendered.getJsonObject("dataType").getJsonNumber("precision").intValue(), equalTo(7)),
+ () -> assertThat(rendered.getJsonObject("dataType").getString("fromTo"), equalTo("YEAR TO MONTH")));
+ }
+
+ private JsonObject renderToJsonObject(final SqlNode node) {
+ return (JsonObject) new PushdownSqlRenderer().render(node);
+ }
+
}