Skip to content

Commit

Permalink
Support for federated query NOT operator (#32961)
Browse files Browse the repository at this point in the history
* Support for federated query NOT operator

* Improve not operator sql node converter

* Format code
  • Loading branch information
zihaoAK47 authored Sep 23, 2024
1 parent 6a6532a commit 8491a00
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlPrefixOperator;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.shardingsphere.sqlfederation.optimizer.converter.operator.dialect.mysql.MySQLMatchAgainstOperator;

/**
Expand Down Expand Up @@ -60,5 +63,7 @@ public final class SQLExtensionOperatorTable {

public static final SqlPrefixOperator TILDE = new SqlPrefixOperator("~", SqlKind.OTHER, 26, null, null, null);

public static final SqlPrefixOperator NOT = new SqlPrefixOperator("NOT", SqlKind.NOT, 26, ReturnTypes.BIGINT, InferTypes.BOOLEAN, OperandTypes.ANY);

public static final MySQLMatchAgainstOperator MATCH_AGAINST = new MySQLMatchAgainstOperator();
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public static Optional<SqlNode> convert(final LiteralExpressionSegment segment)
if (segment.getLiterals() instanceof String) {
return Optional.of(SqlLiteral.createCharString(literalValue, SqlParserPos.ZERO));
}
if (segment.getLiterals() instanceof Boolean) {
return Optional.of(SqlLiteral.createBoolean(Boolean.parseBoolean(literalValue), SqlParserPos.ZERO));
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import lombok.NoArgsConstructor;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.NotExpression;
import org.apache.shardingsphere.sqlfederation.optimizer.converter.operator.common.SQLExtensionOperatorTable;
Expand Down Expand Up @@ -50,6 +49,6 @@ public static Optional<SqlNode> convert(final NotExpression segment) {
if (segment.getNotSign().equals(true)) {
return Optional.of(new SqlBasicCall(SQLExtensionOperatorTable.NOT_SIGN, sqlNodes, SqlParserPos.ZERO));
}
return Optional.of(new SqlBasicCall(SqlStdOperatorTable.NOT, sqlNodes, SqlParserPos.ZERO));
return Optional.of(new SqlBasicCall(SQLExtensionOperatorTable.NOT, sqlNodes, SqlParserPos.ZERO));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.shardingsphere.sqlfederation.optimizer.function.mysql;

import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable;
import org.apache.calcite.sql.validate.SqlUserDefinedFunction;
import org.apache.shardingsphere.sqlfederation.optimizer.function.mysql.impl.MySQLNotFunction;

/**
* MySQL operator table.
*/
public final class MySQLOperatorTable extends ReflectiveSqlOperatorTable {

public static final SqlUserDefinedFunction NOT = new MySQLNotFunction();

public MySQLOperatorTable() {
init();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.shardingsphere.sqlfederation.optimizer.function.mysql.impl;

import com.google.common.collect.ImmutableList;
import org.apache.calcite.schema.impl.ScalarFunctionImpl;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlUserDefinedFunction;

import java.util.Collections;

/**
* MySQL not function.
*/
public final class MySQLNotFunction extends SqlUserDefinedFunction {

public MySQLNotFunction() {
super(new SqlIdentifier("NOT", SqlParserPos.ZERO), SqlKind.NOT, ReturnTypes.BIGINT_NULLABLE, InferTypes.BOOLEAN,
OperandTypes.operandMetadata(Collections.singletonList(SqlTypeFamily.ANY), typeFactory -> ImmutableList.of(typeFactory.createSqlType(SqlTypeName.BIGINT)), null, arg -> false),
ScalarFunctionImpl.create(MySQLNotFunction.class, "not"));
}

@Override
public SqlSyntax getSyntax() {
return SqlSyntax.PREFIX;
}

/**
* not.
*
* @param value value
* @return not operator result
*/
public static Long not(final Object value) {
if (null == value) {
return null;
}
if (value instanceof Number) {
return ((Number) value).longValue() == 0 ? 1L : 0L;
}
if (value instanceof Boolean) {
return ((Boolean) value) ? 0L : 1L;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.apache.shardingsphere.infra.database.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
import org.apache.shardingsphere.parser.rule.SQLParserRule;
import org.apache.shardingsphere.sqlfederation.optimizer.function.mysql.MySQLOperatorTable;
import org.apache.shardingsphere.sqlfederation.optimizer.function.SQLFederationFunctionRegister;
import org.apache.shardingsphere.sqlfederation.optimizer.metadata.view.ShardingSphereViewExpander;
import org.apache.shardingsphere.sqlfederation.optimizer.planner.rule.converter.EnumerableModifyConverterRule;
Expand Down Expand Up @@ -232,7 +233,7 @@ public static SqlValidator createSqlValidator(final CalciteCatalogReader catalog
}

private static SqlOperatorTable getSQLOperatorTable(final CalciteCatalogReader catalogReader, final DatabaseType databaseType) {
return SqlOperatorTables.chain(Arrays.asList(SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
return SqlOperatorTables.chain(Arrays.asList(new MySQLOperatorTable(), SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
Arrays.asList(SqlLibrary.STANDARD, DATABASE_TYPE_SQL_LIBRARIES.getOrDefault(databaseType.getType(), SqlLibrary.MYSQL))), catalogReader));
}

Expand Down
16 changes: 16 additions & 0 deletions test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select.xml
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,20 @@
<test-case sql="SELECT BIN(NULL), BIN(''), BIN(' ')" db-types="MySQL" scenario-types="db_tbl_sql_federation">
<assertion expected-data-source-name="read_dataset" />
</test-case>

<test-case sql="SELECT NOT 1, not 0, NOT null" db-types="MySQL" scenario-types="db_tbl_sql_federation">
<assertion expected-data-source-name="read_dataset" />
</test-case>

<test-case sql="SELECT NOT order_id FROM t_order" db-types="MySQL" scenario-types="db_tbl_sql_federation">
<assertion expected-data-source-name="read_dataset" />
</test-case>

<test-case sql="SELECT NOT (t1.order_id + t2.order_id) FROM t_order t1 INNER JOIN t_order_item t2 ON t1.order_id = t2.order_id" db-types="MySQL" scenario-types="db_tbl_sql_federation">
<assertion expected-data-source-name="read_dataset" />
</test-case>

<test-case sql="SELECT BIN(NOT 1), NOT BIT_COUNT(0)" db-types="MySQL" scenario-types="db_tbl_sql_federation">
<assertion expected-data-source-name="read_dataset" />
</test-case>
</e2e-test-cases>
2 changes: 2 additions & 0 deletions test/it/optimizer/src/test/resources/converter/select.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@
<test-cases sql-case-id="select_with_json_value_return_type" expected-sql="SELECT * FROM `t_order` WHERE JSON_VALUE(`items`, '''$.name''' 'RETURNING' VARCHAR(100)) = 'jack'" db-types="MySQL" />
<test-cases sql-case-id="select_projection_with_parameter" expected-sql="SELECT 1 &quot;id&quot;, ?, &quot;SYSDATE&quot; &quot;create_time&quot;, &quot;TRUNC&quot;(&quot;SYSDATE&quot;) &quot;create_date&quot;" db-types="Oracle" sql-case-types="PLACEHOLDER" />
<test-cases sql-case-id="select_projection_with_parameter" expected-sql="SELECT 1 &quot;id&quot;, 'OK' &quot;status&quot;, &quot;SYSDATE&quot; &quot;create_time&quot;, &quot;TRUNC&quot;(&quot;SYSDATE&quot;) &quot;create_date&quot;" db-types="Oracle" sql-case-types="LITERAL" />
<test-cases sql-case-id="select_with_not_operator_number" expected-sql="SELECT NOT 0, NOT 1, NOT 2" db-types="MySQL" />
<test-cases sql-case-id="select_with_not_operator_boolean" expected-sql="SELECT NOT TRUE, NOT FALSE" db-types="MySQL" />
</sql-node-converter-test-cases>
55 changes: 55 additions & 0 deletions test/it/parser/src/main/resources/case/dml/select.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9555,4 +9555,59 @@
</expr>
</where>
</select>

<select sql-case-id="select_with_not_operator_number">
<projections start-index="7" stop-index="25">
<expression-projection text="NOT 0" start-index="7" stop-index="11">
<expr>
<not-expression start-index="7" stop-index="11">
<expr>
<literal-expression value="0" start-index="11" stop-index="11" />
</expr>
</not-expression>
</expr>
</expression-projection>
<expression-projection text="NOT 1" start-index="14" stop-index="18">
<expr>
<not-expression start-index="14" stop-index="18">
<expr>
<literal-expression value="1" start-index="18" stop-index="18" />
</expr>
</not-expression>
</expr>
</expression-projection>
<expression-projection text="NOT 2" start-index="21" stop-index="25">
<expr>
<not-expression start-index="21" stop-index="25">
<expr>
<literal-expression value="2" start-index="25" stop-index="25" />
</expr>
</not-expression>
</expr>
</expression-projection>
</projections>
</select>

<select sql-case-id="select_with_not_operator_boolean">
<projections start-index="7" stop-index="25">
<expression-projection text="NOT TRUE" start-index="7" stop-index="14">
<expr>
<not-expression start-index="7" stop-index="14">
<expr>
<literal-expression value="true" start-index="11" stop-index="14" />
</expr>
</not-expression>
</expr>
</expression-projection>
<expression-projection text="NOT FALSE" start-index="17" stop-index="25">
<expr>
<not-expression start-index="17" stop-index="25">
<expr>
<literal-expression value="false" start-index="21" stop-index="25" />
</expr>
</not-expression>
</expr>
</expression-projection>
</projections>
</select>
</sql-parser-test-cases>
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,6 @@
<sql-case id="select_with_reserved_word_with_table_ref" value="select xxx.condition from xxx" db-types="MySQL,Doris"/>
<sql-case id="select_with_reserved_word" value="select describe from xxx" db-types="MySQL,Doris"/>
<sql-case id="select_with_nvl_function_and_interval_hour" value="SELECT * FROM t_order t WHERE t.CREATE_TIME &lt;= nvl(END_TIME, sysdate) - INTERVAL ? HOUR AND t.STATUS = 'FAILURE'" db-types="Oracle"/>
<sql-case id="select_with_not_operator_number" value="SELECT NOT 0, NOT 1, NOT 2" db-types="MySQL" />
<sql-case id="select_with_not_operator_boolean" value="SELECT NOT TRUE, NOT FALSE" db-types="MySQL" />
</sql-cases>

0 comments on commit 8491a00

Please sign in to comment.