diff --git a/shadow-core/pom.xml b/shadow-core/pom.xml index d2886bb7b394b70f3b35a4f1e41a7430508afd15..55b92eb2edfd72b8637f0937b61a65cc6c9f1001 100644 --- a/shadow-core/pom.xml +++ b/shadow-core/pom.xml @@ -31,5 +31,7 @@ shadow-core-rewrite + shadow-core-common + shadow-core-route diff --git a/shadow-core/shadow-core-common/pom.xml b/shadow-core/shadow-core-common/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..f20d76ed98ce50d27a22a1b5927a90331db2ed56 --- /dev/null +++ b/shadow-core/shadow-core-common/pom.xml @@ -0,0 +1,45 @@ + + + + + + shadow-core + org.apache.shardingsphere + 5.0.0-RC1-SNAPSHOT + + 4.0.0 + + shadow-core-common + ${project.artifactId} + + + + org.apache.shardingsphere + sharding-core-common + ${project.version} + + + + org.apache.shardingsphere + shardingsphere-route + ${project.version} + + + diff --git a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowCondition.java b/shadow-core/shadow-core-common/src/main/java/org/apache/shardingsphere/shadow/common/condition/ShadowCondition.java similarity index 98% rename from shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowCondition.java rename to shadow-core/shadow-core-common/src/main/java/org/apache/shardingsphere/shadow/common/condition/ShadowCondition.java index 02d109f3257e4b019e89f14d219b96a58f937d56..fc48b8c68589e24ea618200317e2c3c448546df1 100644 --- a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowCondition.java +++ b/shadow-core/shadow-core-common/src/main/java/org/apache/shardingsphere/shadow/common/condition/ShadowCondition.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.shardingsphere.shadow.rewrite.condition; +package org.apache.shardingsphere.shadow.common.condition; import lombok.EqualsAndHashCode; import lombok.Getter; diff --git a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowConditionEngine.java b/shadow-core/shadow-core-common/src/main/java/org/apache/shardingsphere/shadow/common/condition/ShadowConditionEngine.java similarity index 98% rename from shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowConditionEngine.java rename to shadow-core/shadow-core-common/src/main/java/org/apache/shardingsphere/shadow/common/condition/ShadowConditionEngine.java index ca95ac25b8ad29369c55103b3bbab616ee90917d..911f8f011aaf9b1d80820c1a0695757107844ef3 100644 --- a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowConditionEngine.java +++ b/shadow-core/shadow-core-common/src/main/java/org/apache/shardingsphere/shadow/common/condition/ShadowConditionEngine.java @@ -15,11 +15,12 @@ * limitations under the License. */ -package org.apache.shardingsphere.shadow.rewrite.condition; +package org.apache.shardingsphere.shadow.common.condition; import lombok.RequiredArgsConstructor; import org.apache.shardingsphere.core.rule.ShadowRule; import org.apache.shardingsphere.sql.parser.binder.statement.SQLStatementContext; +import org.apache.shardingsphere.sql.parser.binder.type.WhereAvailable; import org.apache.shardingsphere.sql.parser.sql.segment.dml.expr.simple.SimpleExpressionSegment; import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.AndPredicate; import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.PredicateSegment; @@ -27,7 +28,6 @@ import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.WhereSegme import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.value.PredicateBetweenRightValue; import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.value.PredicateCompareRightValue; import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.value.PredicateInRightValue; -import org.apache.shardingsphere.sql.parser.binder.type.WhereAvailable; import org.apache.shardingsphere.underlying.common.exception.ShardingSphereException; import java.util.Collection; diff --git a/shadow-core/shadow-core-rewrite/src/test/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowConditionEngineTest.java b/shadow-core/shadow-core-common/src/test/java/org/apache/shardingsphere/shadow/common/condition/ShadowConditionEngineTest.java similarity index 95% rename from shadow-core/shadow-core-rewrite/src/test/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowConditionEngineTest.java rename to shadow-core/shadow-core-common/src/test/java/org/apache/shardingsphere/shadow/common/condition/ShadowConditionEngineTest.java index c3dad7e6ec31bdaa5b7cd14420e0c3d60a3ffffc..e764b8f81f8802daddc9385d72cf86798d92ebbf 100644 --- a/shadow-core/shadow-core-rewrite/src/test/java/org/apache/shardingsphere/shadow/rewrite/condition/ShadowConditionEngineTest.java +++ b/shadow-core/shadow-core-common/src/test/java/org/apache/shardingsphere/shadow/common/condition/ShadowConditionEngineTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.shardingsphere.shadow.rewrite.condition; +package org.apache.shardingsphere.shadow.common.condition; import org.apache.shardingsphere.sql.parser.sql.segment.dml.expr.simple.LiteralExpressionSegment; import org.junit.Test; diff --git a/shadow-core/shadow-core-rewrite/pom.xml b/shadow-core/shadow-core-rewrite/pom.xml index 6ac2fcb885386f24ebd7ffd3dcff943ed93900c5..cb79dd58d9947b95cc656ff083614c6014684a72 100644 --- a/shadow-core/shadow-core-rewrite/pom.xml +++ b/shadow-core/shadow-core-rewrite/pom.xml @@ -34,6 +34,11 @@ sharding-core-common ${project.version} + + org.apache.shardingsphere + shadow-core-common + ${project.version} + org.apache.shardingsphere shardingsphere-sql-parser-binder diff --git a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/ShadowJudgementEngine.java b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/ShadowJudgementEngine.java index 2f6ff32a8806f91defd1ee6a01775845b06790e6..7bb02015223282ea38b5b58199cdd726332ccf04 100644 --- a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/ShadowJudgementEngine.java +++ b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/ShadowJudgementEngine.java @@ -20,6 +20,7 @@ package org.apache.shardingsphere.shadow.rewrite.judgement; /** * Shadow judgement engine. */ +//TODO need to remove public interface ShadowJudgementEngine { /** diff --git a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/PreparedJudgementEngine.java b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/PreparedJudgementEngine.java index 536fabb610304c5790c9bee3399aeea798c18ad5..a0f92ad44dc66d4a71b57c7673e74d7794f1de9c 100644 --- a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/PreparedJudgementEngine.java +++ b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/PreparedJudgementEngine.java @@ -38,6 +38,7 @@ import java.util.Optional; /** * Shadow judgement engine for prepared. */ +//TODO need to remove @RequiredArgsConstructor public final class PreparedJudgementEngine implements ShadowJudgementEngine { diff --git a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/SimpleJudgementEngine.java b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/SimpleJudgementEngine.java index 0f625ffdabe7ae48b1d6c72d5df954228fe5d965..cf24773e8628d34cb9a3dbd55921df84e24fbe6d 100644 --- a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/SimpleJudgementEngine.java +++ b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/judgement/impl/SimpleJudgementEngine.java @@ -19,8 +19,8 @@ package org.apache.shardingsphere.shadow.rewrite.judgement.impl; import lombok.RequiredArgsConstructor; import org.apache.shardingsphere.core.rule.ShadowRule; -import org.apache.shardingsphere.shadow.rewrite.condition.ShadowCondition; -import org.apache.shardingsphere.shadow.rewrite.condition.ShadowConditionEngine; +import org.apache.shardingsphere.shadow.common.condition.ShadowCondition; +import org.apache.shardingsphere.shadow.common.condition.ShadowConditionEngine; import org.apache.shardingsphere.shadow.rewrite.judgement.ShadowJudgementEngine; import org.apache.shardingsphere.sql.parser.binder.segment.insert.values.InsertValueContext; import org.apache.shardingsphere.sql.parser.binder.statement.SQLStatementContext; @@ -35,6 +35,7 @@ import java.util.Optional; /** * Simple shadow judgement engine. */ +//TODO need to remove @RequiredArgsConstructor public final class SimpleJudgementEngine implements ShadowJudgementEngine { diff --git a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/parameter/impl/ShadowPredicateParameterRewriter.java b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/parameter/impl/ShadowPredicateParameterRewriter.java index fd02204f3b068335bc8592a4953a51a881521167..9819e22e0aa8f269c6a466217a2a04511fbe566a 100644 --- a/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/parameter/impl/ShadowPredicateParameterRewriter.java +++ b/shadow-core/shadow-core-rewrite/src/main/java/org/apache/shardingsphere/shadow/rewrite/parameter/impl/ShadowPredicateParameterRewriter.java @@ -17,7 +17,7 @@ package org.apache.shardingsphere.shadow.rewrite.parameter.impl; -import org.apache.shardingsphere.shadow.rewrite.condition.ShadowConditionEngine; +import org.apache.shardingsphere.shadow.common.condition.ShadowConditionEngine; import org.apache.shardingsphere.shadow.rewrite.parameter.ShadowParameterRewriter; import org.apache.shardingsphere.sql.parser.binder.statement.SQLStatementContext; import org.apache.shardingsphere.underlying.rewrite.parameter.builder.ParameterBuilder; diff --git a/shadow-core/shadow-core-route/pom.xml b/shadow-core/shadow-core-route/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..3d2db538c5dbc76331cd2d3a6e3e94b6cd6a0c11 --- /dev/null +++ b/shadow-core/shadow-core-route/pom.xml @@ -0,0 +1,51 @@ + + + + + + shadow-core + org.apache.shardingsphere + 5.0.0-RC1-SNAPSHOT + + 4.0.0 + + shadow-core-route + ${project.artifactId} + + + + org.apache.shardingsphere + sharding-core-common + ${project.version} + + + + org.apache.shardingsphere + shadow-core-common + ${project.version} + + + + org.apache.shardingsphere + shardingsphere-route + ${project.version} + + + diff --git a/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/ShadowDataSourceRouter.java b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/ShadowDataSourceRouter.java new file mode 100644 index 0000000000000000000000000000000000000000..a0a81b158f531650c16af8affd0f16e657e9e897 --- /dev/null +++ b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/ShadowDataSourceRouter.java @@ -0,0 +1,43 @@ +/* + * 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.shadow.route.engine; + +/** + * Route decorator for shadow. + */ +public interface ShadowDataSourceRouter { + + /** + * Judge whether SQL is shadow. + * + * @return SQL is shadow or not + */ + boolean isShadowSQL(); + + /** + * Judge whether field is shadow. + * @param value the field + * @return field is shadow or not + */ + default boolean isShadowField(final Object value) { + return (value instanceof Boolean && (Boolean) value) + || (value instanceof Integer && 1 == (Integer) value) + || (value instanceof String && Boolean.parseBoolean((String) value)); + } + +} diff --git a/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/ShadowRouteDecorator.java b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/ShadowRouteDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..ade55f0aaa13b2d4b92678edd64aa484a6aee29c --- /dev/null +++ b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/ShadowRouteDecorator.java @@ -0,0 +1,107 @@ +/* + * 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.shadow.route.engine; + +import org.apache.shardingsphere.core.rule.ShadowRule; +import org.apache.shardingsphere.shadow.route.engine.impl.PreparedShadowDataSourceRouter; +import org.apache.shardingsphere.shadow.route.engine.impl.SimpleShadowDataSourceRouter; +import org.apache.shardingsphere.sql.parser.binder.statement.SQLStatementContext; +import org.apache.shardingsphere.sql.parser.sql.statement.SQLStatement; +import org.apache.shardingsphere.sql.parser.sql.statement.dml.DMLStatement; +import org.apache.shardingsphere.underlying.common.config.properties.ConfigurationProperties; +import org.apache.shardingsphere.underlying.common.metadata.ShardingSphereMetaData; +import org.apache.shardingsphere.underlying.route.context.RouteContext; +import org.apache.shardingsphere.underlying.route.context.RouteMapper; +import org.apache.shardingsphere.underlying.route.context.RouteResult; +import org.apache.shardingsphere.underlying.route.context.RouteUnit; +import org.apache.shardingsphere.underlying.route.decorator.RouteDecorator; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Route decorator for shadow. + */ +public final class ShadowRouteDecorator implements RouteDecorator { + + @Override + public RouteContext decorate(final RouteContext routeContext, final ShardingSphereMetaData metaData, final ShadowRule shadowRule, final ConfigurationProperties properties) { + return routeContext.getRouteResult().getRouteUnits().isEmpty() ? getRouteContext(routeContext, shadowRule) : getRouteContextWithRouteResult(routeContext, shadowRule); + } + + private RouteContext getRouteContext(final RouteContext routeContext, final ShadowRule shadowRule) { + SQLStatementContext sqlStatementContext = routeContext.getSqlStatementContext(); + SQLStatement sqlStatement = sqlStatementContext.getSqlStatement(); + RouteResult routeResult = new RouteResult(); + List parameters = routeContext.getParameters(); + if (!(sqlStatement instanceof DMLStatement)) { + shadowRule.getShadowMappings().forEach((k, v) -> { + routeResult.getRouteUnits().add(new RouteUnit(new RouteMapper(k, k), Collections.emptyList())); + routeResult.getRouteUnits().add(new RouteUnit(new RouteMapper(v, v), Collections.emptyList())); + }); + return new RouteContext(sqlStatementContext, parameters, routeResult); + } + if (isShadowSQL(routeContext, shadowRule)) { + shadowRule.getShadowMappings().keySet().forEach(each -> routeResult.getRouteUnits().add(new RouteUnit(new RouteMapper(each, each), Collections.emptyList()))); + } else { + shadowRule.getShadowMappings().values().forEach(each -> routeResult.getRouteUnits().add(new RouteUnit(new RouteMapper(each, each), Collections.emptyList()))); + } + return new RouteContext(sqlStatementContext, parameters, routeResult); + } + + private RouteContext getRouteContextWithRouteResult(final RouteContext routeContext, final ShadowRule shadowRule) { + SQLStatement sqlStatement = routeContext.getSqlStatementContext().getSqlStatement(); + Collection toBeAdded = new LinkedList<>(); + if (!(sqlStatement instanceof DMLStatement)) { + for (RouteUnit each : routeContext.getRouteResult().getRouteUnits()) { + String shadowDataSourceName = shadowRule.getShadowMappings().get(each.getDataSourceMapper().getActualName()); + toBeAdded.add(new RouteUnit(new RouteMapper(each.getDataSourceMapper().getLogicName(), shadowDataSourceName), Collections.emptyList())); + } + routeContext.getRouteResult().getRouteUnits().addAll(toBeAdded); + return routeContext; + } + if (isShadowSQL(routeContext, shadowRule)) { + for (RouteUnit each : routeContext.getRouteResult().getRouteUnits()) { + routeContext.getRouteResult().getRouteUnits().remove(each); + String shadowDataSourceName = shadowRule.getShadowMappings().get(each.getDataSourceMapper().getActualName()); + routeContext.getRouteResult().getRouteUnits().add(new RouteUnit(new RouteMapper(each.getDataSourceMapper().getLogicName(), shadowDataSourceName), Collections.emptyList())); + } + } + return routeContext; + } + + private boolean isShadowSQL(final RouteContext routeContext, final ShadowRule shadowRule) { + List parameters = routeContext.getParameters(); + SQLStatementContext sqlStatementContext = routeContext.getSqlStatementContext(); + ShadowDataSourceRouter shadowDataSourceRouter = parameters == null ? new SimpleShadowDataSourceRouter(shadowRule, sqlStatementContext) + : new PreparedShadowDataSourceRouter(shadowRule, sqlStatementContext, parameters); + return shadowDataSourceRouter.isShadowSQL(); + } + + @Override + public int getOrder() { + return 20; + } + + @Override + public Class getTypeClass() { + return ShadowRule.class; + } +} diff --git a/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/impl/PreparedShadowDataSourceRouter.java b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/impl/PreparedShadowDataSourceRouter.java new file mode 100644 index 0000000000000000000000000000000000000000..cda3d767e8ed9e441c8616e615e24c59c9c3a6b3 --- /dev/null +++ b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/impl/PreparedShadowDataSourceRouter.java @@ -0,0 +1,91 @@ +/* + * 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.shadow.route.engine.impl; + +import com.google.common.base.Preconditions; +import lombok.RequiredArgsConstructor; +import org.apache.shardingsphere.core.rule.ShadowRule; +import org.apache.shardingsphere.shadow.route.engine.ShadowDataSourceRouter; +import org.apache.shardingsphere.sql.parser.binder.statement.SQLStatementContext; +import org.apache.shardingsphere.sql.parser.binder.statement.dml.InsertStatementContext; +import org.apache.shardingsphere.sql.parser.binder.type.WhereAvailable; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.column.ColumnSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.expr.simple.ParameterMarkerExpressionSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.AndPredicate; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.PredicateSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.WhereSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.value.PredicateCompareRightValue; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * Shadow judgement engine for prepared. + */ +@RequiredArgsConstructor +public final class PreparedShadowDataSourceRouter implements ShadowDataSourceRouter { + + private final ShadowRule shadowRule; + + private final SQLStatementContext sqlStatementContext; + + private final List parameters; + + @Override + public boolean isShadowSQL() { + if (sqlStatementContext instanceof InsertStatementContext) { + Collection columnSegments = (((InsertStatementContext) sqlStatementContext).getSqlStatement()).getColumns(); + int count = 0; + for (ColumnSegment each : columnSegments) { + if (each.getIdentifier().getValue().equals(shadowRule.getColumn())) { + Object value = parameters.get(count); + return isShadowField(value); + } + count++; + } + return false; + } + if (sqlStatementContext instanceof WhereAvailable) { + Optional whereSegment = ((WhereAvailable) sqlStatementContext).getWhere(); + if (!whereSegment.isPresent()) { + return false; + } + Collection andPredicates = whereSegment.get().getAndPredicates(); + for (AndPredicate andPredicate : andPredicates) { + if (judgePredicateSegments(andPredicate.getPredicates())) { + return true; + } + } + } + return false; + } + + private boolean judgePredicateSegments(final Collection predicates) { + for (PredicateSegment each : predicates) { + if (each.getColumn().getIdentifier().getValue().equals(shadowRule.getColumn())) { + Preconditions.checkArgument(each.getRightValue() instanceof PredicateCompareRightValue, "must be PredicateCompareRightValue"); + PredicateCompareRightValue rightValue = (PredicateCompareRightValue) each.getRightValue(); + int parameterMarkerIndex = ((ParameterMarkerExpressionSegment) rightValue.getExpression()).getParameterMarkerIndex(); + final Object value = parameters.get(parameterMarkerIndex); + return isShadowField(value); + } + } + return false; + } +} diff --git a/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/impl/SimpleShadowDataSourceRouter.java b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/impl/SimpleShadowDataSourceRouter.java new file mode 100644 index 0000000000000000000000000000000000000000..3cf0bc93a85683c8e2f9f3bbde31bf20075b25c8 --- /dev/null +++ b/shadow-core/shadow-core-route/src/main/java/org/apache/shardingsphere/shadow/route/engine/impl/SimpleShadowDataSourceRouter.java @@ -0,0 +1,78 @@ +/* + * 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.shadow.route.engine.impl; + +import lombok.RequiredArgsConstructor; +import org.apache.shardingsphere.core.rule.ShadowRule; +import org.apache.shardingsphere.shadow.common.condition.ShadowCondition; +import org.apache.shardingsphere.shadow.common.condition.ShadowConditionEngine; +import org.apache.shardingsphere.shadow.route.engine.ShadowDataSourceRouter; +import org.apache.shardingsphere.sql.parser.binder.segment.insert.values.InsertValueContext; +import org.apache.shardingsphere.sql.parser.binder.statement.SQLStatementContext; +import org.apache.shardingsphere.sql.parser.binder.statement.dml.InsertStatementContext; +import org.apache.shardingsphere.sql.parser.binder.type.WhereAvailable; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +/** + * Simple shadow judgement engine. + */ +@RequiredArgsConstructor +public final class SimpleShadowDataSourceRouter implements ShadowDataSourceRouter { + + private final ShadowRule shadowRule; + + private final SQLStatementContext sqlStatementContext; + + @Override + public boolean isShadowSQL() { + if (sqlStatementContext instanceof InsertStatementContext) { + for (InsertValueContext each : ((InsertStatementContext) sqlStatementContext).getInsertValueContexts()) { + if (judgeShadowSqlForInsert(each, (InsertStatementContext) sqlStatementContext)) { + return true; + } + } + return false; + } + if (sqlStatementContext instanceof WhereAvailable) { + Optional shadowCondition = new ShadowConditionEngine(shadowRule).createShadowCondition(sqlStatementContext); + if (!shadowCondition.isPresent()) { + return false; + } + List values = shadowCondition.get().getValues(Collections.emptyList()); + return values.size() != 0 && isShadowField(values.get(0)); + } + return false; + } + + private boolean judgeShadowSqlForInsert(final InsertValueContext insertValueContext, final InsertStatementContext insertStatementContext) { + Iterator descendingColumnNames = insertStatementContext.getDescendingColumnNames(); + while (descendingColumnNames.hasNext()) { + String columnName = descendingColumnNames.next(); + if (shadowRule.getColumn().equals(columnName)) { + int columnIndex = insertStatementContext.getColumnNames().indexOf(columnName); + Object value = insertValueContext.getValue(columnIndex); + return isShadowField(value); + } + } + return false; + } +} diff --git a/shadow-core/shadow-core-route/src/main/resources/META-INF/services/org.apache.shardingsphere.underlying.route.decorator.RouteDecorator b/shadow-core/shadow-core-route/src/main/resources/META-INF/services/org.apache.shardingsphere.underlying.route.decorator.RouteDecorator new file mode 100644 index 0000000000000000000000000000000000000000..feb7f064a96559eeade1f682c48a4122716e022e --- /dev/null +++ b/shadow-core/shadow-core-route/src/main/resources/META-INF/services/org.apache.shardingsphere.underlying.route.decorator.RouteDecorator @@ -0,0 +1,18 @@ +# +# 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. +# + +org.apache.shardingsphere.shadow.route.engine.ShadowRouteDecorator diff --git a/shadow-core/shadow-core-route/src/test/java/org/apache/shardingsphere/shadow/route/engine/impl/PreparedShadowDataSourceRouterTest.java b/shadow-core/shadow-core-route/src/test/java/org/apache/shardingsphere/shadow/route/engine/impl/PreparedShadowDataSourceRouterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..06a6c1343d19b1d33a25ee52e2df63fb3e3033e2 --- /dev/null +++ b/shadow-core/shadow-core-route/src/test/java/org/apache/shardingsphere/shadow/route/engine/impl/PreparedShadowDataSourceRouterTest.java @@ -0,0 +1,55 @@ +/* + * 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.shadow.route.engine.impl; + +import org.apache.shardingsphere.api.config.shadow.ShadowRuleConfiguration; +import org.apache.shardingsphere.core.rule.ShadowRule; +import org.apache.shardingsphere.sql.parser.binder.metadata.schema.SchemaMetaData; +import org.apache.shardingsphere.sql.parser.binder.statement.dml.InsertStatementContext; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.column.ColumnSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.column.InsertColumnsSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.generic.table.SimpleTableSegment; +import org.apache.shardingsphere.sql.parser.sql.statement.dml.InsertStatement; +import org.apache.shardingsphere.sql.parser.sql.value.identifier.IdentifierValue; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PreparedShadowDataSourceRouterTest { + + @Test + public void isShadowSQL() { + SchemaMetaData schemaMetaData = mock(SchemaMetaData.class); + when(schemaMetaData.getAllColumnNames("tbl")).thenReturn(Arrays.asList("id", "name", "shadow")); + ShadowRuleConfiguration shadowRuleConfiguration = new ShadowRuleConfiguration(); + shadowRuleConfiguration.setColumn("shadow"); + ShadowRule shadowRule = new ShadowRule(shadowRuleConfiguration); + InsertStatement insertStatement = new InsertStatement(); + insertStatement.setTable(new SimpleTableSegment(0, 0, new IdentifierValue("tbl"))); + InsertColumnsSegment insertColumnsSegment = new InsertColumnsSegment(0, 0, + Arrays.asList(new ColumnSegment(0, 0, new IdentifierValue("id")), new ColumnSegment(0, 0, new IdentifierValue("name")), new ColumnSegment(0, 0, new IdentifierValue("shadow")))); + insertStatement.setInsertColumns(insertColumnsSegment); + InsertStatementContext insertStatementContext = new InsertStatementContext(schemaMetaData, Arrays.asList(1, "Tom", 2, "Jerry", 3, true), insertStatement); + PreparedShadowDataSourceRouter preparedShadowDataSourceRouter = new PreparedShadowDataSourceRouter(shadowRule, insertStatementContext, Arrays.asList(1, "Tom", true)); + Assert.assertTrue("should be shadow", preparedShadowDataSourceRouter.isShadowSQL()); + } +} diff --git a/shadow-core/shadow-core-route/src/test/java/org/apache/shardingsphere/shadow/route/engine/impl/SimpleShadowDataSourceRouterTest.java b/shadow-core/shadow-core-route/src/test/java/org/apache/shardingsphere/shadow/route/engine/impl/SimpleShadowDataSourceRouterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ac59bc4e0b4a72d865f39d909d5b27e60640e7fd --- /dev/null +++ b/shadow-core/shadow-core-route/src/test/java/org/apache/shardingsphere/shadow/route/engine/impl/SimpleShadowDataSourceRouterTest.java @@ -0,0 +1,107 @@ +/* + * 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.shadow.route.engine.impl; + +import org.apache.shardingsphere.api.config.shadow.ShadowRuleConfiguration; +import org.apache.shardingsphere.core.rule.ShadowRule; +import org.apache.shardingsphere.sql.parser.binder.metadata.schema.SchemaMetaData; +import org.apache.shardingsphere.sql.parser.binder.statement.dml.InsertStatementContext; +import org.apache.shardingsphere.sql.parser.binder.statement.dml.SelectStatementContext; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.assignment.InsertValuesSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.column.ColumnSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.column.InsertColumnsSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.expr.simple.LiteralExpressionSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.item.ExpressionProjectionSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.item.ProjectionsSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.AndPredicate; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.PredicateSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.WhereSegment; +import org.apache.shardingsphere.sql.parser.sql.segment.dml.predicate.value.PredicateCompareRightValue; +import org.apache.shardingsphere.sql.parser.sql.segment.generic.table.SimpleTableSegment; +import org.apache.shardingsphere.sql.parser.sql.statement.dml.InsertStatement; +import org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement; +import org.apache.shardingsphere.sql.parser.sql.value.identifier.IdentifierValue; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SimpleShadowDataSourceRouterTest { + + private SchemaMetaData schemaMetaData; + + private ShadowRule shadowRule; + + @Before + public void setUp() { + schemaMetaData = mock(SchemaMetaData.class); + when(schemaMetaData.getAllColumnNames("tbl")).thenReturn(Arrays.asList("id", "name", "shadow")); + ShadowRuleConfiguration shadowRuleConfiguration = new ShadowRuleConfiguration(); + shadowRuleConfiguration.setColumn("shadow"); + shadowRule = new ShadowRule(shadowRuleConfiguration); + } + + @Test + public void judgeForInsert() { + InsertStatement insertStatement = new InsertStatement(); + insertStatement.setTable(new SimpleTableSegment(0, 0, new IdentifierValue("tbl"))); + InsertColumnsSegment insertColumnsSegment = new InsertColumnsSegment(0, 0, + Arrays.asList(new ColumnSegment(0, 0, new IdentifierValue("id")), new ColumnSegment(0, 0, new IdentifierValue("name")), new ColumnSegment(0, 0, new IdentifierValue("shadow")))); + insertStatement.setInsertColumns(insertColumnsSegment); + insertStatement.getValues().addAll(Collections.singletonList(new InsertValuesSegment( + 0, 0, Arrays.asList(new LiteralExpressionSegment(0, 0, 1), new LiteralExpressionSegment(0, 0, "name"), new LiteralExpressionSegment(0, 0, true))))); + InsertStatementContext insertStatementContext = new InsertStatementContext(schemaMetaData, Collections.emptyList(), insertStatement); + SimpleShadowDataSourceRouter simpleShadowDataSourceRouter = new SimpleShadowDataSourceRouter(shadowRule, insertStatementContext); + Assert.assertTrue("should be shadow", simpleShadowDataSourceRouter.isShadowSQL()); + insertStatement.getValues().clear(); + insertStatement.getValues().addAll(Collections.singletonList( + new InsertValuesSegment(0, 0, Arrays.asList(new LiteralExpressionSegment(0, 0, 1), new LiteralExpressionSegment(0, 0, "name"), new LiteralExpressionSegment(0, 0, false))))); + insertStatementContext = new InsertStatementContext(schemaMetaData, Collections.emptyList(), insertStatement); + simpleShadowDataSourceRouter = new SimpleShadowDataSourceRouter(shadowRule, insertStatementContext); + Assert.assertFalse("should not be shadow", simpleShadowDataSourceRouter.isShadowSQL()); + } + + @Test + public void judgeForWhereSegment() { + SelectStatement selectStatement = new SelectStatement(); + WhereSegment whereSegment = new WhereSegment(0, 0); + AndPredicate andPredicate = new AndPredicate(); + andPredicate.getPredicates().addAll(Collections.singletonList( + new PredicateSegment(0, 0, new ColumnSegment(0, 0, new IdentifierValue("shadow")), new PredicateCompareRightValue("=", new LiteralExpressionSegment(0, 0, true))))); + whereSegment.getAndPredicates().addAll(Collections.singletonList(andPredicate)); + selectStatement.setWhere(whereSegment); + ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0); + projectionsSegment.setDistinctRow(true); + projectionsSegment.getProjections().addAll(Collections.singletonList(new ExpressionProjectionSegment(0, 0, "true"))); + selectStatement.setProjections(projectionsSegment); + SelectStatementContext selectStatementContext = new SelectStatementContext(schemaMetaData, "", Collections.emptyList(), selectStatement); + SimpleShadowDataSourceRouter simpleShadowDataSourceRouter = new SimpleShadowDataSourceRouter(shadowRule, selectStatementContext); + Assert.assertTrue("should be shadow", simpleShadowDataSourceRouter.isShadowSQL()); + andPredicate.getPredicates().clear(); + andPredicate.getPredicates().addAll(Collections.singletonList( + new PredicateSegment(0, 0, new ColumnSegment(0, 0, new IdentifierValue("shadow")), new PredicateCompareRightValue("=", new LiteralExpressionSegment(0, 0, false))))); + projectionsSegment.getProjections().clear(); + projectionsSegment.getProjections().addAll(Collections.singletonList(new ExpressionProjectionSegment(0, 0, "false"))); + Assert.assertFalse("should not be shadow", simpleShadowDataSourceRouter.isShadowSQL()); + } +} diff --git a/sharding-core/sharding-core-common/src/main/java/org/apache/shardingsphere/core/rule/ShadowRuleBuilder.java b/sharding-core/sharding-core-common/src/main/java/org/apache/shardingsphere/core/rule/ShadowRuleBuilder.java index 4ea16ed38d8cfc03eb7e2b9225f9b942f3eed440..20c8c0bae12e600ca2786aff2ad90a3727d4cc19 100644 --- a/sharding-core/sharding-core-common/src/main/java/org/apache/shardingsphere/core/rule/ShadowRuleBuilder.java +++ b/sharding-core/sharding-core-common/src/main/java/org/apache/shardingsphere/core/rule/ShadowRuleBuilder.java @@ -34,7 +34,7 @@ public final class ShadowRuleBuilder implements ShardingSphereRuleBuilder