/*
* #%L
* server
* %%
* Copyright (C) 2012 - 2015 valdasraps
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package lt.emasina.resthub.server.query;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
import lombok.Getter;
import lombok.ToString;
import lt.emasina.resthub.exception.QueryException;
import lt.emasina.resthub.factory.TableBuilder;
import lt.emasina.resthub.model.MdColumn;
import lt.emasina.resthub.model.MdTable;
import lt.emasina.resthub.server.factory.MetadataFactory;
import lt.emasina.resthub.server.factory.ResourceFactory;
import lt.emasina.resthub.server.parser.check.CheckSelectParser;
import lt.emasina.resthub.server.parser.check.SubSelectDef;
import lt.emasina.resthub.server.parser.update.UpdateSelectParser;
import lt.emasina.resthub.server.table.ServerTable;
import lt.emasina.resthub.server.table.TableId;
import net.sf.jsqlparser.statement.select.Select;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.restlet.data.Reference;
import com.google.common.collect.Lists;
import com.google.inject.assistedinject.Assisted;
import lt.emasina.resthub.server.ServerAppConfig;
/**
* QueryMd
* @author valdo
*/
@Getter
@ToString(of = {"qid"})
public class Query {
@Inject
private ServerAppConfig cfg;
private final QueryId qid;
private final String sql;
private final Date createTime = new Date();
private String connectionName;
private Integer hitCount = 0;
private Integer cacheTime = MdTable.ETERNAL_CACHE_TIME;
private Integer rowsLimit = MdTable.MAX_ROWS_LIMIT;
private Integer timeOut = 0;
private final List<MdColumn> columns = new ArrayList<>();
private final List<QueryParameter> parameters = new ArrayList<>();
private final Set<TableId> tables = new HashSet<>();
private final QueryStats stats = new QueryStats();
@Inject
public Query(@Assisted QueryId qid, ResourceFactory rf, TableBuilder tf) throws QueryException, Exception {
this.qid = qid;
CheckSelectParser checkParser = rf.createSelectParser((SubSelectDef) null);
// Check syntax
qid.getSelect().getSelectBody().accept(checkParser);
Boolean hitCountWasZero = false;
// Collect aggregate info from tables
for (ServerTable t: checkParser.getTables()) {
this.tables.add(t.getId());
this.connectionName = t.getTable().getConnectionName();
Integer ct = t.getTable().getCacheTime();
if (ct != MdTable.ETERNAL_CACHE_TIME && this.cacheTime != MdTable.SKIP_CACHE_TIME &&
!Objects.equals(ct, this.cacheTime)) {
if (this.cacheTime == MdTable.ETERNAL_CACHE_TIME || ct < this.cacheTime) {
this.cacheTime = t.getTable().getCacheTime();
}
}
if (t.getTable().getRowsLimit() < this.rowsLimit) {
this.rowsLimit = t.getTable().getRowsLimit();
}
if (!hitCountWasZero) {
if (t.getTable().getHitCount() == 0) {
hitCountWasZero = true;
} else {
this.hitCount += t.getTable().getHitCount();
}
}
if (this.timeOut < t.getTable().getTimeout()) {
this.timeOut = t.getTable().getTimeout();
}
}
if (hitCountWasZero) {
this.hitCount = 0;
} else {
this.hitCount /= checkParser.getTables().size();
}
// Create exec version
UpdateSelectParser execParser = new UpdateSelectParser(checkParser);
Select select = qid.getSelect();
select.getSelectBody().accept(execParser);
this.parameters.addAll(execParser.getParameters().values());
String tsql = select.toString();
// Collect columns
tf.collectColumns(this.connectionName, tsql, columns);
StringBuilder cols = new StringBuilder();
cols.append("select ");
boolean firstPass = Boolean.TRUE;
for (MdColumn c: columns) {
if (!firstPass) {
cols.append(",");
}
cols.append("\"").append(c.getName()).append("\"");
firstPass = Boolean.FALSE;
}
cols.append(" from (").append(tsql).append(")");
this.sql = cols.toString();
}
public boolean isEternal() {
return cacheTime == MdTable.ETERNAL_CACHE_TIME;
}
public int getCacheTime() {
return isEternal() ? MdTable.SKIP_CACHE_TIME : cacheTime;
}
public int getCacheTimeInMilliseconds() {
return isEternal() ? MdTable.SKIP_CACHE_TIME : cacheTime * 1000;
}
public boolean isCacheable() {
return cacheTime != MdTable.SKIP_CACHE_TIME;
}
public URL getReference(Reference ref, Integer perpage, Integer page, String query) {
return getReference(ref, "page", query, perpage, page, "data");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public URL getReference(Reference ref, String query, Object... parts) {
List list = Lists.newArrayList("query", qid.getId());
list.addAll(Arrays.asList(parts));
return cfg.getReference(ref, query, list);
}
public JSONObject getJSON(Reference ref, boolean verbose) throws JSONException {
JSONObject ret = new JSONObject();
ret.put("connection", connectionName);
ret.put("query", qid.getSql());
if (verbose) {
ret.put("sql", sql);
ret.put("cacheTime", cacheTime);
ret.put("md5", qid.getMd5());
ret.put("md5Raw", qid.getMd5Raw());
ret.put("id", qid.getId());
ret.put("rowsLimit", rowsLimit);
ret.put("hitCount", hitCount);
ret.put("timeOut", timeOut);
if (isCacheable()) {
ret.put("eternal", isEternal());
ret.put("cache", getReference(ref, null, "cache"));
}
ret.put("stats", stats.getJSON());
}
ret.put("columns", getColumnsJSON());
JSONArray pars = getParamsJSON(verbose);
if (pars.length() > 0) {
ret.put("parameters", pars);
}
return ret;
}
public JSONArray getColumnsJSON() throws JSONException {
JSONArray cols = new JSONArray();
for (MdColumn c: columns) {
JSONObject col = new JSONObject();
col.put("name", c.getName());
col.put("type", c.getType());
col.put("cname", c.getCName());
col.put("jname", c.getJName());
cols.put(col);
}
return cols;
}
public JSONArray getParamsJSON(boolean verbose) throws JSONException {
JSONArray pars = new JSONArray();
for (QueryParameter p: parameters) {
JSONObject par = new JSONObject();
par.put("name", p.getName());
par.put("type", p.getType());
par.put("array", p.getArray());
if (verbose) {
par.put("sqlName", p.getSqlName());
}
par.put("metadata", MetadataFactory.mapToJSONObject(p.getMetadata()));
pars.put(par);
}
return pars;
}
}