From 6f44abbed20a63fa47567eb9fa7823ea720ab79d Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Thu, 19 Feb 2026 10:42:36 -0800 Subject: [PATCH] Partial backport of platform PR #7435 --- .../IVisualizationSourceQuery.java | 2 - .../labkey/study/query/StudyQuerySchema.java | 22 ---- .../query/VisualizationVisitTagTable.java | 104 ------------------ .../StudyVisualizationProvider.java | 41 ++++--- .../sql/OuterJoinSourceQuery.java | 6 - .../sql/VisualizationSourceQuery.java | 17 +-- 6 files changed, 24 insertions(+), 168 deletions(-) diff --git a/api/src/org/labkey/api/visualization/IVisualizationSourceQuery.java b/api/src/org/labkey/api/visualization/IVisualizationSourceQuery.java index 0fc293bb331..9c017057af5 100644 --- a/api/src/org/labkey/api/visualization/IVisualizationSourceQuery.java +++ b/api/src/org/labkey/api/visualization/IVisualizationSourceQuery.java @@ -63,8 +63,6 @@ public interface IVisualizationSourceQuery boolean isSkipVisitJoin(); - boolean isVisitTagQuery(); - /** * True if any select or aggregate requires a left join explicitly. This is an override for any columns * that might require some form of an INNER JOIN. diff --git a/study/src/org/labkey/study/query/StudyQuerySchema.java b/study/src/org/labkey/study/query/StudyQuerySchema.java index a5fbb61149e..83a2fb67591 100644 --- a/study/src/org/labkey/study/query/StudyQuerySchema.java +++ b/study/src/org/labkey/study/query/StudyQuerySchema.java @@ -127,7 +127,6 @@ public class StudyQuerySchema extends UserSchema implements UserSchema.HasContex public static final String VISIT_TAG_TABLE_NAME = "VisitTag"; public static final String VISIT_TAG_MAP_TABLE_NAME = "VisitTagMap"; public static final String VISIT_ALIASES = "VisitAliases"; - public static final String VISUALIZATION_VISIT_TAG_TABLE_NAME = "VisualizationVisitTag"; public static final String VISIT_MAP_TABLE_NAME = "VisitMap"; public static final String STUDY_DATA_TABLE_NAME = "StudyData"; @@ -645,27 +644,6 @@ public TableInfo createTable(String name, ContainerFilter cf) { return new VisitMapTable(this, cf); } - if (name.startsWith(VISUALIZATION_VISIT_TAG_TABLE_NAME)) - { - // Name is encoded with useProtocolDay boolean, tagName, and altQueryName - String params = name.substring(VISUALIZATION_VISIT_TAG_TABLE_NAME.length()); - boolean useProtocolDay; - if (params.startsWith("-true")) - { - params = params.substring(params.indexOf("-true") + 6); - useProtocolDay = true; - } - else - { - params = params.substring(params.indexOf("-false") + 7); - useProtocolDay = false; - } - int hyphenIndex = params.indexOf("-"); - String tagName = hyphenIndex > -1 ? params.substring(0, hyphenIndex) : params; - String altQueryName = hyphenIndex > -1 ? params.substring(hyphenIndex + 1) : null; - - return new VisualizationVisitTagTable(this, cf, getStudy(), getUser(), tagName, useProtocolDay, altQueryName); - } // Might be a dataset DatasetDefinition dsd = getDatasetDefinitionByQueryName(name); diff --git a/study/src/org/labkey/study/query/VisualizationVisitTagTable.java b/study/src/org/labkey/study/query/VisualizationVisitTagTable.java index b494591ef6e..e69de29bb2d 100644 --- a/study/src/org/labkey/study/query/VisualizationVisitTagTable.java +++ b/study/src/org/labkey/study/query/VisualizationVisitTagTable.java @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2014-2019 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.labkey.study.query; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.data.ContainerFilter; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.VirtualTable; -import org.labkey.api.query.ExprColumn; -import org.labkey.api.security.User; -import org.labkey.api.study.StudyService; -import org.labkey.study.model.StudyImpl; - -public class VisualizationVisitTagTable extends VirtualTable -{ - private final StudyImpl _study; - private final User _user; - private final boolean _useProtocolDay; - private final String _visitTagName; - private final String _dayString; - private final String _altQueryName; - - public VisualizationVisitTagTable(StudyQuerySchema schema, ContainerFilter cf, StudyImpl study, User user, String visitTagName, boolean useProtocolDay, String altQueryName) - { - super(schema.getDbSchema(), "VizVisitTag", schema, cf); - _study = study; - _user = user; - _visitTagName = visitTagName; - _useProtocolDay = useProtocolDay; - if (_useProtocolDay) - _dayString = "ProtocolDay"; - else - _dayString = "Day"; - _altQueryName = altQueryName; - - addColumn(new ExprColumn(this, StudyService.get().getSubjectColumnName(_study.getContainer()), - new SQLFragment(ExprColumn.STR_TABLE_ALIAS + "." + "ParticipantId"), JdbcType.VARCHAR)); - - // 20546: need to expose Container for use in StudyVisualizationProvider.getJoinColumns - addColumn(new ExprColumn(this, "Container", new SQLFragment(ExprColumn.STR_TABLE_ALIAS + "." + "Container"), JdbcType.VARCHAR)); - - addColumn(new ExprColumn(this, "ZeroDay", new SQLFragment(ExprColumn.STR_TABLE_ALIAS + "." + _dayString), JdbcType.INTEGER)); - } - - @NotNull - @Override - public SQLFragment getFromSQL(String alias) - { - checkReadBeforeExecute(); - if (_altQueryName == null) - { - String innerAlias = alias + "_SP"; - String joinString; - if (_useProtocolDay) - { - joinString = "\nJOIN study.Visit ON " + innerAlias + ".VisitId = study.Visit.RowId AND " + - innerAlias + ".VisitId IS NOT NULL"; - } - else - { - joinString = "\nJOIN study.ParticipantVisit ON " + innerAlias + ".VisitId = study.ParticipantVisit.VisitRowId AND " + - innerAlias + ".ParticipantId = study.ParticipantVisit.ParticipantId"; - } - - SQLFragment from = new SQLFragment("(SELECT VisitId, " + _dayString + ", " + innerAlias + ".ParticipantId, Container " + " FROM (SELECT ParticipantId, "); - from.append("COALESCE(CohortVisitTag.VisitId, NoCohortTag.VisitId) As VisitId, CurrentCohortId FROM study.Participant\n") - .append("LEFT OUTER JOIN study.VisitTagMap CohortVisitTag ON study.Participant.CurrentCohortId = CohortVisitTag.CohortID AND CohortVisitTag.VisitTag=") - .append("'" + _visitTagName + "'\n") - .append("AND CohortVisitTag.Container = Participant.Container"); - from.append("\nLEFT OUTER JOIN study.VisitTagMap NoCohortTag ON NoCohortTag.VisitTag =") - .append("'" + _visitTagName + "' AND NoCohortTag.CohortID IS NULL\n") - .append("AND NoCohortTag.Container = Participant.Container"); - from.append("\nWHERE "); - - // TODO: this is a temp fix for the Dataspace usecase - from.append(ContainerFilter.Type.AllInProject.create(_study.getContainer(), _user).getSQLFragment(getSchema(), new SQLFragment("study.Participant.Container"))); - - from.append(") ").appendIdentifier(innerAlias); - from.append(joinString); - from.append("\n) ").appendIdentifier(alias); - return from; - } - else - { - // allow caller to pass in their own query to use for the SQL to get the participant-to-zero day map (used for CDS study axis alignment by visit tag) - // NOTE: it is assumed that the query will have the expected columns plus a column for VisitTagMap to filter on - return new SQLFragment("(SELECT * FROM " + _altQueryName + " WHERE VisitTagName = '" + _visitTagName + "')").appendIdentifier(alias); - } - } -} diff --git a/study/src/org/labkey/study/visualization/StudyVisualizationProvider.java b/study/src/org/labkey/study/visualization/StudyVisualizationProvider.java index 445a8eb9fff..353feb1e7dc 100644 --- a/study/src/org/labkey/study/visualization/StudyVisualizationProvider.java +++ b/study/src/org/labkey/study/visualization/StudyVisualizationProvider.java @@ -177,31 +177,28 @@ public List> getJoinC joinCols.add(new Pair<>(firstSubjectCol, secondSubjectCol)); - if (!first.isVisitTagQuery() && ! second.isVisitTagQuery()) - { - // attempt to lookup the dataset using the queryName by label and then by name - Dataset firstDataset = StudyService.get().resolveDataset(first.getContainer(), first.getQueryName()); - Dataset secondDataset = StudyService.get().resolveDataset(second.getContainer(), second.getQueryName()); + // attempt to lookup the dataset using the queryName by label and then by name + Dataset firstDataset = StudyService.get().resolveDataset(first.getContainer(), first.getQueryName()); + Dataset secondDataset = StudyService.get().resolveDataset(second.getContainer(), second.getQueryName()); - boolean subjectJoinOnly = isGroupByQuery || first.isSkipVisitJoin() || second.isSkipVisitJoin(); + boolean subjectJoinOnly = isGroupByQuery || first.isSkipVisitJoin() || second.isSkipVisitJoin(); - // if either query is a demographic dataset, it's sufficient to join on subject only: - if (!subjectJoinOnly && (firstDataset == null || firstDataset.getKeyType() != Dataset.KeyType.SUBJECT) && - (secondDataset == null || secondDataset.getKeyType() != Dataset.KeyType.SUBJECT)) + // if either query is a demographic dataset, it's sufficient to join on subject only: + if (!subjectJoinOnly && (firstDataset == null || firstDataset.getKeyType() != Dataset.KeyType.SUBJECT) && + (secondDataset == null || secondDataset.getKeyType() != Dataset.KeyType.SUBJECT)) + { + VisualizationSourceColumn firstSequenceCol = getVisitJoinColumn(factory, first, firstSubjectNounSingular); + VisualizationSourceColumn secondSequenceCol = getVisitJoinColumn(factory, second, secondSubjectNounSingular); + joinCols.add(new Pair<>(firstSequenceCol, secondSequenceCol)); + + // for datasets with matching 3rd keys, join on subject/visit/key (if neither are pivoted), allowing null results for this column so as to follow the lead of the primary measure column for this query: + if (firstDataset != null && firstDataset.getKeyType() == Dataset.KeyType.SUBJECT_VISIT_OTHER && + secondDataset != null && secondDataset.getKeyType() == Dataset.KeyType.SUBJECT_VISIT_OTHER && + first.getPivot() == null && second.getPivot() == null && firstDataset.hasMatchingExtraKey(secondDataset)) { - VisualizationSourceColumn firstSequenceCol = getVisitJoinColumn(factory, first, firstSubjectNounSingular); - VisualizationSourceColumn secondSequenceCol = getVisitJoinColumn(factory, second, secondSubjectNounSingular); - joinCols.add(new Pair<>(firstSequenceCol, secondSequenceCol)); - - // for datasets with matching 3rd keys, join on subject/visit/key (if neither are pivoted), allowing null results for this column so as to follow the lead of the primary measure column for this query: - if (firstDataset != null && firstDataset.getKeyType() == Dataset.KeyType.SUBJECT_VISIT_OTHER && - secondDataset != null && secondDataset.getKeyType() == Dataset.KeyType.SUBJECT_VISIT_OTHER && - first.getPivot() == null && second.getPivot() == null && firstDataset.hasMatchingExtraKey(secondDataset)) - { - VisualizationSourceColumn firstKeyCol = factory.create(first.getSchema(), first.getQueryName(), firstDataset.getKeyPropertyName(), true); - VisualizationSourceColumn secondKeyCol = factory.create(second.getSchema(), second.getQueryName(), secondDataset.getKeyPropertyName(), true); - joinCols.add(new Pair<>(firstKeyCol, secondKeyCol)); - } + VisualizationSourceColumn firstKeyCol = factory.create(first.getSchema(), first.getQueryName(), firstDataset.getKeyPropertyName(), true); + VisualizationSourceColumn secondKeyCol = factory.create(second.getSchema(), second.getQueryName(), secondDataset.getKeyPropertyName(), true); + joinCols.add(new Pair<>(firstKeyCol, secondKeyCol)); } } diff --git a/visualization/src/org/labkey/visualization/sql/OuterJoinSourceQuery.java b/visualization/src/org/labkey/visualization/sql/OuterJoinSourceQuery.java index 5b4b214e07c..e955a3d1554 100644 --- a/visualization/src/org/labkey/visualization/sql/OuterJoinSourceQuery.java +++ b/visualization/src/org/labkey/visualization/sql/OuterJoinSourceQuery.java @@ -235,12 +235,6 @@ public boolean isSkipVisitJoin() return false; } - @Override - public boolean isVisitTagQuery() - { - return false; - } - @Override public boolean isRequireLeftJoin() { diff --git a/visualization/src/org/labkey/visualization/sql/VisualizationSourceQuery.java b/visualization/src/org/labkey/visualization/sql/VisualizationSourceQuery.java index ba0b0901c54..9827a6e2410 100644 --- a/visualization/src/org/labkey/visualization/sql/VisualizationSourceQuery.java +++ b/visualization/src/org/labkey/visualization/sql/VisualizationSourceQuery.java @@ -131,12 +131,6 @@ public boolean requireInnerJoin() return false; } - @Override - public boolean isVisitTagQuery() - { - return _queryName.startsWith("VisualizationVisitTag"); - } - @Override public boolean isRequireLeftJoin() { @@ -213,7 +207,7 @@ public String getSelectListName(Set selectAliases) private static void addToColMap(Map> colMap, String name, VisualizationSourceColumn alias) { - Set aliases = colMap.computeIfAbsent(name, k -> new LinkedHashSet<>()); + Set aliases = colMap.computeIfAbsent(name, n -> new LinkedHashSet<>()); aliases.add(alias); } @@ -434,7 +428,7 @@ public String getGroupByClause() return ""; } - private void appendValueList(StringBuilder sql, VisualizationSourceColumn col) throws org.labkey.api.visualization.SQLGenerationException + private void appendValueList(StringBuilder sql, VisualizationSourceColumn col) { if (col.getValues() != null && !col.getValues().isEmpty()) { @@ -457,7 +451,7 @@ private void appendValueList(StringBuilder sql, VisualizationSourceColumn col) t } } - public String getPivotClause() throws org.labkey.api.visualization.SQLGenerationException + public String getPivotClause() { if (_pivot != null) { @@ -560,7 +554,7 @@ private String appendSimpleFilter(StringBuilder where, SimpleFilter filter, Stri return separator; } - public String getWhereClause() throws org.labkey.api.visualization.SQLGenerationException + public String getWhereClause() { StringBuilder where = new StringBuilder(); String sep = "WHERE "; @@ -598,12 +592,11 @@ public String getWhereClause() throws org.labkey.api.visualization.SQLGeneration @Override public String getSQL(VisualizationSourceColumn.Factory factory) throws SQLGenerationException { - String sql = getSelectClause(factory) + "\n" + + return getSelectClause(factory) + "\n" + getFromClause() + "\n" + getWhereClause() + "\n" + getGroupByClause() + "\n" + getPivotClause() + "\n"; - return sql; } @Override