Skip to content

Commit 6818f87

Browse files
authored
Rework sorting a bit for table tabular reporters (#138)
1 parent 7c62f24 commit 6818f87

File tree

11 files changed

+122
-57
lines changed

11 files changed

+122
-57
lines changed

Model/src/main/java/org/gusdb/wdk/model/answer/AnswerValue.java

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import java.util.ArrayList;
99
import java.util.Arrays;
1010
import java.util.Collection;
11-
import java.util.HashMap;
1211
import java.util.LinkedHashMap;
1312
import java.util.List;
1413
import java.util.Map;
@@ -55,14 +54,15 @@
5554
import org.gusdb.wdk.model.question.Question;
5655
import org.gusdb.wdk.model.record.Field;
5756
import org.gusdb.wdk.model.record.PrimaryKeyDefinition;
58-
import org.gusdb.wdk.model.record.PrimaryKeyValue;
5957
import org.gusdb.wdk.model.record.PrimaryKeyIterator;
58+
import org.gusdb.wdk.model.record.PrimaryKeyValue;
6059
import org.gusdb.wdk.model.record.RecordClass;
6160
import org.gusdb.wdk.model.record.RecordInstance;
6261
import org.gusdb.wdk.model.record.ResultSetPrimaryKeyIterator;
6362
import org.gusdb.wdk.model.record.TableField;
6463
import org.gusdb.wdk.model.record.attribute.AttributeField;
6564
import org.gusdb.wdk.model.record.attribute.ColumnAttributeField;
65+
import org.gusdb.wdk.model.record.attribute.PkColumnAttributeField;
6666
import org.gusdb.wdk.model.record.attribute.QueryColumnAttributeField;
6767
import org.gusdb.wdk.model.user.StepContainer;
6868
import org.gusdb.wdk.model.user.User;
@@ -159,8 +159,8 @@ public class AnswerValue implements PartitionKeysProvider {
159159
private int _startIndex = 1;
160160
private int _endIndex = UNBOUNDED_END_PAGE_INDEX;
161161

162-
// sorting for this answer
163-
private Map<String, Boolean> _sortingMap;
162+
// sorting for this answer (true = ascending)
163+
private LinkedHashMap<String, Boolean> _sortingMap;
164164

165165
// values generated and cached from the above
166166
private String _sortedIdSql;
@@ -179,7 +179,7 @@ public class AnswerValue implements PartitionKeysProvider {
179179
* @param avoidCacheHit
180180
*/
181181
public AnswerValue(RunnableObj<AnswerSpec> validAnswerSpec, int startIndex,
182-
int endIndex, Map<String, Boolean> sortingMap, boolean avoidCacheHit) throws WdkModelException {
182+
int endIndex, LinkedHashMap<String, Boolean> sortingMap, boolean avoidCacheHit) throws WdkModelException {
183183
_validAnswerSpec = validAnswerSpec;
184184
_answerSpec = validAnswerSpec.get();
185185
_question = _answerSpec.getQuestion().get(); // must be present if answerspec is valid
@@ -188,7 +188,7 @@ public AnswerValue(RunnableObj<AnswerSpec> validAnswerSpec, int startIndex,
188188
_avoidCacheHit = avoidCacheHit;
189189
_idsQueryInstance = Query.makeQueryInstance(_answerSpec.getQueryInstanceSpec().getRunnable().getLeft(), avoidCacheHit, this);
190190
_resultSizeFactory = new ResultSizeFactory(this);
191-
_sortingMap = sortingMap == null? new HashMap<String, Boolean>() : sortingMap;
191+
_sortingMap = sortingMap == null? new LinkedHashMap<String, Boolean>() : sortingMap;
192192
setPageIndex(startIndex, endIndex);
193193
LOG.debug("AnswerValue created for question: " + _question.getDisplayName());
194194
}
@@ -508,35 +508,17 @@ public String getFilteredAttributeSql(
508508
final Query attrQuery,
509509
final boolean sort
510510
) throws WdkModelException {
511-
String wrapped = joinToIds(getAttributeSql(attrQuery));
512-
wrapped = substitutePartitionKeys(wrapped, attrQuery.getName() + "-getFilteredAttributeSql()");
513-
if (!sort)
514-
return wrapped;
515-
516-
final var cols = getSortingColumns()
517-
.stream()
518-
.filter(spec -> spec.getItem() instanceof QueryColumnAttributeField)
519-
.iterator();
520-
521-
if (!cols.hasNext())
522-
return wrapped;
523511

524-
final var out = new StringBuilder(wrapped).append("\nORDER BY\n inq.");
525-
526-
boolean first = true;
527-
while (cols.hasNext()) {
528-
final var spec = cols.next();
529-
530-
if (!first)
531-
out.append("\n, inq.");
512+
// get attribute SQL joined to IDs
513+
String wrapped = joinToIds(getAttributeSql(attrQuery));
532514

533-
out.append(spec.getItemName())
534-
.append(' ')
535-
.append(spec.getDirection().toString());
536-
first=false;
537-
}
515+
// substitute partition keys
516+
wrapped = substitutePartitionKeys(wrapped, attrQuery.getName() + "-getFilteredAttributeSql()");
538517

539-
return out.toString();
518+
// if asked to sort, append generated order-by clause
519+
return !sort ? wrapped :
520+
wrapped + getOrderByClause(_question.getRecordClass().getPrimaryKeyDefinition(),
521+
_sortingMap, _question.getAttributeFieldMap(), Optional.of("inq."));
540522
}
541523

542524
public String getAttributeSql(Query attributeQuery) throws WdkModelException {
@@ -772,11 +754,14 @@ private void prepareSortingSqls(Map<String, String> sqls, Collection<String> ord
772754
LOG.debug("AnswerValue: prepareSortingSqls(): sorting map: " + _sortingMap); //e.g.: {primary_key=true}
773755
final String idQueryNameStub = "answer_id_query";
774756
queryNames.put(idQueryNameStub, "idq");
757+
775758
for (String fieldName : _sortingMap.keySet()) {
776759
AttributeField field = fields.get(fieldName);
777760
if (field == null) continue;
778761
boolean ascend = _sortingMap.get(fieldName);
762+
779763
Map<String, ColumnAttributeField> dependents = field.getColumnAttributeFields();
764+
780765
for (ColumnAttributeField dependent : dependents.values()) {
781766

782767
// set default values for PK and simple columns
@@ -869,12 +854,76 @@ public Optional<String> getResultMessage() throws WdkModelException {
869854
return _idsQueryInstance.getResultMessage();
870855
}
871856

872-
public Map<String, Boolean> getSortingMap() {
857+
public void setSortByIdAttribute() {
858+
String idAttribute = _question.getRecordClass().getIdAttributeField().getName();
859+
_sortingMap.clear();
860+
_sortingMap.put(idAttribute, true);
861+
}
862+
863+
/**
864+
* @return the requested column names to sort and a boolean indication sort direction (true = ascending)
865+
*/
866+
public LinkedHashMap<String, Boolean> getSortingMap() {
873867
return new LinkedHashMap<>(_sortingMap);
874868
}
875869

876-
public List<SortDirectionSpec<AttributeField>> getSortingColumns() {
877-
return SortDirectionSpec.convertSorting(_sortingMap, _question.getAttributeFieldMap());
870+
/**
871+
* Converts the passed sorting columns into those used to perform the actual sort; note
872+
* these columns could be dependent columns (for derived atrributes) and/or may have been
873+
* mapped to proxy sort columns and may not match the column names returned by getSortingMap().
874+
*
875+
* Note 1: some of this logic is repeated in prepareSortingSqls() and a combination of
876+
* RecorStreamFactory::requiresExactlyOneAttrQuery and FileBasedRecordStream::getRequiredColumnAttributeFields.
877+
*
878+
* Note 2: We append primary key columns to the end to ensure consistent paging; this
879+
* means if you want to avoid runtime by not sorting at all (PKs included) if
880+
* _sortingMap is empty, do NOT call this method. It will always return a sort.
881+
* @throws WdkModelException
882+
*/
883+
public static String getOrderByClause(PrimaryKeyDefinition pk, Map<String, Boolean> sortingColumns,
884+
Map<String, AttributeField> attributeFieldMap, Optional<String> tableAlias) throws WdkModelException {
885+
886+
String aliasStr = tableAlias.map(a -> a.endsWith(".") ? a : a + ".").orElse("");
887+
888+
List<SortDirectionSpec<AttributeField>> baseSorts =
889+
SortDirectionSpec.convertSorting(sortingColumns, attributeFieldMap);
890+
891+
Map<String, String> orderClauses = new LinkedHashMap<>();
892+
893+
for (SortDirectionSpec<AttributeField> spec : baseSorts) {
894+
895+
for (ColumnAttributeField dependedField: spec.getItem().getColumnAttributeFields().values()) {
896+
897+
if (dependedField instanceof QueryColumnAttributeField) {
898+
899+
QueryColumnAttributeField queryField = (QueryColumnAttributeField)dependedField;
900+
Column column = queryField.getColumn();
901+
902+
// use custom sorting column if specified
903+
String columnName = Optional
904+
.ofNullable(column.getSortingColumn())
905+
.orElse(column.getName());
906+
907+
orderClauses.putIfAbsent(columnName, column.isIgnoreCase()
908+
? "lower(" + aliasStr + columnName + ") " + spec.getDirection()
909+
: aliasStr + columnName + " " + spec.getDirection());
910+
}
911+
else if (dependedField instanceof PkColumnAttributeField) {
912+
orderClauses.putIfAbsent(dependedField.getName(), dependedField.getName() + " ASC");
913+
}
914+
else {
915+
throw new IllegalStateException("Unknown subclass of ColumnAttributeField: " + dependedField.getClass().getName());
916+
}
917+
}
918+
}
919+
920+
// for true stable sort, add PK cols to the end if they are not already present
921+
for (String pkCol : pk.getColumnRefs()) {
922+
orderClauses.putIfAbsent(pkCol, pkCol + " ASC");
923+
}
924+
925+
// join together
926+
return orderClauses.values().stream().collect(Collectors.joining(", ", "\n ORDER BY ", "\n"));
878927
}
879928

880929
/**
@@ -1048,19 +1097,22 @@ public boolean entireResultRequested() {
10481097
}
10491098

10501099
public static String wrapToReturnOnlyPkAndSelectedCols(String sql,
1051-
RecordClass rc, Collection<QueryColumnAttributeField> fields) {
1100+
Question question, Collection<QueryColumnAttributeField> fields,
1101+
Map<String,Boolean> sortingColumns) throws WdkModelException {
10521102

1103+
PrimaryKeyDefinition pk = question.getRecordClass().getPrimaryKeyDefinition();
10531104
List<String> cols = new ListBuilder<String>()
1054-
.addAll(Arrays.asList(rc.getPrimaryKeyDefinition().getColumnRefs()))
1105+
.addAll(Arrays.asList(pk.getColumnRefs()))
10551106
.addAll(fields.stream().map(Field::getName).collect(Collectors.toList()))
10561107
.toList();
1057-
1108+
10581109
return new StringBuilder()
10591110
.append("/* SingleAttributeRecordStream */\nSELECT\n ")
10601111
.append(String.join(",\n ", cols))
10611112
.append("\n FROM (\n")
10621113
.append(sql)
10631114
.append("\n) sarsc")
1115+
.append(getOrderByClause(pk, sortingColumns, question.getAttributeFieldMap(), Optional.of("sarsc")))
10641116
.toString();
10651117
}
10661118

Model/src/main/java/org/gusdb/wdk/model/answer/factory/AnswerValueFactory.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.gusdb.wdk.model.answer.factory;
22

3-
import java.util.Collections;
4-
import java.util.Map;
3+
import java.util.LinkedHashMap;
54

65
import org.gusdb.fgputil.validation.ValidObjectFactory.RunnableObj;
76
import org.gusdb.wdk.model.WdkModelException;
@@ -25,7 +24,7 @@ public static AnswerValue makeAnswer(RunnableObj<AnswerSpec> validSpec, boolean
2524
Question question = validSpec.get().getQuestion().get();
2625
int pageStart = 1;
2726
int pageEnd = AnswerValue.UNBOUNDED_END_PAGE_INDEX;
28-
AnswerValue answerValue = makeAnswer(validSpec, pageStart, pageEnd, Collections.emptyMap(), avoidCacheHit);
27+
AnswerValue answerValue = makeAnswer(validSpec, pageStart, pageEnd, new LinkedHashMap<>(), avoidCacheHit);
2928
if (question.isFullAnswer()) {
3029
int resultSize = answerValue.getResultSizeFactory().getResultSize();
3130
if (resultSize > pageEnd)
@@ -35,7 +34,7 @@ public static AnswerValue makeAnswer(RunnableObj<AnswerSpec> validSpec, boolean
3534
}
3635

3736
public static AnswerValue makeAnswer(RunnableObj<AnswerSpec> validSpec,
38-
int startIndex, int endIndex, Map<String, Boolean> sortingMap, boolean avoidCacheHit) throws WdkModelException {
37+
int startIndex, int endIndex, LinkedHashMap<String, Boolean> sortingMap, boolean avoidCacheHit) throws WdkModelException {
3938
Question question = validSpec.get().getQuestion().get();
4039
if (question instanceof SingleRecordQuestion) {
4140
return new SingleRecordAnswerValue(validSpec);

Model/src/main/java/org/gusdb/wdk/model/answer/single/SingleRecordAnswerValue.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import static org.gusdb.fgputil.FormatUtil.join;
44
import static org.gusdb.fgputil.functional.Functions.mapToList;
55

6-
import java.util.Collections;
6+
import java.util.LinkedHashMap;
77
import java.util.Map;
88
import java.util.NoSuchElementException;
99

@@ -38,7 +38,7 @@ public SingleRecordResultSizeFactory(AnswerValue answerValue) {
3838
private Map<String, Object> _pkMap;
3939

4040
public SingleRecordAnswerValue(RunnableObj<AnswerSpec> validSpec) throws WdkModelException {
41-
super(validSpec, 1, UNBOUNDED_END_PAGE_INDEX, Collections.EMPTY_MAP, false);
41+
super(validSpec, 1, UNBOUNDED_END_PAGE_INDEX, new LinkedHashMap<>(), false);
4242
SingleRecordQuestion question = (SingleRecordQuestion)validSpec.get().getQuestion().get();
4343
SingleRecordQuestionParam param = question.getParam();
4444
_recordClass = question.getRecordClass();

Model/src/main/java/org/gusdb/wdk/model/answer/stream/RecordStreamFactory.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.apache.log4j.Logger;
44
import org.gusdb.fgputil.SortDirectionSpec;
5+
import org.gusdb.fgputil.functional.Functions;
56
import org.gusdb.wdk.model.WdkModelException;
67
import org.gusdb.wdk.model.WdkUserException;
78
import org.gusdb.wdk.model.answer.AnswerValue;
@@ -98,8 +99,13 @@ private static boolean requiresExactlyOneAttrQuery(
9899
}
99100
var fieldsToConsider = new ArrayList<>(fields);
100101
if (includeSortingColumnsInCalculation) {
101-
fieldsToConsider.addAll(answerValue.getSortingColumns()
102-
.stream().map(SortDirectionSpec::getItem).collect(Collectors.toList()));
102+
fieldsToConsider.addAll(
103+
SortDirectionSpec.convertSorting(
104+
answerValue.getSortingMap(),
105+
Functions.getMapFromValues(fields, AttributeField::getName))
106+
.stream()
107+
.map(SortDirectionSpec::getItem)
108+
.collect(Collectors.toList()));
103109
}
104110
return FileBasedRecordStream.requiresExactlyOneAttributeQuery(fieldsToConsider);
105111
}

Model/src/main/java/org/gusdb/wdk/model/answer/stream/SingleAttributeRecordStream.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public SingleAttributeRecordStream(
4040
_sql = attributeQuery == null
4141
? (performSort ? _answerValue.getSortedIdSql() : _answerValue.getIdSql())
4242
: AnswerValue.wrapToReturnOnlyPkAndSelectedCols(
43-
_answerValue.getFilteredAttributeSql(attributeQuery, performSort),
44-
_answerValue.getQuestion().getRecordClass(), _requiredFields);
43+
_answerValue.getFilteredAttributeSql(attributeQuery, false), // do not perform sort here; must do it in the wrapper
44+
_answerValue.getQuestion(), _requiredFields, _answerValue.getSortingMap());
4545
}
4646

4747
public String getSql() {

Model/src/main/java/org/gusdb/wdk/model/query/SqlQueryInstance.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public ResultList getUncachedResultsSubstitutePartitionKeys(PartitionKeysProvide
8585
var sql = partitionKeysProvider.substitutePartitionKeys(getUncachedSql(), getQuery().getFullName());
8686

8787
LOG.info("Performing the following SQL: (use debug to see SQL) " );
88-
LOG.debug("Performing the following SQL: " + sql);
88+
LOG.info("Performing the following SQL: " + sql);
8989
return new SqlResultList(SqlUtils.executeQuery(
9090
_wdkModel.getAppDb().getDataSource(),
9191
sql,

Model/src/main/java/org/gusdb/wdk/model/question/Question.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,8 +666,8 @@ public void setQuestionSet(QuestionSet questionSet) {
666666
* This method is use to clone the question, excluding dynamic attributes
667667
*/
668668
@Override
669-
public Map<String, Boolean> getSortingAttributeMap() {
670-
Map<String, Boolean> map = new LinkedHashMap<>();
669+
public LinkedHashMap<String, Boolean> getSortingAttributeMap() {
670+
LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
671671

672672
for (String attrName : _defaultSortingMap.keySet()) {
673673
map.put(attrName, _defaultSortingMap.get(attrName));

Model/src/main/java/org/gusdb/wdk/model/record/RecordClass.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,8 +1427,8 @@ public Map<String, AttributeField> getSummaryAttributeFieldMap() {
14271427
}
14281428

14291429
@Override
1430-
public Map<String, Boolean> getSortingAttributeMap() {
1431-
Map<String, Boolean> map = new LinkedHashMap<>();
1430+
public LinkedHashMap<String, Boolean> getSortingAttributeMap() {
1431+
LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
14321432
int count = 0;
14331433
for (String attrName : defaultSortingMap.keySet()) {
14341434
map.put(attrName, defaultSortingMap.get(attrName));
@@ -1445,8 +1445,8 @@ public Map<String, Boolean> getSortingAttributeMap() {
14451445
return map;
14461446
}
14471447

1448-
public Map<String, Boolean> getIdSortingAttributeMap() {
1449-
return new MapBuilder<String, Boolean>(new LinkedHashMap<>())
1448+
public LinkedHashMap<String, Boolean> getIdSortingAttributeMap() {
1449+
return (LinkedHashMap<String,Boolean>)new MapBuilder<String, Boolean>(new LinkedHashMap<>())
14501450
.put(idAttributeField.getName(), true).toMap();
14511451
}
14521452

Model/src/main/java/org/gusdb/wdk/model/report/reporter/AbstractTabularReporter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ public void write(OutputStream out) throws WdkModelException {
141141
}
142142
}
143143

144+
@Override
145+
protected RecordStream getRecords() throws WdkModelException {
146+
// tabular reporters should always be sorted by the ID attribute; set sort before getting records
147+
_baseAnswer.setSortByIdAttribute();
148+
return super.getRecords();
149+
}
150+
144151
/**
145152
* Ensures values are uniformly formatted across output types
146153
*

Model/src/main/java/org/gusdb/wdk/model/report/reporter/TableTabularReporter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public RecordStream getRecords() throws WdkModelException {
6565
throw new WdkModelException(e.getMessage(), e);
6666
}
6767
} */
68+
_baseAnswer.setSortByIdAttribute();
6869
RecordClass recordClass = _baseAnswer.getQuestion().getRecordClass();
6970
if (idAttributeContainsNonPkFields(recordClass)) {
7071
// need to use FileBasedRecordStream to support both this table and any needed attributes

0 commit comments

Comments
 (0)