|
43 | 43 | import org.apache.shardingsphere.timeservice.core.rule.TimestampServiceRule; |
44 | 44 |
|
45 | 45 | import java.util.ArrayList; |
46 | | -import java.util.Arrays; |
47 | 46 | import java.util.Collection; |
48 | 47 | import java.util.Collections; |
49 | | -import java.util.Date; |
50 | | -import java.util.HashSet; |
51 | 48 | import java.util.Iterator; |
52 | 49 | import java.util.LinkedHashSet; |
53 | 50 | import java.util.LinkedList; |
54 | 51 | import java.util.List; |
55 | | -import java.util.Locale; |
56 | 52 | import java.util.Optional; |
57 | | -import java.util.Set; |
58 | 53 |
|
59 | 54 | /** |
60 | 55 | * Sharding condition engine for insert clause. |
61 | 56 | */ |
62 | 57 | @RequiredArgsConstructor |
63 | 58 | public final class InsertClauseShardingConditionEngine { |
64 | 59 |
|
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 | | - |
79 | 60 | private final ShardingSphereDatabase database; |
80 | 61 |
|
81 | 62 | private final ShardingRule rule; |
82 | 63 |
|
83 | 64 | private final TimestampServiceRule timestampServiceRule; |
84 | 65 |
|
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 | | - |
91 | 66 | /** |
92 | 67 | * Create sharding conditions. |
93 | 68 | * |
@@ -159,92 +134,84 @@ private ShardingCondition createShardingCondition(final String tableName, final |
159 | 134 | if (!shardingColumn.isPresent()) { |
160 | 135 | continue; |
161 | 136 | } |
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)); |
175 | 139 | } |
176 | 140 | return result; |
177 | 141 | } |
178 | 142 |
|
179 | 143 | /** |
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. |
183 | 147 | * |
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> |
195 | 151 | * |
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()} |
200 | 159 | */ |
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) { |
202 | 162 | if (!(expressionSegment instanceof TypeCastExpression)) { |
203 | | - return expressionSegment; |
| 163 | + return Optional.empty(); |
204 | 164 | } |
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(); |
209 | 170 | } |
210 | | - if (!(inner instanceof ParameterMarkerExpressionSegment) && !(inner instanceof LiteralExpressionSegment)) { |
211 | | - return expressionSegment; |
| 171 | + if (!(innermost instanceof ParameterMarkerExpressionSegment) && !(innermost instanceof LiteralExpressionSegment)) { |
| 172 | + return Optional.empty(); |
212 | 173 | } |
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(); |
216 | 178 | if (parameterMarkerIndex < 0 || parameterMarkerIndex >= params.size()) { |
217 | | - return expressionSegment; |
| 179 | + return Optional.empty(); |
218 | 180 | } |
219 | | - routingValue = params.get(parameterMarkerIndex); |
| 181 | + value = params.get(parameterMarkerIndex); |
| 182 | + parameterMarkerIndexes = Collections.singletonList(parameterMarkerIndex); |
220 | 183 | } else { |
221 | | - routingValue = ((LiteralExpressionSegment) inner).getLiterals(); |
| 184 | + value = ((LiteralExpressionSegment) innermost).getLiterals(); |
| 185 | + parameterMarkerIndexes = Collections.emptyList(); |
222 | 186 | } |
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(); |
240 | 193 | } |
241 | | - if (value instanceof Boolean) { |
242 | | - return BOOLEAN_CAST_TARGETS.contains(normalized); |
| 194 | + if (!(value instanceof Comparable)) { |
| 195 | + return Optional.empty(); |
243 | 196 | } |
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()))); |
246 | 213 | } |
247 | | - return false; |
| 214 | + return Boolean.TRUE; |
248 | 215 | } |
249 | 216 |
|
250 | 217 | private void generateShardingCondition(final CommonExpressionSegment expressionSegment, final ShardingCondition condition, final String shardingColumn, final String tableName) { |
|
0 commit comments