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 '&lt;numeric-value&gt;'
+ * </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()));
+        }
+    }
+}

Reply via email to