Skip to content

Commit e2e3c56

Browse files
committed
Route INSERT VALUES and WHERE-clause sharding-key casts on the PostgreSQL/openGauss cast result via a dedicated cast evaluator so that string-to-int, lossy numeric and boolean casts pick the database-visible value while overflow and parse failures fall through
1 parent f570d09 commit e2e3c56

7 files changed

Lines changed: 643 additions & 99 deletions

File tree

features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/route/engine/condition/engine/InsertClauseShardingConditionEngine.java

Lines changed: 59 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -43,51 +43,26 @@
4343
import org.apache.shardingsphere.timeservice.core.rule.TimestampServiceRule;
4444

4545
import java.util.ArrayList;
46-
import java.util.Arrays;
4746
import java.util.Collection;
4847
import java.util.Collections;
49-
import java.util.Date;
50-
import java.util.HashSet;
5148
import java.util.Iterator;
5249
import java.util.LinkedHashSet;
5350
import java.util.LinkedList;
5451
import java.util.List;
55-
import java.util.Locale;
5652
import java.util.Optional;
57-
import java.util.Set;
5853

5954
/**
6055
* Sharding condition engine for insert clause.
6156
*/
6257
@RequiredArgsConstructor
6358
public final class InsertClauseShardingConditionEngine {
6459

65-
private static final Set<String> NUMERIC_CAST_TARGETS = unmodifiableUpperCaseSet(
66-
"INT", "INTEGER", "INT2", "INT4", "INT8", "BIGINT", "SMALLINT", "TINYINT",
67-
"NUMERIC", "DECIMAL", "DEC", "NUMBER",
68-
"FLOAT", "FLOAT4", "FLOAT8", "REAL", "DOUBLE", "DOUBLE PRECISION",
69-
"SERIAL", "BIGSERIAL", "SMALLSERIAL");
70-
71-
private static final Set<String> TEXT_CAST_TARGETS = unmodifiableUpperCaseSet(
72-
"TEXT", "VARCHAR", "CHARACTER VARYING", "CHAR", "CHARACTER", "BPCHAR", "NAME", "NVARCHAR", "NCHAR");
73-
74-
private static final Set<String> BOOLEAN_CAST_TARGETS = unmodifiableUpperCaseSet("BOOL", "BOOLEAN");
75-
76-
private static final Set<String> TEMPORAL_CAST_TARGETS = unmodifiableUpperCaseSet(
77-
"DATE", "TIME", "TIMETZ", "TIMESTAMP", "TIMESTAMPTZ", "TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP WITH TIME ZONE");
78-
7960
private final ShardingSphereDatabase database;
8061

8162
private final ShardingRule rule;
8263

8364
private final TimestampServiceRule timestampServiceRule;
8465

85-
private static Set<String> unmodifiableUpperCaseSet(final String... values) {
86-
Set<String> result = new HashSet<>(values.length, 1F);
87-
result.addAll(Arrays.asList(values));
88-
return Collections.unmodifiableSet(result);
89-
}
90-
9166
/**
9267
* Create sharding conditions.
9368
*
@@ -159,92 +134,84 @@ private ShardingCondition createShardingCondition(final String tableName, final
159134
if (!shardingColumn.isPresent()) {
160135
continue;
161136
}
162-
ExpressionSegment value = unwrapTypeCastForRouting(each, params);
163-
if (value instanceof SimpleExpressionSegment) {
164-
List<Integer> parameterMarkerIndexes = value instanceof ParameterMarkerExpressionSegment
165-
? Collections.singletonList(((ParameterMarkerExpressionSegment) value).getParameterMarkerIndex())
166-
: Collections.emptyList();
167-
Object shardingValue = getShardingValue((SimpleExpressionSegment) value, params);
168-
result.getValues().add(new ListShardingConditionValue<>(shardingColumn.get(), tableName, Collections.singletonList(shardingValue),
169-
parameterMarkerIndexes));
170-
} else if (value instanceof CommonExpressionSegment) {
171-
generateShardingCondition((CommonExpressionSegment) value, result, shardingColumn.get(), tableName);
172-
} else if (ExpressionConditionUtils.isNowExpression(value)) {
173-
result.getValues().add(new ListShardingConditionValue<>(shardingColumn.get(), tableName, Collections.singletonList(timestampServiceRule.getTimestamp())));
174-
}
137+
appendCastRoutedValueIfPresent(each, params, shardingColumn.get(), tableName, result)
138+
.orElseGet(() -> appendRoutedValueWithoutCast(each, params, shardingColumn.get(), tableName, result));
175139
}
176140
return result;
177141
}
178142

179143
/**
180-
* Unwrap nested {@link TypeCastExpression} layers for routing only when the cast is semantically safe, i.e. the bound
181-
* Java value belongs to the same category as the outermost cast target type. An {@code expression::type} cast on a
182-
* sharding key then reuses the underlying parameter marker or literal for routing without coercion.
144+
* Route a {@code TypeCastExpression} sharding-key value by the database-visible cast result instead of the raw bound
145+
* Java value, so that PostgreSQL/openGauss runtime semantics for {@code expression::type} (string-to-integer,
146+
* lossy-numeric-to-integer, boolean conversions, etc.) are preserved by routing.
183147
*
184-
* <p>Unwrap is refused, returning the original cast (which falls through the {@code instanceof} chain and adds no
185-
* sharding condition), in any of the following cases:</p>
186-
* <ul>
187-
* <li>the innermost expression is not a {@link ParameterMarkerExpressionSegment} or {@link LiteralExpressionSegment}
188-
* (e.g. {@link org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubqueryExpressionSegment}),
189-
* which would otherwise hit a {@link ClassCastException} at {@code getShardingValue};</li>
190-
* <li>the bound Java value is {@code null};</li>
191-
* <li>the bound Java value's class does not match the outermost cast target category, e.g. binding a {@code String}
192-
* for {@code ?::int4}. PostgreSQL would evaluate the SQL value as an integer while raw-value routing would still see
193-
* the {@code String}, so refusing to unwrap avoids routing to the wrong shard.</li>
194-
* </ul>
148+
* <p>Returns {@link Optional#empty()} when the segment is not a cast or when {@link PostgreSQLCastEvaluator} cannot
149+
* resolve the cast (unsupported target, parse failure, overflow, or a non-marker / non-literal inner expression such
150+
* as a {@code SubqueryExpressionSegment}). In that case the caller falls back to the no-cast path.</p>
195151
*
196-
* @param expressionSegment expression segment that may be wrapped in one or more {@link TypeCastExpression} layers
197-
* @param params bound parameter values used to look up the runtime Java type of a parameter marker
198-
* @return the innermost {@link ParameterMarkerExpressionSegment} or {@link LiteralExpressionSegment} when the cast is
199-
* safe; otherwise the original argument
152+
* @param expressionSegment expression segment to route
153+
* @param params bound parameter values for parameter markers
154+
* @param shardingColumn sharding column name to attach the routed value to
155+
* @param tableName sharding table name to attach the routed value to
156+
* @param condition sharding condition to append the routed value to
157+
* @return {@link Optional#of(Object)} with a non-null placeholder when a cast-derived sharding condition was
158+
* appended, otherwise {@link Optional#empty()}
200159
*/
201-
private static ExpressionSegment unwrapTypeCastForRouting(final ExpressionSegment expressionSegment, final List<Object> params) {
160+
private Optional<Object> appendCastRoutedValueIfPresent(final ExpressionSegment expressionSegment, final List<Object> params,
161+
final String shardingColumn, final String tableName, final ShardingCondition condition) {
202162
if (!(expressionSegment instanceof TypeCastExpression)) {
203-
return expressionSegment;
163+
return Optional.empty();
204164
}
205-
TypeCastExpression outermost = (TypeCastExpression) expressionSegment;
206-
ExpressionSegment inner = outermost;
207-
while (inner instanceof TypeCastExpression) {
208-
inner = ((TypeCastExpression) inner).getExpression();
165+
List<String> castTargetTypesOuterToInner = new ArrayList<>();
166+
ExpressionSegment innermost = expressionSegment;
167+
while (innermost instanceof TypeCastExpression) {
168+
castTargetTypesOuterToInner.add(((TypeCastExpression) innermost).getDataType());
169+
innermost = ((TypeCastExpression) innermost).getExpression();
209170
}
210-
if (!(inner instanceof ParameterMarkerExpressionSegment) && !(inner instanceof LiteralExpressionSegment)) {
211-
return expressionSegment;
171+
if (!(innermost instanceof ParameterMarkerExpressionSegment) && !(innermost instanceof LiteralExpressionSegment)) {
172+
return Optional.empty();
212173
}
213-
Object routingValue;
214-
if (inner instanceof ParameterMarkerExpressionSegment) {
215-
int parameterMarkerIndex = ((ParameterMarkerExpressionSegment) inner).getParameterMarkerIndex();
174+
Object value;
175+
List<Integer> parameterMarkerIndexes;
176+
if (innermost instanceof ParameterMarkerExpressionSegment) {
177+
int parameterMarkerIndex = ((ParameterMarkerExpressionSegment) innermost).getParameterMarkerIndex();
216178
if (parameterMarkerIndex < 0 || parameterMarkerIndex >= params.size()) {
217-
return expressionSegment;
179+
return Optional.empty();
218180
}
219-
routingValue = params.get(parameterMarkerIndex);
181+
value = params.get(parameterMarkerIndex);
182+
parameterMarkerIndexes = Collections.singletonList(parameterMarkerIndex);
220183
} else {
221-
routingValue = ((LiteralExpressionSegment) inner).getLiterals();
184+
value = ((LiteralExpressionSegment) innermost).getLiterals();
185+
parameterMarkerIndexes = Collections.emptyList();
222186
}
223-
return isCastSafeForRouting(routingValue, outermost.getDataType()) ? inner : expressionSegment;
224-
}
225-
226-
private static boolean isCastSafeForRouting(final Object value, final String castTargetType) {
227-
if (null == value || null == castTargetType) {
228-
return false;
229-
}
230-
String normalized = castTargetType.toUpperCase(Locale.ROOT).trim();
231-
int parenIndex = normalized.indexOf('(');
232-
if (parenIndex > 0) {
233-
normalized = normalized.substring(0, parenIndex).trim();
234-
}
235-
if (value instanceof Number) {
236-
return NUMERIC_CAST_TARGETS.contains(normalized);
237-
}
238-
if (value instanceof String) {
239-
return TEXT_CAST_TARGETS.contains(normalized);
187+
for (int index = castTargetTypesOuterToInner.size() - 1; index >= 0; index--) {
188+
Optional<Comparable<?>> casted = PostgreSQLCastEvaluator.evaluate(value, castTargetTypesOuterToInner.get(index));
189+
if (!casted.isPresent()) {
190+
return Optional.empty();
191+
}
192+
value = casted.get();
240193
}
241-
if (value instanceof Boolean) {
242-
return BOOLEAN_CAST_TARGETS.contains(normalized);
194+
if (!(value instanceof Comparable)) {
195+
return Optional.empty();
243196
}
244-
if (value instanceof Date) {
245-
return TEMPORAL_CAST_TARGETS.contains(normalized);
197+
condition.getValues().add(new ListShardingConditionValue<>(shardingColumn, tableName, Collections.singletonList((Comparable<?>) value), parameterMarkerIndexes));
198+
return Optional.of(Boolean.TRUE);
199+
}
200+
201+
private Object appendRoutedValueWithoutCast(final ExpressionSegment expressionSegment, final List<Object> params,
202+
final String shardingColumn, final String tableName, final ShardingCondition condition) {
203+
if (expressionSegment instanceof SimpleExpressionSegment) {
204+
List<Integer> parameterMarkerIndexes = expressionSegment instanceof ParameterMarkerExpressionSegment
205+
? Collections.singletonList(((ParameterMarkerExpressionSegment) expressionSegment).getParameterMarkerIndex())
206+
: Collections.emptyList();
207+
Object shardingValue = getShardingValue((SimpleExpressionSegment) expressionSegment, params);
208+
condition.getValues().add(new ListShardingConditionValue<>(shardingColumn, tableName, Collections.singletonList(shardingValue), parameterMarkerIndexes));
209+
} else if (expressionSegment instanceof CommonExpressionSegment) {
210+
generateShardingCondition((CommonExpressionSegment) expressionSegment, condition, shardingColumn, tableName);
211+
} else if (ExpressionConditionUtils.isNowExpression(expressionSegment)) {
212+
condition.getValues().add(new ListShardingConditionValue<>(shardingColumn, tableName, Collections.singletonList(timestampServiceRule.getTimestamp())));
246213
}
247-
return false;
214+
return Boolean.TRUE;
248215
}
249216

250217
private void generateShardingCondition(final CommonExpressionSegment expressionSegment, final ShardingCondition condition, final String shardingColumn, final String tableName) {

0 commit comments

Comments
 (0)