/* * Licensed 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 com.facebook.presto.raptor.metadata; import com.facebook.presto.raptor.RaptorColumnHandle; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.predicate.Domain; import com.facebook.presto.spi.predicate.Range; import com.facebook.presto.spi.predicate.Ranges; import com.facebook.presto.spi.predicate.TupleDomain; import com.facebook.presto.spi.type.Type; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; import java.sql.JDBCType; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; import java.util.Map.Entry; import java.util.StringJoiner; import static com.facebook.presto.raptor.metadata.DatabaseShardManager.maxColumn; import static com.facebook.presto.raptor.metadata.DatabaseShardManager.minColumn; import static com.facebook.presto.raptor.storage.ColumnIndexStatsUtils.jdbcType; import static com.facebook.presto.raptor.storage.ShardStats.truncateIndexValue; import static com.facebook.presto.raptor.util.UuidUtil.uuidStringToBytes; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; import static java.lang.String.format; import static java.util.Objects.requireNonNull; class ShardPredicate { private final String predicate; private final List<JDBCType> types; private final List<Object> values; private ShardPredicate(String predicate, List<JDBCType> types, List<Object> values) { this.predicate = requireNonNull(predicate, "predicate is null"); this.types = ImmutableList.copyOf(requireNonNull(types, "types is null")); this.values = ImmutableList.copyOf(requireNonNull(values, "values is null")); checkArgument(types.size() == values.size(), "types and values sizes do not match"); } public String getPredicate() { return predicate; } public void bind(PreparedStatement statement) throws SQLException { for (int i = 0; i < types.size(); i++) { JDBCType type = types.get(i); Object value = values.get(i); bindValue(statement, type, value, i + 1); } } @Override public String toString() { return toStringHelper(this) .addValue(predicate) .toString(); } public static ShardPredicate create(TupleDomain<RaptorColumnHandle> tupleDomain, boolean bucketed) { StringJoiner predicate = new StringJoiner(" AND ").setEmptyValue("true"); ImmutableList.Builder<JDBCType> types = ImmutableList.builder(); ImmutableList.Builder<Object> values = ImmutableList.builder(); for (Entry<RaptorColumnHandle, Domain> entry : tupleDomain.getDomains().get().entrySet()) { Domain domain = entry.getValue(); if (domain.isNullAllowed() || domain.isAll()) { continue; } RaptorColumnHandle handle = entry.getKey(); Type type = handle.getColumnType(); JDBCType jdbcType = jdbcType(type); if (jdbcType == null) { continue; } if (handle.isShardUuid()) { predicate.add(createShardPredicate(types, values, domain, jdbcType)); continue; } if (!domain.getType().isOrderable()) { continue; } Ranges ranges = domain.getValues().getRanges(); // TODO: support multiple ranges if (ranges.getRangeCount() != 1) { continue; } Range range = getOnlyElement(ranges.getOrderedRanges()); Object minValue = null; Object maxValue = null; if (range.isSingleValue()) { minValue = range.getSingleValue(); maxValue = range.getSingleValue(); } else { if (!range.getLow().isLowerUnbounded()) { minValue = range.getLow().getValue(); } if (!range.getHigh().isUpperUnbounded()) { maxValue = range.getHigh().getValue(); } } String min; String max; if (handle.isBucketNumber()) { if (!bucketed) { predicate.add("false"); continue; } min = "bucket_number"; max = "bucket_number"; } else { min = minColumn(handle.getColumnId()); max = maxColumn(handle.getColumnId()); } if (minValue != null) { predicate.add(format("(%s >= ? OR %s IS NULL)", max, max)); types.add(jdbcType); values.add(minValue); } if (maxValue != null) { predicate.add(format("(%s <= ? OR %s IS NULL)", min, min)); types.add(jdbcType); values.add(maxValue); } } return new ShardPredicate(predicate.toString(), types.build(), values.build()); } private static String createShardPredicate(ImmutableList.Builder<JDBCType> types, ImmutableList.Builder<Object> values, Domain domain, JDBCType jdbcType) { List<Range> ranges = domain.getValues().getRanges().getOrderedRanges(); // only apply predicates if all ranges are single values if (ranges.isEmpty() || !ranges.stream().allMatch(Range::isSingleValue)) { return "true"; } ImmutableList.Builder<Object> valuesBuilder = ImmutableList.builder(); ImmutableList.Builder<JDBCType> typesBuilder = ImmutableList.builder(); StringJoiner rangePredicate = new StringJoiner(" OR "); for (Range range : ranges) { Slice uuidText = (Slice) range.getSingleValue(); try { Slice uuidBytes = uuidStringToBytes(uuidText); typesBuilder.add(jdbcType); valuesBuilder.add(uuidBytes); } catch (IllegalArgumentException e) { return "true"; } rangePredicate.add("shard_uuid = ?"); } types.addAll(typesBuilder.build()); values.addAll(valuesBuilder.build()); return rangePredicate.toString(); } @VisibleForTesting protected List<JDBCType> getTypes() { return types; } @VisibleForTesting protected List<Object> getValues() { return values; } public static void bindValue(PreparedStatement statement, JDBCType type, Object value, int index) throws SQLException { if (value == null) { statement.setNull(index, type.getVendorTypeNumber()); return; } switch (type) { case BOOLEAN: statement.setBoolean(index, (boolean) value); return; case INTEGER: statement.setInt(index, ((Number) value).intValue()); return; case BIGINT: statement.setLong(index, ((Number) value).longValue()); return; case DOUBLE: statement.setDouble(index, ((Number) value).doubleValue()); return; case VARBINARY: statement.setBytes(index, truncateIndexValue((Slice) value).getBytes()); return; } throw new PrestoException(GENERIC_INTERNAL_ERROR, "Unhandled type: " + type); } }