package org.gcube.accounting.analytics.persistence.postgresql; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.gcube.accounting.analytics.Filter; import org.gcube.accounting.analytics.TemporalConstraint; import org.gcube.accounting.analytics.TemporalConstraint.AggregationMode; import org.gcube.accounting.analytics.TemporalConstraint.CalendarEnum; import org.gcube.accounting.datamodel.UsageRecord; import org.gcube.accounting.datamodel.aggregation.AggregatedServiceUsageRecord; import org.gcube.accounting.datamodel.aggregation.AggregatedStorageStatusRecord; import org.gcube.accounting.datamodel.aggregation.AggregatedStorageUsageRecord; import org.gcube.accounting.utility.postgresql.PostgreSQLQuery; import org.gcube.accounting.utility.postgresql.RecordToDBFields; import org.gcube.accounting.utility.postgresql.RecordToDBMapping; import org.gcube.documentstore.records.AggregatedRecord; public class Query extends PostgreSQLQuery { public static final String DATE_OF_TIMESERIES_AS_FIELD = AggregatedRecord.START_TIME; public static final String NESTED_TABLE_NAME = "nested"; private Class> clz; private final RecordToDBFields recordToDBFields; private Set requestedTableField; protected TemporalConstraint temporalConstraint; protected Map> filters; protected Set contexts; private String tableFieldToRequest; private String orderByField; private Integer limit; private String recordId; public Query(Class> clz) throws Exception { this.clz = clz; this.recordToDBFields = RecordToDBMapping.getRecordToDBFields(clz); } public Set getRequestedTableField() { return requestedTableField; } public void setTemporalConstraint(TemporalConstraint temporalConstraint) { this.temporalConstraint = temporalConstraint; } public void setFilters(Collection filters) { this.filters = new HashMap<>(); if(filters!=null && filters.size()>0) { for(Filter filter : filters) { String key = filter.getKey(); String value = filter.getValue(); Collection list = this.filters.get(key); if(list == null) { list = new ArrayList<>(); this.filters.put(key, list); } list.add(value); } } } public void setContexts(Set contexts) { this.contexts = contexts; } public void setTableFieldToRequest(String tableFieldToRequest) { this.tableFieldToRequest = tableFieldToRequest; } public void setOrderByField(String orderByField) { this.orderByField = orderByField; } public void setLimit(Integer limit) { this.limit = limit; } public void addContext(String context) { if(this.contexts == null) { this.contexts = new HashSet<>(); } this.contexts.add(context); } public void setRecordId(String recordId) { this.recordId = recordId; } public RecordToDBFields getRecordToDBMapper() { return recordToDBFields; } protected String getTableField(String fieldName) { return recordToDBFields.getTableField(fieldName); } protected void appendTableField(String fieldName) { stringBuffer.append(getTableField(fieldName)); } public static String getFormattedDate(Calendar calendar) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATETIME_PATTERN); String date = simpleDateFormat.format(calendar.getTime()); return date; } protected void addFilters() throws SQLException { if(filters!=null && filters.size()>0) { if(filters.size()>1) { stringBuffer.append("("); } boolean first = true; for(String key : filters.keySet()) { if(first) { first = false; }else { stringBuffer.append(" AND "); } addOrConditions(key, filters.get(key)); } if(filters.size()>1) { stringBuffer.append(")"); } } } protected void addOrConditions(String key, Collection values) throws SQLException { if(values!=null && values.size()>0) { if(values.size()>1) { stringBuffer.append("("); } boolean first = true; for(String value : values) { if(first) { first = false; }else { stringBuffer.append(" OR "); } appendTableField(key); stringBuffer.append("="); appendValue(value); } if(values.size()>1) { stringBuffer.append(")"); } } } protected void addContextFilter() throws SQLException { addOrConditions(UsageRecord.SCOPE, contexts); } protected void addEmittedFields(boolean addNested) throws Exception { Set aggregatedField = clz.newInstance().getAggregatedFields(); for(String fieldName : aggregatedField) { String dbField = getTableField(fieldName); switch (fieldName) { case AggregatedRecord.START_TIME: case AggregatedRecord.END_TIME: case AggregatedRecord.AGGREGATED: continue; case AggregatedRecord.OPERATION_COUNT: stringBuffer.append(", SUM("); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } stringBuffer.append(dbField); stringBuffer.append(") AS "); break; case AggregatedStorageStatusRecord.DATA_COUNT: stringBuffer.append(", MAX("); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } stringBuffer.append(dbField); stringBuffer.append(") AS "); break; case AggregatedStorageUsageRecord.DATA_VOLUME: if(clz.isAssignableFrom(AggregatedStorageUsageRecord.class)) { stringBuffer.append(", SUM("); }else if(clz.isAssignableFrom(AggregatedStorageStatusRecord.class)){ stringBuffer.append(", MAX("); } if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } stringBuffer.append(dbField); stringBuffer.append(") AS "); break; // WEIGHTED AVERAGE case AggregatedServiceUsageRecord.DURATION: stringBuffer.append(", ROUND(SUM("); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } stringBuffer.append(dbField); stringBuffer.append("*"); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } appendTableField(AggregatedRecord.OPERATION_COUNT); stringBuffer.append(")/SUM("); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } appendTableField(AggregatedRecord.OPERATION_COUNT); stringBuffer.append(")) AS "); break; case AggregatedServiceUsageRecord.MAX_INVOCATION_TIME: stringBuffer.append(", MAX("); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } stringBuffer.append(dbField); stringBuffer.append(") AS "); break; case AggregatedServiceUsageRecord.MIN_INVOCATION_TIME: stringBuffer.append(", MIN("); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } stringBuffer.append(dbField); stringBuffer.append(") AS "); break; default: stringBuffer.append(", "); break; } stringBuffer.append(dbField); requestedTableField.add(dbField); } } protected String getTimeBucketCalendarInterval(AggregationMode aggregationMode) { CalendarEnum calendarEnum = CalendarEnum.values()[aggregationMode.ordinal()]; return calendarEnum.name().toLowerCase(); } private void newQuery() { stringBuffer = new StringBuffer(); requestedTableField = new HashSet<>(); stringBuffer.append("SELECT "); } protected void addTemporalConstraintToQuery() throws SQLException { stringBuffer.append(" WHERE ("); String tableField = getTableField(AggregatedRecord.START_TIME); stringBuffer.append(tableField); stringBuffer.append(" > "); appendValue(temporalConstraint.getAlignedStartTime()); stringBuffer.append(" AND "); tableField = getTableField(AggregatedRecord.END_TIME); stringBuffer.append(tableField); stringBuffer.append(" <= "); appendValue(temporalConstraint.getAlignedEndTime()); stringBuffer.append(")"); } protected void addDateGroupBy(boolean addNested) { stringBuffer.append(" GROUP BY "); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } appendTableField(DATE_OF_TIMESERIES_AS_FIELD); } protected void addDateOrderBy(boolean addNested) { stringBuffer.append(" ORDER BY "); if(addNested) { stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); } appendTableField(DATE_OF_TIMESERIES_AS_FIELD); stringBuffer.append(" ASC"); } protected void addGroupAndOrderByForOrderByField() { stringBuffer.append(" GROUP BY "); String dbField = getTableField(tableFieldToRequest); stringBuffer.append(dbField); stringBuffer.append(" ORDER BY "); dbField = getTableField(orderByField); stringBuffer.append(dbField); stringBuffer.append(" DESC"); } protected void addLimit() { if(limit != null) { stringBuffer.append(" LIMIT "); stringBuffer.append(limit.toString()); } } protected void addRequestedField() { String dbField = getTableField(tableFieldToRequest); stringBuffer.append(dbField); requestedTableField.add(dbField); stringBuffer.append(", SUM("); dbField = getTableField(orderByField); stringBuffer.append(dbField); requestedTableField.add(dbField); stringBuffer.append(") AS "); stringBuffer.append(dbField); } protected void addRequestedDate() { String calendarInterval = getTimeBucketCalendarInterval(temporalConstraint.getAggregationMode()); String tableField = getTableField(DATE_OF_TIMESERIES_AS_FIELD); stringBuffer.append("date_trunc('"); stringBuffer.append(calendarInterval); stringBuffer.append("', "); stringBuffer.append(tableField); stringBuffer.append(" AT TIME ZONE 'UTC') "); stringBuffer.append(tableField); requestedTableField.add(tableField); } public String getTimeSeriesQuery() throws Exception { newQuery(); stringBuffer.append(NESTED_TABLE_NAME); stringBuffer.append("."); String tableField = getTableField(DATE_OF_TIMESERIES_AS_FIELD); stringBuffer.append(tableField); addEmittedFields(true); /* Nested Query */ stringBuffer.append(" FROM ( SELECT "); addRequestedDate(); addEmittedFields(false); stringBuffer.append(" FROM "); stringBuffer.append(recordToDBFields.getTableName()); addTemporalConstraintToQuery(); if(filters!=null && filters.size()>0) { stringBuffer.append(" AND "); addFilters(); } if(contexts!=null && contexts.size()>0) { stringBuffer.append(" AND "); addContextFilter(); } addDateGroupBy(false); addDateOrderBy(false); stringBuffer.append(") AS "); stringBuffer.append(NESTED_TABLE_NAME); /* Nested Query */ addDateGroupBy(true); addDateOrderBy(true); return stringBuffer.toString(); } public String getNextPossibleValueQuery() throws Exception { newQuery(); addRequestedField(); stringBuffer.append(" FROM "); stringBuffer.append(recordToDBFields.getTableName()); boolean addAnd = false; if(temporalConstraint!=null) { addTemporalConstraintToQuery(); addAnd = true; } if(filters!=null && filters.size()>0) { if(addAnd) { stringBuffer.append(" AND "); } addFilters(); addAnd = true; } if(contexts!=null && contexts.size()>0) { if(addAnd) { stringBuffer.append(" AND "); } addContextFilter(); addAnd = false; } addGroupAndOrderByForOrderByField(); addLimit(); return stringBuffer.toString(); } public String getRecordQuery(){ newQuery(); stringBuffer.append("* "); stringBuffer.append(" FROM "); stringBuffer.append(recordToDBFields.getTableName()); stringBuffer.append(" WHERE "); stringBuffer.append(" id="); appendString(recordId); return stringBuffer.toString(); } public String getDinstinctValuesQuery() { newQuery(); String dbField = getTableField(tableFieldToRequest); requestedTableField.add(dbField); stringBuffer.append("DISTINCT "); stringBuffer.append(dbField); stringBuffer.append(" FROM "); stringBuffer.append(recordToDBFields.getTableName()); return stringBuffer.toString(); } }