if (whereClause == LiteralExpression.FALSE_EXPRESSION) {
context.setScanRanges(ScanRanges.NOTHING);
return null;
}
// TODO: Single table for now
PTable table = context.getResolver().getTables().get(0).getTable();
KeyExpressionVisitor visitor = new KeyExpressionVisitor(context, table);
// TODO:: When we only have one where clause, the keySlots returns as a single slot object,
// instead of an array of slots for the corresponding column. Change the behavior so it
// becomes consistent.
KeyExpressionVisitor.KeySlots keySlots = whereClause.accept(visitor);
if (keySlots == null) {
context.setScanRanges(ScanRanges.EVERYTHING);
return whereClause;
}
// If a parameter is bound to null (as will be the case for calculating ResultSetMetaData and
// ParameterMetaData), this will be the case. It can also happen for an equality comparison
// for unequal lengths.
if (keySlots == KeyExpressionVisitor.DEGENERATE_KEY_PARTS) {
context.setScanRanges(ScanRanges.NOTHING);
return null;
}
if (extractNodes == null) {
extractNodes = new HashSet<Expression>(table.getPKColumns().size());
}
// We're fully qualified if all columns except the salt column are specified
int fullyQualifiedColumnCount = table.getPKColumns().size() - (table.getBucketNum() == null ? 0 : 1);
int pkPos = table.getBucketNum() == null ? -1 : 0;
LinkedList<List<KeyRange>> cnf = new LinkedList<List<KeyRange>>();
RowKeySchema schema = table.getRowKeySchema();
boolean forcedSkipScan = statement.getHint().hasHint(Hint.SKIP_SCAN);
boolean forcedRangeScan = statement.getHint().hasHint(Hint.RANGE_SCAN);
boolean hasUnboundedRange = false;
boolean hasAnyRange = false;
// Concat byte arrays of literals to form scan start key
for (KeyExpressionVisitor.KeySlot slot : keySlots) {
// If the position of the pk columns in the query skips any part of the row k
// then we have to handle in the next phase through a key filter.
// If the slot is null this means we have no entry for this pk position.
if (slot == null || slot.getKeyRanges().isEmpty()) {
if (!forcedSkipScan) break;
continue;
}
if (slot.getPKPosition() != pkPos + 1) {
if (!forcedSkipScan) break;
for (int i=pkPos + 1; i < slot.getPKPosition(); i++) {
cnf.add(Collections.singletonList(KeyRange.EVERYTHING_RANGE));
}
}
// We support (a,b) IN ((1,2),(3,4), so in this case we switch to a flattened schema
if (fullyQualifiedColumnCount > 1 && slot.getPKSpan() == fullyQualifiedColumnCount && slot.getKeyRanges().size() > 1) {
schema = SchemaUtil.VAR_BINARY_SCHEMA;
}
KeyPart keyPart = slot.getKeyPart();
pkPos = slot.getPKPosition();
List<KeyRange> keyRanges = slot.getKeyRanges();
cnf.add(keyRanges);
for (KeyRange range : keyRanges) {
hasUnboundedRange |= range.isUnbound();
}
// Will be null in cases for which only part of the expression was factored out here
// to set the start/end key. An example would be <column> LIKE 'foo%bar' where we can
// set the start key to 'foo' but still need to match the regex at filter time.
// Don't extract expressions if we're forcing a range scan and we've already come
// across a range for a prior slot. The reason is that we have an inexact range after
// that, so must filter on the remaining conditions (see issue #467).
if (!forcedRangeScan || !hasAnyRange) {
List<Expression> nodesToExtract = keyPart.getExtractNodes();
extractNodes.addAll(nodesToExtract);
}
// Stop building start/stop key once we encounter a non single key range.
if (hasUnboundedRange && !forcedSkipScan) {
// TODO: when stats are available, we may want to continue this loop if the
// cardinality of this slot is low. We could potentially even continue this
// loop in the absence of a range for a key slot.
break;
}
hasAnyRange |= keyRanges.size() > 1 || (keyRanges.size() == 1 && !keyRanges.get(0).isSingleKey());
}
List<List<KeyRange>> ranges = cnf;
if (table.getBucketNum() != null) {
if (!cnf.isEmpty()) {
// If we have all single keys, we can optimize by adding the salt byte up front
if (schema == SchemaUtil.VAR_BINARY_SCHEMA) {
ranges = SaltingUtil.setSaltByte(ranges, table.getBucketNum());
} else if (isAllSingleRowScan(cnf, table)) {
cnf.addFirst(SALT_PLACEHOLDER);
ranges = SaltingUtil.flattenRanges(cnf, table.getRowKeySchema(), table.getBucketNum());
schema = SchemaUtil.VAR_BINARY_SCHEMA;
} else {
cnf.addFirst(SaltingUtil.generateAllSaltingRanges(table.getBucketNum()));
}
}
}
context.setScanRanges(
ScanRanges.create(ranges, schema, statement.getHint().hasHint(Hint.RANGE_SCAN)),