Skip to content

Commit

Permalink
Issue 31437 with keyword query error mysql (#34163)
Browse files Browse the repository at this point in the history
* Added the With Segment Binder which improves the working of With Query and resolved the CheckTableExist error during With keyword Query execution

* Resolves Merge Conflicts

* Added unique Alias Exception while using Common Table Expression

* Fix Spotless error

* Added Release Notes

* Added SQL bind test case for WithSegmentBinder

* Added Release Notes

* Resolve Merge Conflict

* Added Release Notes

* Solved Spotless and Checkstyle error

* Changed the working for Unique Alias Name Exception handling for CTE's

* Update Release Notes

* Changed Release Notes to resolve merge conflict

* Sync With Master Branch

* Removed the Changes for with Clause in DeleteStatement will raise it with a different PR.

* Added SQLBinderIT test case for with clause.

* Updated documentation for the new Exception created

* Added e2e test case for with clause in e2e\sql\resources\cases\dql

* Corrected e2e test case for with clause

* Refactor to sync master branch

* Updates for e2e test case failure

* Changes made in e2e test case for db and tbl

* Changes in e2e test case

---------

Co-authored-by: Zhengqiang Duan <[email protected]>
  • Loading branch information
Yash-cor and strongduanmu authored Jan 9, 2025
1 parent 425ef83 commit 5c6745e
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ SQL 错误码以标准的 SQL State,Vendor Code 和详细错误信息提供,
| 12101 | 42000 | Can not accept SQL type '%s'. |
| 12200 | 42000 | Hint data source '%s' does not exist. |
| 12300 | 0A000 | DROP TABLE ... CASCADE is not supported. |
| 12500 | 42000 | Not unique table/alias: '%s' |

### 连接

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ SQL error codes provide by standard `SQL State`, `Vendor Code` and `Reason`, whi
| 12101 | 42000 | Can not accept SQL type '%s'. |
| 12200 | 42000 | Hint data source '%s' does not exist. |
| 12300 | 0A000 | DROP TABLE ... CASCADE is not supported. |
| 12500 | 42000 | Not unique table/alias: '%s' |

### Connection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ private static SubquerySegment bindSubquerySegment(final SubquerySegment segment
SQLStatementBinderContext subqueryBinderContext =
new SQLStatementBinderContext(binderContext.getMetaData(), binderContext.getCurrentDatabaseName(), binderContext.getHintValueContext(), segment.getSelect());
subqueryBinderContext.getExternalTableBinderContexts().putAll(binderContext.getExternalTableBinderContexts());
subqueryBinderContext.getCommonTableExpressionsSegmentsUniqueAliases().addAll(binderContext.getCommonTableExpressionsSegmentsUniqueAliases());
result.setSelect(new SelectStatementBinder(outerTableBinderContexts).bind(segment.getSelect(), subqueryBinderContext));
binderContext.getCommonTableExpressionsSegmentsUniqueAliases().addAll(subqueryBinderContext.getCommonTableExpressionsSegmentsUniqueAliases());
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public static SubquerySegment bind(final SubquerySegment segment, final SQLState
SQLStatementBinderContext selectBinderContext =
new SQLStatementBinderContext(binderContext.getMetaData(), binderContext.getCurrentDatabaseName(), binderContext.getHintValueContext(), segment.getSelect());
selectBinderContext.getExternalTableBinderContexts().putAll(binderContext.getExternalTableBinderContexts());
selectBinderContext.getCommonTableExpressionsSegmentsUniqueAliases().addAll(binderContext.getCommonTableExpressionsSegmentsUniqueAliases());
SelectStatement boundSelectStatement = new SelectStatementBinder(outerTableBinderContexts).bind(segment.getSelect(), selectBinderContext);
binderContext.getCommonTableExpressionsSegmentsUniqueAliases().addAll(selectBinderContext.getCommonTableExpressionsSegmentsUniqueAliases());
return new SubquerySegment(segment.getStartIndex(), segment.getStopIndex(), boundSelectStatement, segment.getText());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ private static void checkTableExists(final SQLStatementBinderContext binderConte
if (binderContext.getExternalTableBinderContexts().containsKey(new CaseInsensitiveString(tableName))) {
return;
}
if (binderContext.getCommonTableExpressionsSegmentsUniqueAliases().contains(tableName)) {
return;
}
ShardingSpherePreconditions.checkState(schema.containsTable(tableName), () -> new TableNotFoundException(tableName));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public static SubqueryTableSegment bind(final SubqueryTableSegment segment, fina
SQLStatementBinderContext subqueryBinderContext =
new SQLStatementBinderContext(binderContext.getMetaData(), binderContext.getCurrentDatabaseName(), binderContext.getHintValueContext(), segment.getSubquery().getSelect());
subqueryBinderContext.getExternalTableBinderContexts().putAll(binderContext.getExternalTableBinderContexts());
subqueryBinderContext.getCommonTableExpressionsSegmentsUniqueAliases().addAll(binderContext.getCommonTableExpressionsSegmentsUniqueAliases());
SelectStatement boundSubSelect = new SelectStatementBinder(outerTableBinderContexts).bind(segment.getSubquery().getSelect(), subqueryBinderContext);
binderContext.getCommonTableExpressionsSegmentsUniqueAliases().addAll(subqueryBinderContext.getCommonTableExpressionsSegmentsUniqueAliases());
SubquerySegment boundSubquerySegment = new SubquerySegment(segment.getSubquery().getStartIndex(), segment.getSubquery().getStopIndex(), boundSubSelect, segment.getSubquery().getText());
IdentifierValue subqueryTableName = segment.getAliasSegment().map(AliasSegment::getIdentifier).orElseGet(() -> new IdentifierValue(""));
SubqueryTableSegment result = new SubqueryTableSegment(segment.getStartIndex(), segment.getStopIndex(), boundSubquerySegment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.type.SimpleTableSegmentBinderContext;
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.type.SubqueryTableSegmentBinder;
import org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
import org.apache.shardingsphere.infra.exception.kernel.syntax.DuplicateCommonTableExpressionAliasException;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonTableExpressionSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SubqueryTableSegment;
Expand All @@ -45,6 +47,11 @@ public final class CommonTableExpressionSegmentBinder {
* @return bound common table expression segment
*/
public static CommonTableExpressionSegment bind(final CommonTableExpressionSegment segment, final SQLStatementBinderContext binderContext, final boolean recursive) {
if (segment.getAliasName().isPresent()) {
ShardingSpherePreconditions.checkState(!binderContext.getCommonTableExpressionsSegmentsUniqueAliases().contains(segment.getAliasName().get()),
() -> new DuplicateCommonTableExpressionAliasException(segment.getAliasName().get()));
binderContext.getCommonTableExpressionsSegmentsUniqueAliases().add(segment.getAliasName().get());
}
if (recursive && segment.getAliasName().isPresent()) {
binderContext.getExternalTableBinderContexts().put(new CaseInsensitiveString(segment.getAliasName().get()),
new SimpleTableSegmentBinderContext(segment.getColumns().stream().map(ColumnProjectionSegment::new).collect(Collectors.toList())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public final class SQLStatementBinderContext {

private final SQLStatement sqlStatement;

private final Collection<String> commonTableExpressionsSegmentsUniqueAliases = new CaseInsensitiveSet<>();

private final Collection<String> usingColumnNames = new CaseInsensitiveSet<>();

private final Collection<ProjectionSegment> joinTableProjectionSegments = new LinkedList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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.infra.binder.with;

import com.cedarsoftware.util.CaseInsensitiveMap.CaseInsensitiveString;
import org.apache.shardingsphere.infra.binder.engine.segment.dml.from.context.type.SimpleTableSegmentBinderContext;
import org.apache.shardingsphere.infra.binder.engine.segment.dml.with.WithSegmentBinder;
import org.apache.shardingsphere.infra.binder.engine.statement.SQLStatementBinderContext;
import org.apache.shardingsphere.infra.hint.HintValueContext;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonTableExpressionSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubquerySegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ShorthandProjectionSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.AliasSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.WithSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SimpleTableSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.TableNameSegment;
import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
import org.apache.shardingsphere.sql.parser.statement.mysql.dml.MySQLSelectStatement;
import org.junit.jupiter.api.Test;

import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;

public class WithSegmentBinderTest {

@Test
void assertBind() {

MySQLSelectStatement mySQLSelectStatement = new MySQLSelectStatement();
ProjectionsSegment projectionSegment = new ProjectionsSegment(42, 48);
ColumnSegment columnSegment = new ColumnSegment(42, 48, new IdentifierValue("user_id"));
projectionSegment.getProjections().add(new ColumnProjectionSegment(columnSegment));
mySQLSelectStatement.setProjections(projectionSegment);
mySQLSelectStatement.setFrom(new SimpleTableSegment(new TableNameSegment(55, 57, new IdentifierValue("cte"))));

MySQLSelectStatement subqueryMySQLSelectStatement = new MySQLSelectStatement();
ProjectionsSegment subqueryProjectionSegment = new ProjectionsSegment(20, 20);
ShorthandProjectionSegment shorthandProjectionSegment = new ShorthandProjectionSegment(20, 20);
subqueryProjectionSegment.getProjections().add(shorthandProjectionSegment);
subqueryMySQLSelectStatement.setProjections(subqueryProjectionSegment);
subqueryMySQLSelectStatement.setFrom(new SimpleTableSegment(new TableNameSegment(27, 32, new IdentifierValue("t_user"))));
SubquerySegment subquerySegment = new SubquerySegment(5, 33, subqueryMySQLSelectStatement, "(SELECT * FROM t_user)");
Collection<CommonTableExpressionSegment> commonTableExpressionSegments = new LinkedList<>();
commonTableExpressionSegments.add(new CommonTableExpressionSegment(5, 33, new AliasSegment(5, 7, new IdentifierValue("cte")), subquerySegment));
WithSegment withSegment = new WithSegment(0, 33, commonTableExpressionSegments);
mySQLSelectStatement.setWithSegment(withSegment);

SQLStatementBinderContext binderContext = new SQLStatementBinderContext(createMetaData(), "foo_db", new HintValueContext(), mySQLSelectStatement);
WithSegment actual = WithSegmentBinder.bind(withSegment, binderContext, binderContext.getExternalTableBinderContexts());

assertThat(binderContext.getExternalTableBinderContexts().size(), is(1));
assertThat(binderContext.getCommonTableExpressionsSegmentsUniqueAliases().size(), is(1));
assertThat(actual.getStartIndex(), is(0));
assertTrue(binderContext.getExternalTableBinderContexts().containsKey(new CaseInsensitiveString("cte")));
assertTrue(actual.getCommonTableExpressions().iterator().next().getAliasName().isPresent());
assertThat(actual.getCommonTableExpressions().iterator().next().getAliasName().get(), is("cte"));
assertThat(binderContext.getCommonTableExpressionsSegmentsUniqueAliases().iterator().next(), is("cte"));

SimpleTableSegmentBinderContext simpleTableSegment = (SimpleTableSegmentBinderContext) binderContext.getExternalTableBinderContexts().get(new CaseInsensitiveString("cte")).iterator().next();
ArrayList<ColumnProjectionSegment> columnProjectionSegments = new ArrayList<>();
simpleTableSegment.getProjectionSegments().forEach(each -> columnProjectionSegments.add((ColumnProjectionSegment) each));

assertThat(columnProjectionSegments.get(0).getColumn().getIdentifier().getValue(), is("user_id"));
assertThat(columnProjectionSegments.get(1).getColumn().getIdentifier().getValue(), is("name"));
assertTrue(columnProjectionSegments.get(1).getColumn().getOwner().isPresent());
assertThat(columnProjectionSegments.get(1).getColumn().getOwner().get().getIdentifier().getValue(), is("t_user"));
assertTrue(columnProjectionSegments.get(0).getColumn().getOwner().isPresent());
assertThat(columnProjectionSegments.get(0).getColumn().getOwner().get().getIdentifier().getValue(), is("t_user"));
assertThat(columnProjectionSegments.get(0).getColumn().getIdentifier().getValue(), is("user_id"));
assertThat(columnProjectionSegments.get(1).getColumn().getColumnBoundInfo().getOriginalSchema().getValue(), is("foo_db"));
assertThat(columnProjectionSegments.get(0).getColumn().getColumnBoundInfo().getOriginalSchema().getValue(), is("foo_db"));
assertThat(actual.getCommonTableExpressions().iterator().next().getSubquery().getText(), is("(SELECT * FROM t_user)"));
}

private ShardingSphereMetaData createMetaData() {
ShardingSphereSchema schema = mock(ShardingSphereSchema.class, RETURNS_DEEP_STUBS);
when(schema.getTable("t_user").getAllColumns()).thenReturn(Arrays.asList(
new ShardingSphereColumn("user_id", Types.INTEGER, true, false, false, true, false, false),
new ShardingSphereColumn("name", Types.VARCHAR, false, false, false, true, false, false)));

ShardingSphereMetaData result = mock(ShardingSphereMetaData.class, RETURNS_DEEP_STUBS);
when(result.getDatabase("foo_db").getSchema("foo_db")).thenReturn(schema);
when(result.containsDatabase("foo_db")).thenReturn(true);
when(result.getDatabase("foo_db").containsSchema("foo_db")).thenReturn(true);
when(result.getDatabase("foo_db").getSchema("foo_db").containsTable("t_user")).thenReturn(true);
return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.infra.exception.kernel.syntax;

import org.apache.shardingsphere.infra.exception.core.external.sql.sqlstate.XOpenSQLState;
import org.apache.shardingsphere.infra.exception.core.external.sql.type.kernel.category.SyntaxSQLException;

/**
* Duplicate common table expression alias exception.
*/
public final class DuplicateCommonTableExpressionAliasException extends SyntaxSQLException {

private static final long serialVersionUID = -8206891094419297634L;

public DuplicateCommonTableExpressionAliasException(final String alias) {
super(XOpenSQLState.SYNTAX_ERROR, 500, "Not unique table/alias: '%s'", alias);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!--
~ 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.
-->

<e2e-test-cases>
<test-case sql="WITH products AS (SELECT * FROM t_product) SELECT * FROM products" db-types="MySQL" scenario-types="db">
<assertion />
</test-case>
</e2e-test-cases>
Loading

0 comments on commit 5c6745e

Please sign in to comment.