/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* 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.cinchapi.concourse.server.storage.db;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import com.cinchapi.concourse.annotate.DoNotInvoke;
import com.cinchapi.concourse.annotate.PackagePrivate;
import com.cinchapi.concourse.server.model.PrimaryKey;
import com.cinchapi.concourse.server.model.Text;
import com.cinchapi.concourse.server.model.Value;
import com.cinchapi.concourse.thrift.Operator;
import com.cinchapi.concourse.util.MultimapViews;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* A grouping of data for efficient indirect queries.
* <p>
* Each SecondaryRecord maps a value to a set of PrimaryKeys and provides an
* interface for querying.
* </p>
*
* @author Jeff Nelson
*/
@ThreadSafe
@PackagePrivate
final class SecondaryRecord extends BrowsableRecord<Text, Value, PrimaryKey> {
/**
* DO NOT INVOKE. Use {@link Record#createSearchRecord(Text)} or
* {@link Record#createSecondaryRecordPartial(Text, Value)} instead.
*
* @param locator
* @param key
*/
@DoNotInvoke
@PackagePrivate
SecondaryRecord(Text locator, @Nullable Value key) {
super(locator, key);
}
/**
* Return the PrimaryKeys that satisfied {@code operator} in relation to the
* specified {@code values} at {@code timestamp}.
*
* @param timestamp
* @param operator
* @param values
* @return the Set of PrimaryKeys that match the query
*/
public Set<PrimaryKey> find(long timestamp, Operator operator,
Value... values) {
return explore(true, timestamp, operator, values).keySet();
}
/**
* Return the PrimaryKeys that <em>currently</em> satisfy {@code operator}
* in relation to the specified {@code values}.
*
* @param operator
* @param values
* @return they Set of PrimaryKeys that match the query
*/
public Set<PrimaryKey> find(Operator operator, Value... values) {
return explore(false, 0, operator, values).keySet();
}
/**
* Explore this record and return a mapping from PrimaryKey to the Values
* that cause the corresponding records to satisfy {@code operator} in
* relation to the specified {@code values} at {@code timestamp}.
*
* @param timestamp
* @param operator
* @param values
* @return the relevant data that causes the matching records to satisfy the
* criteria
*/
public Map<PrimaryKey, Set<Value>> explore(long timestamp,
Operator operator, Value... values) {
return explore(true, timestamp, operator, values);
}
/**
* Explore this record and return a mapping from PrimaryKey to the
* Values that cause the corresponding records to satisfy {@code operator}
* in relation to the specified {@code values}.
*
* @param operator
* @param values
* @return the relevant data that causes the matching records to satisfy the
* criteria
*/
public Map<PrimaryKey, Set<Value>> explore(Operator operator,
Value... values) {
return explore(false, 0, operator, values);
}
@Override
protected Map<Value, Set<PrimaryKey>> mapType() {
return Maps.newTreeMap(Value.Sorter.INSTANCE);
}
/**
* Explore this record and return a mapping from PrimaryKey to the Values
* that cause the corresponding records to satisfy {@code operator} in
* relation to the specified {@code values} (and at the specified
* {@code timestamp} if {@code historical} is {@code true}).
*
* @param historical - if {@code true} query the history, otherwise query
* the current state
* @param timestamp - this value is ignored if {@code historical} is
* {@code false}, otherwise this value is the historical
* timestamp at which to query the field
* @param operator
* @param values
* @return the relevant data that causes the matching records to satisfy the
* criteria
*/
private Map<PrimaryKey, Set<Value>> explore(boolean historical,
long timestamp, Operator operator, Value... values) { /* Authorized */
read.lock();
try {
Map<PrimaryKey, Set<Value>> data = Maps.newHashMap();
Value value = values[0];
if(operator == Operator.EQUALS) {
for (PrimaryKey record : historical ? get(value, timestamp)
: get(value)) {
MultimapViews.put(data, record, value);
}
}
else if(operator == Operator.NOT_EQUALS) {
for (Value stored : historical ? history.keySet() : present
.keySet()) {
if(!value.equals(stored)) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.GREATER_THAN) {
for (Value stored : historical ? history.keySet()
: ((NavigableSet<Value>) present.keySet()).tailSet(
value, false)) {
if(!historical || stored.compareTo(value) > 0) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.GREATER_THAN_OR_EQUALS) {
for (Value stored : historical ? history.keySet()
: ((NavigableSet<Value>) present.keySet()).tailSet(
value, true)) {
if(!historical || stored.compareTo(value) >= 0) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.LESS_THAN) {
for (Value stored : historical ? history.keySet()
: ((NavigableSet<Value>) present.keySet()).headSet(
value, false)) {
if(!historical || stored.compareTo(value) < 0) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.LESS_THAN_OR_EQUALS) {
for (Value stored : historical ? history.keySet()
: ((NavigableSet<Value>) present.keySet()).headSet(
value, true)) {
if(!historical || stored.compareTo(value) <= 0) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.BETWEEN) {
Preconditions.checkArgument(values.length > 1);
Value value2 = values[1];
for (Value stored : historical ? history.keySet()
: ((NavigableSet<Value>) present.keySet()).subSet(
value, true, value2, false)) {
if(!historical
|| (stored.compareTo(value) >= 0 && stored
.compareTo(value2) < 0)) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.REGEX) {
Pattern p = Pattern.compile(value.getObject().toString());
for (Value stored : historical ? history.keySet() : present
.keySet()) {
Matcher m = p.matcher(stored.getObject().toString());
if(m.matches()) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else if(operator == Operator.NOT_REGEX) {
Pattern p = Pattern.compile(value.getObject().toString());
for (Value stored : historical ? history.keySet() : present
.keySet()) {
Matcher m = p.matcher(stored.getObject().toString());
if(!m.matches()) {
for (PrimaryKey record : historical ? get(stored,
timestamp) : get(stored)) {
MultimapViews.put(data, record, stored);
}
}
}
}
else {
throw new UnsupportedOperationException();
}
return data;
}
finally {
read.unlock();
}
}
}