package org.rakam.plugin; import com.facebook.presto.sql.parser.SqlParser; import com.facebook.presto.sql.tree.Query; import com.facebook.presto.sql.tree.QuerySpecification; import com.facebook.presto.sql.tree.Statement; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.rakam.server.http.annotations.ApiParam; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Map; import static com.google.common.base.Preconditions.*; import static java.time.temporal.ChronoUnit.MILLIS; public class MaterializedView { private final static SqlParser SQL_PARSER = new SqlParser(); @JsonProperty("table_name") public final String tableName; @JsonProperty("query") public final String query; @JsonProperty("incremental") public final boolean incremental; @JsonProperty("real_time") public final boolean realTime; @JsonProperty("update_interval") public final Duration updateInterval; @JsonProperty("last_update") public transient Instant lastUpdate; @JsonProperty("name") public String name; @JsonProperty("options") public final Map<String, Object> options; @JsonCreator public MaterializedView(@ApiParam(value = "table_name", description="The table name of the materialized view that can be used when querying") String tableName, @ApiParam(value = "name", description="Name") String name, @ApiParam(value = "query", description="The sql query that will be executed and materialized") String query, @ApiParam(value = "update_interval", required = false) Duration updateInterval, @ApiParam(value = "incremental", required = false) Boolean incremental, @ApiParam(value = "real_time", required = false) Boolean realTime, @ApiParam(value = "options", required = false) Map<String, Object> options) { this.tableName = checkNotNull(tableName, "table_name is required"); this.name = checkNotNull(name, "name is required"); this.query = checkNotNull(query, "query is required"); this.incremental = incremental == null ? false : incremental; this.realTime = realTime == null ? false : realTime; this.updateInterval = updateInterval; this.options = options; validateQuery(); } public void validateQuery() { Statement query; synchronized (SQL_PARSER) { query = SQL_PARSER.createStatement(this.query); } checkState(query instanceof Query, "Expression is not query"); checkState((!((Query) query).getLimit().isPresent()), "The query of materialized view can't contain LIMIT statement"); checkState(!(((QuerySpecification) ((Query) query).getQueryBody()).getLimit().isPresent()), "The query of materialized view can't contain LIMIT statement"); } public boolean needsUpdate(Clock clock) { return lastUpdate == null || lastUpdate.until(clock.instant(), MILLIS) > updateInterval.toMillis(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MaterializedView that = (MaterializedView) o; if (incremental != that.incremental) { return false; } if (realTime != that.realTime) { return false; } if (!tableName.equals(that.tableName)) { return false; } if (!query.equals(that.query)) { return false; } if (updateInterval != null ? !updateInterval.equals(that.updateInterval) : that.updateInterval != null) { return false; } return options != null ? options.equals(that.options) : that.options == null; } @Override public int hashCode() { int result = tableName.hashCode(); result = 31 * result + query.hashCode(); result = 31 * result + (incremental ? 1 : 0); result = 31 * result + (realTime ? 1 : 0); result = 31 * result + (updateInterval != null ? updateInterval.hashCode() : 0); result = 31 * result + (options != null ? options.hashCode() : 0); return result; } }