Pagination fix for ORM

Commit fixes pagination with ORM.
Offset and limit are used in sub query
that retrieves alarm ids.

Closes-Bug: 1579625
Change-Id: Ic9fa93e5ba9360cabe5a9d63ae31f2d431f5e8d1
This commit is contained in:
Tomasz Trębski 2016-05-18 12:27:56 +02:00
parent ea2447df72
commit 930a1dfa04
2 changed files with 172 additions and 150 deletions

View File

@ -15,15 +15,19 @@
package monasca.api.infrastructure.persistence.hibernate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@ -149,13 +153,23 @@ public class AlarmSqlRepoImpl
}
@SuppressWarnings("unchecked")
@Override
public List<Alarm> find(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
AlarmSeverity severity, String lifecycleState, String link,
DateTime stateUpdatedStart, List<String> sortBy,
String offset, int limit, boolean enforceLimit) {
public List<Alarm> find(final String tenantId,
final String alarmDefId,
final String metricName,
final Map<String, String> metricDimensions,
final AlarmState state,
final AlarmSeverity severity,
final String lifecycleState,
final String link,
final DateTime stateUpdatedStart,
final List<String> sortBy,
final String offset,
final int limit,
final boolean enforceLimit) {
logger.trace(ORM_LOG_MARKER, "find(...) entering");
if (sortBy != null && !sortBy.isEmpty()) {
throw Exceptions.unprocessableEntity(
"Sort_by is not implemented for the hibernate database type");
@ -165,159 +179,83 @@ public class AlarmSqlRepoImpl
"Severity filter is not implemented for the hibernate database type");
}
List<Alarm> alarms = this.findInternal(tenantId, alarmDefId, metricName, metricDimensions, state,
lifecycleState, link, stateUpdatedStart, offset, (3 * limit / 2), enforceLimit);
Preconditions.checkNotNull(tenantId, "TenantId is required");
if (limit == 0 || !enforceLimit)
return alarms;
if (alarms.size() > limit) {
for (int i = alarms.size() - 1; i > limit; i--) {
alarms.remove(i);
}
} else if (alarms.size() > 0) {
while (alarms.size() < limit) {
List<Alarm> alarms2;
int diff = limit - alarms.size();
String offset2 = alarms.get(alarms.size() - 1).getId();
alarms2 = this.findInternal(tenantId, alarmDefId, metricName, metricDimensions, state,
lifecycleState, link, stateUpdatedStart, offset2, (2 * diff), enforceLimit);
if (alarms2.size() == 0)
break;
for (int i = 0; i < alarms2.size() && i < diff; i++)
alarms.add(alarms2.get(i));
}
}
return alarms;
}
private List<Alarm> findInternal(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
String lifecycleState, String link, DateTime stateUpdatedStart,
String offset, int limit, boolean enforceLimit) {
Session session = null;
List<Alarm> alarms = new LinkedList<>();
try {
Query query;
session = sessionFactory.openSession();
StringBuilder
sbWhere =
new StringBuilder("(select a.id "
+ "from alarm as a, alarm_definition as ad "
+ "where ad.id = a.alarm_definition_id "
+ " and ad.deleted_at is null "
+ " and ad.tenant_id = :tenantId ");
if (alarmDefId != null) {
sbWhere.append(" and ad.id = :alarmDefId ");
}
if (metricName != null) {
sbWhere.append(" and a.id in (select distinct a.id from alarm as a "
+ "inner join alarm_metric as am on am.alarm_id = a.id "
+ "inner join metric_definition_dimensions as mdd "
+ " on mdd.id = am.metric_definition_dimensions_id "
+ "inner join (select distinct id from metric_definition "
+ " where name = :metricName) as md "
+ " on md.id = mdd.metric_definition_id ");
buildJoinClauseFor(metricDimensions, sbWhere);
sbWhere.append(")");
} else if (metricDimensions != null) {
sbWhere.append(" and a.id in (select distinct a.id from alarm as a "
+ "inner join alarm_metric as am on am.alarm_id = a.id "
+ "inner join metric_definition_dimensions as mdd "
+ " on mdd.id = am.metric_definition_dimensions_id ");
buildJoinClauseFor(metricDimensions, sbWhere);
sbWhere.append(")");
}
if (state != null) {
sbWhere.append(" and a.state = :state");
}
if (lifecycleState != null) {
sbWhere.append(" and a.lifecycle_state = :lifecycleState");
}
if (link != null) {
sbWhere.append(" and a.link = :link");
}
if (stateUpdatedStart != null) {
sbWhere.append(" and a.state_updated_at >= :stateUpdatedStart");
}
if (offset != null) {
sbWhere.append(" and a.id > :offset");
}
sbWhere.append(" order by a.id ASC ");
if (enforceLimit && limit > 0) {
sbWhere.append(" limit :limit");
}
sbWhere.append(")");
String sql = String.format(FIND_ALARMS_SQL, sbWhere);
final Query query;
final String sql = String.format(FIND_ALARMS_SQL, this.getFindAlarmsSubQuery(
alarmDefId,
metricName,
metricDimensions,
state,
lifecycleState,
link,
stateUpdatedStart,
offset,
limit,
enforceLimit
));
try {
query = session.createSQLQuery(sql);
query = new Function<Session, Query>(){
@Nullable
@Override
public Query apply(@Nullable final Session input) {
assert input != null;
final Query query = input.createSQLQuery(sql);
query.setString("tenantId", tenantId);
if (alarmDefId != null) {
query.setString("alarmDefId", alarmDefId);
}
if (metricName != null) {
query.setString("metricName", metricName);
}
if (state != null) {
query.setString("state", state.name());
}
if (link != null) {
query.setString("link", link);
}
if (lifecycleState != null) {
query.setString("lifecycleState", lifecycleState);
}
if (stateUpdatedStart != null) {
query.setDate("stateUpdatedStart", stateUpdatedStart.toDateTime(DateTimeZone.UTC).toDate());
}
if (enforceLimit && limit > 0) {
query.setInteger("limit", limit + 1);
}
bindDimensionsToQuery(query, metricDimensions);
return query;
}
}.apply((session = sessionFactory.openSession()));
} catch (Exception e) {
logger.error("Failed to bind query {}, error is {}", sql, e.getMessage());
throw new RuntimeException("Failed to bind query", e);
}
query.setString("tenantId", tenantId);
if (alarmDefId != null) {
query.setString("alarmDefId", alarmDefId);
}
if (offset != null) {
query.setString("offset", offset);
}
if (metricName != null) {
query.setString("metricName", metricName);
}
if (state != null) {
query.setString("state", state.name());
}
if (link != null) {
query.setString("link", link);
}
if (lifecycleState != null) {
query.setString("lifecycleState", lifecycleState);
}
if (stateUpdatedStart != null) {
query.setDate("stateUpdatedStart", stateUpdatedStart.toDateTime(DateTimeZone.UTC).toDate());
}
if (enforceLimit && limit > 0) {
query.setInteger("limit", limit + 1);
}
bindDimensionsToQuery(query, metricDimensions);
List<Object[]> alarmList = (List<Object[]>) query.list();
if(alarmList.isEmpty()){
return Collections.emptyList();
}
alarms = createAlarms(alarmList);
} finally {
@ -326,6 +264,88 @@ public class AlarmSqlRepoImpl
}
}
return alarms;
}
private String getFindAlarmsSubQuery(final String alarmDefId,
final String metricName,
final Map<String, String> metricDimensions,
final AlarmState state,
final String lifecycleState,
final String link,
final DateTime stateUpdatedStart,
final String offset,
final int limit,
final boolean enforceLimit) {
final StringBuilder
sbWhere =
new StringBuilder("(select distinct a.id "
+ "from alarm as a, alarm_definition as ad "
+ "where ad.id = a.alarm_definition_id "
+ " and ad.deleted_at is null "
+ " and ad.tenant_id = :tenantId ");
if (alarmDefId != null) {
sbWhere.append(" and ad.id = :alarmDefId ");
}
if (metricName != null) {
sbWhere.append(" and a.id in (select distinct a.id from alarm as a "
+ "inner join alarm_metric as am on am.alarm_id = a.id "
+ "inner join metric_definition_dimensions as mdd "
+ " on mdd.id = am.metric_definition_dimensions_id "
+ "inner join (select distinct id from metric_definition "
+ " where name = :metricName) as md "
+ " on md.id = mdd.metric_definition_id ");
buildJoinClauseFor(metricDimensions, sbWhere);
sbWhere.append(")");
} else if (metricDimensions != null) {
sbWhere.append(" and a.id in (select distinct a.id from alarm as a "
+ "inner join alarm_metric as am on am.alarm_id = a.id "
+ "inner join metric_definition_dimensions as mdd "
+ " on mdd.id = am.metric_definition_dimensions_id ");
buildJoinClauseFor(metricDimensions, sbWhere);
sbWhere.append(")");
}
if (state != null) {
sbWhere.append(" and a.state = :state");
}
if (lifecycleState != null) {
sbWhere.append(" and a.lifecycle_state = :lifecycleState");
}
if (link != null) {
sbWhere.append(" and a.link = :link");
}
if (stateUpdatedStart != null) {
sbWhere.append(" and a.state_updated_at >= :stateUpdatedStart");
}
sbWhere.append(" order by a.id ASC ");
if (enforceLimit && limit > 0) {
sbWhere.append(" limit :limit");
}
if (offset != null) {
sbWhere.append(" offset ");
sbWhere.append(offset);
sbWhere.append(' ');
}
sbWhere.append(")");
return sbWhere.toString();
}
private List<Alarm> createAlarms(List<Object[]> alarmList) {
@ -391,10 +411,7 @@ public class AlarmSqlRepoImpl
return dimensionSetId;
}
private void bindDimensionsToQuery(
Query query,
Map<String, String> dimensions) {
private void bindDimensionsToQuery(Query query, Map<String, String> dimensions) {
if (dimensions != null) {
int i = 0;
for (Iterator<Map.Entry<String, String>> it = dimensions.entrySet().iterator(); it.hasNext(); i++) {

View File

@ -347,6 +347,11 @@ public class AlarmSqlRepositoryImplTest {
@Test(groups = "orm")
public void shouldFind() {
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, null, 1, true), alarm1, alarm2);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, null, 2, true), alarm1, alarm2, compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, "1", 1, true), alarm2, compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, "2", 1, true), compoundAlarm, alarm3);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, "3", 1, true), alarm3);
checkList(repo.find("Not a tenant id", null, null, null, null, null, null, null, null, null, null, 1, false));
@ -389,8 +394,8 @@ public class AlarmSqlRepositoryImplTest {
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, DateTime.now(UTC_TIMEZONE), null, null, 0, false));
//checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-15T00:00:00Z"), null, 0, false),
// compoundAlarm);
// checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-15T00:00:00Z"), null, 0, false),
// compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-14T00:00:00Z"), null, null, 1, false),
alarm1, alarm2, alarm3, compoundAlarm);