/*
* Copyright 2014 Cask Data, 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 co.cask.cdap.api.dataset.lib.cube;
import co.cask.cdap.api.annotation.Beta;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Defines a query to perform on {@link Cube} data.
* </p>
* Another way to think about the query is to map it to the following statement::
* <pre><code>
* SELECT count('read.ops') {@literal <<} measure name and aggregation function
* FROM aggregation1.1min_resolution {@literal <<} aggregation and resolution
* GROUP BY dataset, {@literal <<} groupByDimensions
* WHERE namespace='ns1' AND app='myApp' AND program='myFlow' AND {@literal <<} dimensionValues
* ts>=1423370200 AND ts{@literal <}1423398198 {@literal <<} startTs and endTs
* LIMIT 100 {@literal <<} limit
*
* </code>
* </pre>
* See also {@link Cube#query(CubeQuery)}.
*/
@Beta
public final class CubeQuery {
// null value means auto-choose aggregation based on query todo: auto-choosing may be error prone, remove it?
@Nullable
private final String aggregation;
private final long startTs;
private final long endTs;
private final int resolution;
private final int limit;
private final Map<String, AggregationFunction> measurements;
private final Map<String, String> dimensionValues;
private final List<String> groupByDimensions;
private final Interpolator interpolator;
/**
* Creates {@link CubeQuery} with given parameters.
* @param aggregation (optional) aggregation name to query in; if {@code null}, the aggregation will be auto-selected
* based on rest of query parameters
* @param startTs start (inclusive) of the time range to query
* @param endTs end (exclusive) of the time range to query
* @param resolution resolution of the aggregation to query in
* @param limit max number of returned data points
* @param measurements map of measure name, measure type to query for, empty map means "all measures"
* @param dimensionValues dimension values to filter by
* @param groupByDimensions dimensions to group by
* @param interpolator {@link Interpolator} to use
*/
public CubeQuery(@Nullable String aggregation,
long startTs, long endTs, int resolution, int limit,
Map<String, AggregationFunction> measurements,
Map<String, String> dimensionValues, List<String> groupByDimensions,
@Nullable Interpolator interpolator) {
this.aggregation = aggregation;
this.startTs = startTs;
this.endTs = endTs;
this.resolution = resolution;
this.limit = limit;
this.measurements = measurements;
this.dimensionValues = Collections.unmodifiableMap(new HashMap<>(dimensionValues));
this.groupByDimensions = Collections.unmodifiableList(new ArrayList<>(groupByDimensions));
this.interpolator = interpolator;
}
@Nullable
public String getAggregation() {
return aggregation;
}
public long getStartTs() {
return startTs;
}
public long getEndTs() {
return endTs;
}
public int getResolution() {
return resolution;
}
public Map<String, AggregationFunction> getMeasurements() {
return measurements;
}
public Map<String, String> getDimensionValues() {
return dimensionValues;
}
public List<String> getGroupByDimensions() {
return groupByDimensions;
}
// todo: push down limit support to Cube
public int getLimit() {
return limit;
}
public Interpolator getInterpolator() {
return interpolator;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("CubeQuery");
sb.append("{aggregation=").append(aggregation);
sb.append(", startTs=").append(startTs);
sb.append(", endTs=").append(endTs);
sb.append(", resolution=").append(resolution);
sb.append(", limit=").append(limit);
sb.append(", measurements=").append(measurements);
sb.append(", dimensionValues=").append(dimensionValues);
sb.append(", groupByDimensions=").append(groupByDimensions);
sb.append(", interpolator=").append(interpolator);
sb.append('}');
return sb.toString();
}
/**
* @return {@link Builder} to build {@link CubeQuery}.
*/
public static Builder builder() {
return new Builder();
}
/**
* Builds {@link CubeQuery}.
*/
public static final class Builder {
private String aggregation;
private long startTs;
private long endTs;
private int resolution;
private int limit;
private Map<String, AggregationFunction> measurements = new HashMap<>();
private Map<String, String> dimensionValues = new HashMap<>();
private List<String> groupByDimensions = new ArrayList<>();
private Interpolator interpolator;
/**
* @return builder for configuring {@link CubeQuery}
*/
public Select select() {
return new Select();
}
/**
* @return instance of {@link CubeQuery}
*/
private CubeQuery build() {
return new CubeQuery(aggregation, startTs, endTs, resolution, limit,
measurements, dimensionValues, groupByDimensions, interpolator);
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class Select {
private Select() {}
/**
* Adds measurement to be included in selection of {@link CubeQuery}.
* @param name name of the measurement
* @param aggFunc function to be used if aggregation of measurement value is needed
* @return builder for configuring {@link CubeQuery}
*/
public Measurement measurement(String name, AggregationFunction aggFunc) {
Builder.this.measurements.put(name, aggFunc);
return new Measurement();
}
/**
* Adds measurements to be included in selection of {@link CubeQuery}.
* @param measurements map of measurement name, agg function to include
* @return builder for configuring {@link CubeQuery}
*/
public Measurement measurements(Map<String, AggregationFunction> measurements) {
Builder.this.measurements.putAll(measurements);
return new Measurement();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class Measurement {
private Measurement() {}
/**
* Adds measurement to be included in selection of {@link CubeQuery}.
* @param name name of the measurement
* @param aggFunc function to be used if aggregation of measurement value is needed
* @return builder for configuring {@link CubeQuery}
*/
public Measurement measurement(String name, AggregationFunction aggFunc) {
Builder.this.measurements.put(name, aggFunc);
return this;
}
/**
* Adds measurements to be included in selection of {@link CubeQuery}.
* @param measurements map of measurement name, agg function to include
* @return builder for configuring {@link CubeQuery}
*/
public Measurement measurements(Map<String, AggregationFunction> measurements) {
Builder.this.measurements.putAll(measurements);
return new Measurement();
}
/**
* Defines aggregation view to query from.
* @param aggregation name of the aggregation view
* @return builder for configuring {@link CubeQuery}
*/
public From from(String aggregation) {
Builder.this.aggregation = aggregation;
return new From();
}
/**
* Sets aggregation view to query from to be auto-selected based on other parameters of the query.
* @return builder for configuring {@link CubeQuery}
*/
public From from() {
Builder.this.aggregation = null;
return new From();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class From {
private From() {}
/**
* Sets resolution for {@link CubeQuery}.
* @param amount amount of units
* @param timeUnit unit type
* @return builder for configuring {@link CubeQuery}
*/
public Where resolution(long amount, TimeUnit timeUnit) {
Builder.this.resolution = (int) timeUnit.convert(amount, TimeUnit.SECONDS);
return new Where();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class Where {
private Where() {}
/**
* @return builder for configuring {@link CubeQuery}
*/
public Dimension where() {
return new Dimension();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class Dimension {
private Dimension() {}
/**
* Adds dimension value to filter by.
* @param name name of dimension
* @param value value of dimension
* @return builder for configuring {@link CubeQuery}
*/
public Dimension dimension(String name, String value) {
Builder.this.dimensionValues.put(name, value);
return this;
}
/**
* Adds dimension values to filter by.
* @param dimValues dimension name, dimension value pairs to filter by
* @return builder for configuring {@link CubeQuery}
*/
public Dimension dimensions(Map<String, String> dimValues) {
Builder.this.dimensionValues.putAll(dimValues);
return this;
}
/**
* Defines time range for {@link CubeQuery}.
* @param startTsInSec start time inclusive (epoch in seconds)
* @param endTsInSec end time exclusive (epoch in seconds)
* @return builder for configuring {@link CubeQuery}
*/
public GroupBy timeRange(long startTsInSec, long endTsInSec) {
Builder.this.startTs = startTsInSec;
Builder.this.endTs = endTsInSec;
return new GroupBy();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class GroupBy {
private GroupBy() {}
/**
* @return builder for configuring {@link CubeQuery}
*/
public GroupByDimension groupBy() {
return new GroupByDimension();
}
/**
* Sets a limit on returned data points per time series
* @param limit limit value
* @return builder for configuring {@link CubeQuery}
*/
public Limit limit(int limit) {
Builder.this.limit = limit;
return new Limit();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class GroupByDimension {
private GroupByDimension() {}
/**
* Adds dimension to use for grouping results into time series.
* @param name name of the dimension
* @return builder for configuring {@link CubeQuery}
*/
public GroupByDimension dimension(String name) {
Builder.this.groupByDimensions.add(name);
return this;
}
/**
* Adds dimensions to use for grouping results into time series.
* @param names names of the dimensions
* @return builder for configuring {@link CubeQuery}
*/
public GroupByDimension dimensions(List<String> names) {
Builder.this.groupByDimensions.addAll(names);
return this;
}
/**
* Sets a limit on returned data points per time series
* @param limit limit value
* @return builder for configuring {@link CubeQuery}
*/
public Limit limit(int limit) {
Builder.this.limit = limit;
return new Limit();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class Limit {
private Limit() {}
/**
* Sets {@link Interpolator} to use for {@link CubeQuery}.
* @param interpolator interpolator to use
* @return builder for configuring {@link CubeQuery}
*/
public Build interpolator(Interpolator interpolator) {
Builder.this.interpolator = interpolator;
return new Build();
}
/**
* @return {@link CubeQuery}
*/
public CubeQuery build() {
return Builder.this.build();
}
}
/**
* Builder for configuring {@link CubeQuery}.
*/
public final class Build {
private Build() {}
/**
* @return {@link CubeQuery}
*/
public CubeQuery build() {
return Builder.this.build();
}
}
}
}