This is an automated email from the ASF dual-hosted git repository.
amashenkov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new d084f1c152 IGNITE-18786 Sql. Fix TPC-H query 6: BETWEEN operator has
no terminating AND. (#1719)
d084f1c152 is described below
commit d084f1c1526915963235ae4f6a062fa92cf9b988
Author: Max Zhuravkov <[email protected]>
AuthorDate: Mon Feb 27 14:34:30 2023 +0400
IGNITE-18786 Sql. Fix TPC-H query 6: BETWEEN operator has no terminating
AND. (#1719)
---
.../java/org/apache/ignite/sql/ColumnType.java | 2 +-
.../internal/sql/engine/ItDataTypesTest.java | 45 ++++++
modules/sql-engine/build.gradle | 2 +
modules/sql-engine/src/main/codegen/config.fmpp | 7 +-
.../src/main/codegen/includes/parserImpls.ftl | 13 +-
.../sql/engine/sql/IgniteSqlDecimalLiteral.java | 101 +++++++++++++
.../sql/engine/sql/IgniteSqlParserUtil.java | 45 ++++++
.../internal/sql/engine/util/IgniteResource.java | 3 +
.../engine/sql/IgniteSqlDecimalLiteralTest.java | 161 +++++++++++++++++++++
.../sql/engine/sql/SqlLiteralArchTest.java | 114 +++++++++++++++
10 files changed, 489 insertions(+), 4 deletions(-)
diff --git a/modules/api/src/main/java/org/apache/ignite/sql/ColumnType.java
b/modules/api/src/main/java/org/apache/ignite/sql/ColumnType.java
index 8239251dc6..ecc0c4010b 100644
--- a/modules/api/src/main/java/org/apache/ignite/sql/ColumnType.java
+++ b/modules/api/src/main/java/org/apache/ignite/sql/ColumnType.java
@@ -53,7 +53,7 @@ public enum ColumnType {
/** 64-bit double-precision floating-point number. */
DOUBLE,
- /** A decimal floating-point number. */
+ /** Arbitrary-precision signed decimal number. */
DECIMAL,
/** Timezone-free date. */
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
index 3da5dca2a0..18be6135e8 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
@@ -339,6 +339,51 @@ public class ItDataTypesTest extends
AbstractBasicIntegrationTest {
.check();
}
+ /**
+ * Test cases for decimal literals.
+ */
+ @Test
+ public void testDecimalLiteral() {
+ sql("CREATE TABLE tbl(id int PRIMARY KEY, val DECIMAL(32, 5))");
+
+ assertQuery("SELECT DECIMAL '-123.0'").returns(new
BigDecimal(("-123.0"))).check();
+ assertQuery("SELECT DECIMAL '10'").returns(new
BigDecimal(("10"))).check();
+ assertQuery("SELECT DECIMAL '10.000'").returns(new
BigDecimal(("10.000"))).check();
+
+ assertQuery("SELECT DECIMAL '10.000' + DECIMAL '0.1'").returns(new
BigDecimal(("10.100"))).check();
+ assertQuery("SELECT DECIMAL '10.000' - DECIMAL '0.01'").returns(new
BigDecimal(("9.990"))).check();
+ assertQuery("SELECT DECIMAL '10.000' * DECIMAL '0.01'").returns(new
BigDecimal(("0.10000"))).check();
+ assertQuery("SELECT DECIMAL '10.000' / DECIMAL '0.01'").returns(new
BigDecimal(("1000.0"))).check();
+
+ assertQuery("SELECT DECIMAL '10.000' =
'10.000'").returns(true).check();
+ assertQuery("SELECT DECIMAL '10.000' =
'10.001'").returns(false).check();
+
+ assertQuery("SELECT CASE WHEN true THEN DECIMAL '1.00' ELSE DECIMAL
'0' END")
+ .returns(new BigDecimal("1.00")).check();
+
+ assertQuery("SELECT CASE WHEN false THEN DECIMAL '1.00' ELSE DECIMAL
'0.0' END")
+ .returns(new BigDecimal("0.0")).check();
+
+ assertQuery("SELECT DECIMAL \"10\" FROM (SELECT 1 as decimal)
tmp").returns(1).check();
+
+ assertQuery(
+ "SELECT DECIMAL '0.09' BETWEEN DECIMAL '0.06' AND DECIMAL
'0.07'")
+ .returns(false).check();
+
+ assertQuery("SELECT ROUND(DECIMAL '10.000', 2)").returns(new
BigDecimal("10.00")).check();
+ assertQuery("SELECT CAST(DECIMAL '10.000' AS
VARCHAR)").returns("10.000").check();
+ assertQuery("SELECT CAST(DECIMAL '10.000' AS
INTEGER)").returns(10).check();
+
+ sql("INSERT INTO tbl VALUES(1, DECIMAL '10.01')");
+
+ assertQuery("SELECT val FROM tbl").returns(new
BigDecimal("10.01000")).check();
+
+ assertQuery("SELECT id FROM tlb WHERE val = DECIMAL
'10.0'").returns(1);
+
+ sql("UPDATE tbl SET val=DECIMAL '10.20' WHERE id = 1");
+ assertQuery("SELECT id FROM tbl WHERE val = DECIMAL
'10.20'").returns(1);
+ }
+
private LocalDate sqlDate(String str) {
return LocalDate.parse(str);
}
diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle
index 52554028c4..944f2ed923 100644
--- a/modules/sql-engine/build.gradle
+++ b/modules/sql-engine/build.gradle
@@ -79,4 +79,6 @@ dependencies {
testImplementation libs.mockito.inline
testImplementation libs.hamcrest.core
testImplementation libs.slf4j.jdk14
+ testImplementation libs.archunit.core
+ testImplementation libs.archunit.junit5
}
diff --git a/modules/sql-engine/src/main/codegen/config.fmpp
b/modules/sql-engine/src/main/codegen/config.fmpp
index 09184aa0c8..0d79b51ed8 100644
--- a/modules/sql-engine/src/main/codegen/config.fmpp
+++ b/modules/sql-engine/src/main/codegen/config.fmpp
@@ -32,6 +32,8 @@ data: {
"org.apache.calcite.sql.SqlDdl",
"org.apache.calcite.sql.SqlLiteral",
"org.apache.calcite.schema.ColumnStrategy",
+ "org.apache.calcite.sql.ddl.SqlDdlNodes",
+ "org.apache.ignite.internal.sql.engine.sql.IgniteSqlParserUtil",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlAlterTableAddColumn",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlAlterTableDropColumn",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateTable",
@@ -46,9 +48,9 @@ data: {
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlIndexType",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlAlterZoneSet",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlAlterZoneRenameTo",
+ "org.apache.ignite.internal.sql.engine.sql.IgniteSqlDecimalLiteral",
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlTypeNameSpec",
"org.apache.ignite.internal.sql.engine.type.UuidType",
- "org.apache.calcite.sql.ddl.SqlDdlNodes",
]
# List of new keywords. Example: "DATABASES", "TABLES". If the keyword is
@@ -634,13 +636,14 @@ data: {
# Return type of method implementation should be "SqlNode".
# Example: ParseJsonLiteral().
literalParserMethods: [
+ "ParseDecimalLiteral()",
]
# List of methods for parsing custom data types.
# Return type of method implementation should be "SqlTypeNameSpec".
# Example: SqlParseTimeStampZ().
dataTypeParserMethods: [
- "SqlParseUuidType(s)",
+ "UuidType(s)",
]
# Binary operators tokens.
diff --git a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
index fed767b74b..8b63b40853 100644
--- a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
@@ -80,7 +80,7 @@ SqlDataTypeSpec IntervalType() :
}
}
-SqlTypeNameSpec SqlParseUuidType(Span s) :
+SqlTypeNameSpec UuidType(Span s) :
{
final SqlIdentifier typeName;
}
@@ -640,3 +640,14 @@ SqlLiteral AlterZoneStringOptionKey() :
{
<DATA_NODES_FILTER> { return
SqlLiteral.createSymbol(IgniteSqlZoneOptionEnum.DATA_NODES_FILTER, getPos()); }
}
+
+SqlLiteral ParseDecimalLiteral():
+{
+ final BigDecimal value;
+}
+{
+ <DECIMAL> <QUOTED_STRING> {
+ value = IgniteSqlParserUtil.parseDecimal(token.image, getPos());
+ return IgniteSqlDecimalLiteral.create(value, getPos());
+ }
+}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteral.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteral.java
new file mode 100644
index 0000000000..fd6981513a
--- /dev/null
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteral.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import java.math.BigDecimal;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNumericLiteral;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Litmus;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A decimal SQL literal.
+ * <pre>
+ * DECIMAL '<numeric-value>'
+ * </pre>
+ */
+public final class IgniteSqlDecimalLiteral extends SqlNumericLiteral {
+
+ /**
+ * Constructor.
+ */
+ private IgniteSqlDecimalLiteral(BigDecimal value, SqlParserPos pos) {
+ // We are using precision/scale from BigDecimal because calcite's
values
+ // for those are not incorrect as they include an additional digit in
precision for negative numbers.
+ super(value, value.precision(), value.scale(), true, pos);
+ }
+
+ /** Creates a decimal literal. */
+ public static IgniteSqlDecimalLiteral create(BigDecimal value,
SqlParserPos pos) {
+ return new IgniteSqlDecimalLiteral(value, pos);
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
+ writer.keyword("DECIMAL");
+
+ var value = getDecimalValue();
+ var strVal = writer.getDialect().quoteStringLiteral(value.toString());
+
+ writer.literal(strVal);
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public RelDataType createSqlType(RelDataTypeFactory typeFactory) {
+ var value = getDecimalValue();
+
+ return typeFactory.createSqlType(SqlTypeName.DECIMAL,
value.precision(), value.scale());
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public SqlNumericLiteral clone(SqlParserPos pos) {
+ var value = getDecimalValue();
+
+ return new IgniteSqlDecimalLiteral(value, pos);
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public boolean equalsDeep(@Nullable SqlNode node, Litmus litmus) {
+ if (!(node instanceof IgniteSqlDecimalLiteral)) {
+ return litmus.fail("{} != {}", this, node);
+ }
+
+ IgniteSqlDecimalLiteral that = (IgniteSqlDecimalLiteral) node;
+
+ if (that.getDecimalValue().compareTo(getDecimalValue()) != 0) {
+ return litmus.fail("{} != {}", this, node);
+ }
+
+ return true;
+ }
+
+ private BigDecimal getDecimalValue() {
+ var value = bigDecimalValue();
+ assert value != null : "bigDecimalValue returned null for a subclass
exact numeric literal: " + this;
+ return value;
+ }
+}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserUtil.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserUtil.java
new file mode 100644
index 0000000000..427390446c
--- /dev/null
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserUtil.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import java.math.BigDecimal;
+import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import
org.apache.ignite.internal.generated.query.calcite.sql.IgniteSqlParserImpl;
+import org.apache.ignite.internal.sql.engine.util.IgniteResource;
+
+/**
+ * Utility functions used by {@link IgniteSqlParserImpl ignite sql parser}.
+ */
+public class IgniteSqlParserUtil {
+
+ private IgniteSqlParserUtil() {
+
+ }
+
+ /** Parses decimal from a decimal literal value (a string in single
quotes). */
+ public static BigDecimal parseDecimal(String value, SqlParserPos pos) {
+ assert value.startsWith("'") && value.endsWith("'") : "input must be
quoted: " + value;
+
+ try {
+ return new BigDecimal(value.substring(1, value.length() - 1));
+ } catch (NumberFormatException ignore) {
+ throw SqlUtil.newContextException(pos,
IgniteResource.INSTANCE.decimalLiteralInvalid());
+ }
+ }
+}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
index 024c872e2a..b9265a20ac 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
@@ -40,4 +40,7 @@ public interface IgniteResource {
@Resources.BaseMessage("Unexpected number of query parameters. Provided
{0} but there is only {1} dynamic parameter(s).")
Resources.ExInst<SqlValidatorException> unexpectedParameter(int provided,
int expected);
+
+ @Resources.BaseMessage("Invalid decimal literal")
+ Resources.ExInst<SqlValidatorException> decimalLiteralInvalid();
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
new file mode 100644
index 0000000000..1fda6fbab9
--- /dev/null
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDecimalLiteralTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import static org.apache.ignite.lang.IgniteStringFormatter.format;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigDecimal;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.pretty.SqlPrettyWriter;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Litmus;
+import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.sql.SqlException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests for {@link IgniteSqlDecimalLiteral}.
+ */
+public class IgniteSqlDecimalLiteralTest extends AbstractPlannerTest {
+
+ /**
+ * Tests literal type type.
+ */
+ @Test
+ public void testValueAndType() {
+ var input = new BigDecimal("100.20");
+ var literal = IgniteSqlDecimalLiteral.create(input, SqlParserPos.ZERO);
+ assertTrue(literal.isExact(), "decimal is always exact");
+
+ assertEquals(5, literal.getPrec(), "precision");
+ assertEquals(2, literal.getScale(), "scale");
+
+ assertEquals(input, literal.getValue(), "value");
+
+ var typeFactory = new IgniteTypeFactory();
+ var actualType = literal.createSqlType(typeFactory);
+
+ var expectedType = typeFactory.createSqlType(SqlTypeName.DECIMAL,
input.precision(), input.scale());
+ assertEquals(expectedType, actualType, "type");
+ }
+
+ /**
+ * Tests {@link IgniteSqlDecimalLiteral#unparse(SqlWriter, int, int)}.
+ */
+ @Test
+ public void testToSql() {
+ var input = new BigDecimal("100.20");
+ var literal = IgniteSqlDecimalLiteral.create(input, SqlParserPos.ZERO);
+
+ var w = new SqlPrettyWriter();
+ literal.unparse(w, 0, 0);
+
+ assertEquals(format("DECIMAL '{}'", input), w.toString(), "SQL
string");
+ }
+
+ /**
+ * Tests {@link IgniteSqlDecimalLiteral#clone(SqlParserPos)}.
+ */
+ @Test
+ public void testClone() {
+ var literal = IgniteSqlDecimalLiteral.create(BigDecimal.ONE,
SqlParserPos.ZERO);
+
+ var newPos = new SqlParserPos(1, 2);
+ var literalAtPos = literal.clone(newPos);
+
+ assertEquals(IgniteSqlDecimalLiteral.create(BigDecimal.ONE, newPos),
literalAtPos, "clone with position");
+ }
+
+ /**
+ * Tests {@link IgniteSqlDecimalLiteral#equalsDeep(SqlNode, Litmus)}.
+ */
+ @Test
+ public void testEquality() {
+ var decimal = IgniteSqlDecimalLiteral.create(BigDecimal.ONE,
SqlParserPos.ZERO);
+ var exactNumeric = SqlLiteral.createExactNumeric("1",
SqlParserPos.ZERO);
+
+ boolean equal = decimal.equalsDeep(exactNumeric, Litmus.IGNORE);
+ assertFalse(equal, "decimal literal != exact numeric literal");
+
+ var decimal2 = IgniteSqlDecimalLiteral.create(BigDecimal.ONE,
SqlParserPos.ZERO);
+ assertEquals(decimal, decimal2);
+ }
+
+ /**
+ * Test cases for invalid literal values.
+ */
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "DECIMAL 'NAN'",
+ "DECIMAL '10a'",
+ "DECIMAL 'a10'",
+ "DECIMAL 'f1'",
+ "DECIMAL '1\n1000'",
+ })
+ public void testParserRejectsInvalidValues(String value) {
+ var query = format("SELECT {}", value);
+ var err = assertThrows(SqlException.class, () -> parseQuery(query));
+
+ assertThat(err.getMessage(), containsString("Invalid decimal
literal"));
+ }
+
+ /**
+ * Test cases for invalid literal expressions.
+ */
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "DECIMAL x'00'",
+ "DECIMAL N\"10\"",
+ "DECIMAL 'a10'",
+ "DECIMAL '10",
+ "DECIMAL 10'",
+ })
+ public void testParserRejectInvalidForms(String value) {
+ var query = format("SELECT {}", value);
+
+ assertThrows(SqlException.class, () -> parseQuery(query));
+ }
+
+ /**
+ * Test case that ensures that {@code DECIMAL} in {@code DECIMAL "1"} is
interpreted as an alias to a column named {@code DECIMAL}.
+ */
+ @Test
+ public void testDecimalAsAlias() {
+ SqlNodeList nodeList = parseQuery("SELECT DECIMAL \"10\"");
+
+ assertEquals("SELECT `DECIMAL` AS `10`", nodeList.get(0).toString());
+ }
+
+ private static SqlNodeList parseQuery(String qry) {
+ return Commons.parse(qry, Commons.PARSER_CONFIG);
+ }
+}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlLiteralArchTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlLiteralArchTest.java
new file mode 100644
index 0000000000..31275adfa9
--- /dev/null
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlLiteralArchTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static org.apache.ignite.lang.IgniteStringFormatter.format;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.Litmus;
+
+/**
+ * {@link ArchRule ArchUnit rules} for subclasses of {@link SqlLiteral}.
+ */
+@AnalyzeClasses(
+ packagesOf = IgniteSqlDecimalLiteral.class
+)
+public class SqlLiteralArchTest {
+
+ @ArchTest
+ @SuppressWarnings("unused")
+ public static ArchRule LITERALS_IMPLEMENT_CLONE = sqlLiterals()
+ .should(new ImplementMethod("clone", SqlParserPos.class));
+
+ @ArchTest
+ @SuppressWarnings("unused")
+ public static ArchRule LITERALS_IMPLEMENT_CREATE_TYPE = sqlLiterals()
+ .should(new ImplementMethod("createSqlType",
RelDataTypeFactory.class));
+
+ @ArchTest
+ @SuppressWarnings("unused")
+ public static ArchRule LITERALS_IMPLEMENT_UNPARSE = sqlLiterals()
+ .should(new ImplementMethod("unparse", SqlWriter.class, int.class,
int.class));
+
+ @ArchTest
+ @SuppressWarnings("unused")
+ public static ArchRule LITERALS_IMPLEMENT_EQUALS_DEEP = sqlLiterals()
+ .should(new ImplementMethod("equalsDeep", SqlNode.class,
Litmus.class));
+
+
+ /**
+ * All subclasses of {@link SqlLiteral}.
+ */
+ private static GivenClassesConjunction sqlLiterals() {
+ return classes().that().areAssignableTo(SqlLiteral.class);
+ }
+
+ /**
+ * Checks that the given method is defined in a class.
+ */
+ private static class ImplementMethod extends ArchCondition<JavaClass> {
+
+ private final String methodName;
+
+ @SuppressWarnings("rawtypes")
+ private final Class[] parameters;
+
+ @SuppressWarnings("rawtypes")
+ ImplementMethod(String methodName, Class... parameters) {
+ super("have %s method defined", methodDisplayName(methodName,
parameters));
+
+ this.methodName = methodName;
+ this.parameters = parameters;
+ }
+
+ /** {@inheritDoc} **/
+ @Override
+ public void check(JavaClass javaClass, ConditionEvents
conditionEvents) {
+ boolean satisfied;
+ try {
+ javaClass.getMethod(methodName, parameters);
+ satisfied = true;
+ } catch (IllegalArgumentException e) {
+ satisfied = false;
+ }
+
+ var violation = javaClass.getFullName() + ": No method " +
methodDisplayName(methodName, parameters);
+ conditionEvents.add(new SimpleConditionEvent(javaClass, satisfied,
violation));
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static String methodDisplayName(String methodName, Class[]
parameters) {
+ return format("{}({})", methodName,
Arrays.stream(parameters).map(Class::getName).collect(Collectors.joining()));
+ }
+ }
+}