From 48110dd9f0116c04ed35ebc500ae54160b242fdf Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 22 Jan 2026 17:30:39 -0800 Subject: [PATCH 1/4] EXPLINEAGEOF - support absolute value depth parameter --- .../src/org/labkey/query/sql/QInLineage.java | 49 +++++++++++++++---- query/src/org/labkey/query/sql/SqlBase.g | 5 +- query/src/org/labkey/query/sql/SqlParser.java | 44 +++++++++++++++-- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/query/src/org/labkey/query/sql/QInLineage.java b/query/src/org/labkey/query/sql/QInLineage.java index 88a926e96a4..5244f7f4c7f 100644 --- a/query/src/org/labkey/query/sql/QInLineage.java +++ b/query/src/org/labkey/query/sql/QInLineage.java @@ -28,21 +28,48 @@ import org.labkey.api.query.column.BuiltInColumnTypes; import org.labkey.query.QueryServiceImpl; +import java.util.Objects; + +import static org.labkey.query.sql.antlr.SqlBaseParser.EXPANCESTORSOF; +import static org.labkey.query.sql.antlr.SqlBaseParser.EXPDESCENDANTSOF; +import static org.labkey.query.sql.antlr.SqlBaseParser.EXPLINEAGEOF; final public class QInLineage extends QExpr { final boolean _in; + final boolean _children; final boolean _parents; + final String _method; - public QInLineage(boolean in, boolean parents) + public QInLineage(boolean in, int methodTokenType) { - this._in = in; - this._parents = parents; + super(QNode.class); + + _in = in; + _method = switch (methodTokenType) + { + case EXPANCESTORSOF -> { + _children = false; + _parents = true; + yield "EXPANCESTORSOF"; + } + case EXPDESCENDANTSOF -> { + _children = true; + _parents = false; + yield "EXPDESCENDANTSOF"; + } + case EXPLINEAGEOF -> { + _children = true; + _parents = true; + yield "EXPLINEAGEOF"; + } + default -> throw new IllegalArgumentException("Invalid QInLineage method token type: " + methodTokenType); + }; } String operator() { - return (_in ? " IN " : " NOT IN ") + (_parents ? "EXPANCESTORSOF " : "EXPDESCENDANTSOF " ); + return (_in ? " IN " : " NOT IN ") + _method + " "; } @Override @@ -51,7 +78,7 @@ public void appendSql(SqlBuilder builder, Query query) SQLTableInfo sqlti = new SQLTableInfo(query.getSchema().getDbSchema(), "_"); var children = childList(); var LHS = ((QExpr) getFirstChild()); - var RHS = ((QQuery) getLastChild()); + var RHS = ((QQuery) children.get(1)); // LHS should be a 'lineage object', e.g. the result of calling {ExtTable}.expObject() ColumnInfo lhsCol = null; @@ -80,7 +107,12 @@ public void appendSql(SqlBuilder builder, Query query) RHS.appendSql(subquery, query); // subquery will have surrounding parens, but the double parens don't cause a problem - ExpLineageOptions options = new ExpLineageOptions(_parents, !_parents, 1000); + int depth = 1_000; // TODO: Not sure why limit to 1,000 here. Underlying query will limit itself based on module properties configuration. + QNode depthExpr = children.size() > 2 ? getLastChild() : null; + if (depthExpr instanceof QNumber n) + depth = n.getValue().intValue(); + + ExpLineageOptions options = new ExpLineageOptions(_parents, _children, depth); options.setUseObjectIds(true); // expObject() returns objectid not lsid options.setOnlySelectObjectId(true); // generate one column SELECT, also don't join to material/data/protocolapplication SQLFragment lineage = ExperimentService.get().generateExperimentTreeSQL(subquery, options); @@ -93,7 +125,6 @@ public void appendSql(SqlBuilder builder, Query query) builder.append("))"); } - @Override public void appendSource(SourceBuilder builder) { @@ -109,18 +140,16 @@ public void appendSource(SourceBuilder builder) builder.popPrefix(")"); } - @Override @NotNull public JdbcType getJdbcType() { return JdbcType.BOOLEAN; } - @Override public boolean equalsNode(QNode other) { - return (other instanceof QInLineage o) && _in == o._in && _parents == o._parents; + return (other instanceof QInLineage o) && _in == o._in && Objects.equals(_method, o._method); } @Override diff --git a/query/src/org/labkey/query/sql/SqlBase.g b/query/src/org/labkey/query/sql/SqlBase.g index f3a9aa7f71c..28ad379df35 100644 --- a/query/src/org/labkey/query/sql/SqlBase.g +++ b/query/src/org/labkey/query/sql/SqlBase.g @@ -191,6 +191,7 @@ EXCEPT : 'except'; EXISTS : 'exists'; EXPDESCENDANTSOF : 'expdescendantsof'; EXPANCESTORSOF : 'expancestorsof'; +EXPLINEAGEOF : 'explineageof'; FALSE : 'false'; FROM : 'from'; FULL : 'full'; @@ -423,7 +424,7 @@ fromRange tableMethod - : (EXPANCESTORSOF | EXPDESCENDANTSOF) op=OPEN^ {$op.setType(METHOD_CALL);} subQuery CLOSE! + : (EXPANCESTORSOF | EXPDESCENDANTSOF | EXPLINEAGEOF) op=OPEN^ {$op.setType(METHOD_CALL);} subQuery (COMMA! expression)? CLOSE! ; @@ -679,7 +680,7 @@ likeEscape inList - : (EXPANCESTORSOF | EXPDESCENDANTSOF) op=OPEN^ {$op.setType(METHOD_CALL);} subQuery CLOSE! + : (EXPANCESTORSOF | EXPDESCENDANTSOF | EXPLINEAGEOF) op=OPEN^ {$op.setType(METHOD_CALL);} subQuery (COMMA! expression)? CLOSE! | compoundExpr -> ^(IN_LIST compoundExpr) ; diff --git a/query/src/org/labkey/query/sql/SqlParser.java b/query/src/org/labkey/query/sql/SqlParser.java index 9174a6e2207..2d2e4180829 100644 --- a/query/src/org/labkey/query/sql/SqlParser.java +++ b/query/src/org/labkey/query/sql/SqlParser.java @@ -885,13 +885,21 @@ else if (divisorType==NUM_DOUBLE || divisorType==NUM_FLOAT || divisorType==NUM_I { // rewrite "IN EXPANCESTORS" "IN EXPDESCENDANTS" var method = rhs.getFirstChild(); - if (method.getTokenType() != EXPANCESTORSOF && method.getTokenType() != EXPDESCENDANTSOF) + if (method.getTokenType() != EXPANCESTORSOF && method.getTokenType() != EXPDESCENDANTSOF && method.getTokenType() != EXPLINEAGEOF) { _parseErrors.add(new QueryParseException("Illegal syntax near 'IN'", null, node.getLine(), node.getCharPositionInLine())); return null; } - var qInLineage = new QInLineage(node.getType()==IN, method.getTokenType() == EXPANCESTORSOF ); - qInLineage._replaceChildren(new LinkedList<>(List.of(lhs, rhs.childList().get(1)))); + var qInLineage = new QInLineage(node.getType() == IN, method.getTokenType()); + var qInLineageChildren = new LinkedList(); + qInLineageChildren.add(lhs); + + var rhsChildren = rhs.childList(); + qInLineageChildren.add(rhsChildren.get(1)); + if (rhsChildren.size() > 2) + qInLineageChildren.add(rhsChildren.get(2)); + + qInLineage._replaceChildren(qInLineageChildren); return qInLineage; } } @@ -967,7 +975,7 @@ else if (name.equals("age")) } // special case for table returning method - var isTableResultMethod = id.getTokenType() == EXPANCESTORSOF || id.getTokenType() == EXPDESCENDANTSOF; + var isTableResultMethod = id.getTokenType() == EXPANCESTORSOF || id.getTokenType() == EXPDESCENDANTSOF || id.getTokenType() == EXPLINEAGEOF; if (!isTableResultMethod) { try @@ -1658,6 +1666,7 @@ QNode qnode(CommonTree node, boolean constExpr) break; case EXPANCESTORSOF: case EXPDESCENDANTSOF: + case EXPLINEAGEOF: case IDENT: case QUOTED_IDENTIFIER: return QIdentifier.create(node); @@ -1942,6 +1951,33 @@ class delete elements fetch indices insert into limit new set update versioned b "SELECT a, GROUP_CONCAT(DISTINCT b, CHR(10)) FROM R GROUP BY a", "SELECT GROUP_CONCAT(b) FROM R GROUP BY a", + "SELECT * FROM EXPANCESTORSOF((SELECT 1), 10) AS X", + "SELECT * FROM EXPDESCENDANTSOF((SELECT 1), Depth < 5) AS X", + "SELECT * FROM exp.Materials WHERE expObject() IN EXPLINEAGEOF((SELECT 1), 10)", + "SELECT * FROM exp.Materials WHERE expObject() IN EXPLINEAGEOF((SELECT 1), Depth < 10)", + + "SELECT\n" + + " Lineage.SampleType,\n" + + " COUNT(CASE WHEN Lineage.IsAliquot = FALSE OR Lineage.IsAliquot IS NULL THEN 1 END) AS Samples,\n" + + " COUNT(CASE WHEN Lineage.IsAliquot = TRUE THEN 1 END) AS Aliquots\n" + + "FROM (\n" + + " -- Ancestors\n" + + " SELECT\n" + + " M.SampleSet.Name AS SampleType,\n" + + " M.IsAliquot,\n" + + " M.RowId\n" + + " FROM exp.Materials M\n" + + " WHERE\n" + + " M.SampleSet.Name IN ('SampleType_01','SampleType_02','Study_SampleType')\n" + + " AND M.expObject() IN EXPLINEAGEOF (\n" + + " SELECT DD.expObject()\n" + + " FROM exp.Data DD\n" + + " WHERE DD.lsid = 'urn:lsid:labkey.com:Data.Folder-24:55732295-d9f4-103e-8274-268b9ecad361'\n" + + " )\n" + + " ) AS Lineage\n" + + "GROUP BY Lineage.SampleType\n" + + "ORDER BY Lineage.SampleType", + "BROKEN", // nested JOINS From 463d16c8a7545b4432d466707c1395f2c12708a1 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 22 Jan 2026 17:33:02 -0800 Subject: [PATCH 2/4] Undo change --- query/src/org/labkey/query/sql/SqlParser.java | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/query/src/org/labkey/query/sql/SqlParser.java b/query/src/org/labkey/query/sql/SqlParser.java index 2d2e4180829..bd70f910f41 100644 --- a/query/src/org/labkey/query/sql/SqlParser.java +++ b/query/src/org/labkey/query/sql/SqlParser.java @@ -1951,33 +1951,6 @@ class delete elements fetch indices insert into limit new set update versioned b "SELECT a, GROUP_CONCAT(DISTINCT b, CHR(10)) FROM R GROUP BY a", "SELECT GROUP_CONCAT(b) FROM R GROUP BY a", - "SELECT * FROM EXPANCESTORSOF((SELECT 1), 10) AS X", - "SELECT * FROM EXPDESCENDANTSOF((SELECT 1), Depth < 5) AS X", - "SELECT * FROM exp.Materials WHERE expObject() IN EXPLINEAGEOF((SELECT 1), 10)", - "SELECT * FROM exp.Materials WHERE expObject() IN EXPLINEAGEOF((SELECT 1), Depth < 10)", - - "SELECT\n" + - " Lineage.SampleType,\n" + - " COUNT(CASE WHEN Lineage.IsAliquot = FALSE OR Lineage.IsAliquot IS NULL THEN 1 END) AS Samples,\n" + - " COUNT(CASE WHEN Lineage.IsAliquot = TRUE THEN 1 END) AS Aliquots\n" + - "FROM (\n" + - " -- Ancestors\n" + - " SELECT\n" + - " M.SampleSet.Name AS SampleType,\n" + - " M.IsAliquot,\n" + - " M.RowId\n" + - " FROM exp.Materials M\n" + - " WHERE\n" + - " M.SampleSet.Name IN ('SampleType_01','SampleType_02','Study_SampleType')\n" + - " AND M.expObject() IN EXPLINEAGEOF (\n" + - " SELECT DD.expObject()\n" + - " FROM exp.Data DD\n" + - " WHERE DD.lsid = 'urn:lsid:labkey.com:Data.Folder-24:55732295-d9f4-103e-8274-268b9ecad361'\n" + - " )\n" + - " ) AS Lineage\n" + - "GROUP BY Lineage.SampleType\n" + - "ORDER BY Lineage.SampleType", - "BROKEN", // nested JOINS From 2c60d59920652e8c1fa5b5f07412bb794daea5ff Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Fri, 23 Jan 2026 15:42:33 -0800 Subject: [PATCH 3/4] Review feedback --- .../src/org/labkey/query/sql/QInLineage.java | 37 +++++++---- query/src/org/labkey/query/sql/QNode.java | 36 ++++++++-- query/src/org/labkey/query/sql/SqlParser.java | 66 ++++++++----------- 3 files changed, 84 insertions(+), 55 deletions(-) diff --git a/query/src/org/labkey/query/sql/QInLineage.java b/query/src/org/labkey/query/sql/QInLineage.java index 5244f7f4c7f..3d1c8552247 100644 --- a/query/src/org/labkey/query/sql/QInLineage.java +++ b/query/src/org/labkey/query/sql/QInLineage.java @@ -75,22 +75,24 @@ String operator() @Override public void appendSql(SqlBuilder builder, Query query) { - SQLTableInfo sqlti = new SQLTableInfo(query.getSchema().getDbSchema(), "_"); var children = childList(); - var LHS = ((QExpr) getFirstChild()); - var RHS = ((QQuery) children.get(1)); + var LHS = (QExpr) firstOrThrow(children); + var RHS = (QQuery) secondOrThrow(children); - // LHS should be a 'lineage object', e.g. the result of calling {ExtTable}.expObject() + // LHS should be a 'lineage object', e.g., the result of calling {ExtTable}.expObject() ColumnInfo lhsCol = null; if (LHS instanceof QueryServiceImpl.QColumnInfo || LHS instanceof QMethodCall) + { + SQLTableInfo sqlti = new SQLTableInfo(query.getSchema().getDbSchema(), "_"); lhsCol = LHS.createColumnInfo(sqlti, "_", query); + } if (lhsCol == null || !Strings.CS.equals(lhsCol.getConceptURI(), BuiltInColumnTypes.EXPOBJECTID_CONCEPT_URI)) { - query.getParseErrors().add(new QueryParseException(operator() + " requires argument to be a lineage object", null, getLine(), getColumn())); + query.getParseErrors().add(new QueryParseException(_method + " requires argument to be a lineage object", null, getLine(), getColumn())); return; } - // RHS should be SELECT with one column of 'lineage object', e.g. the result of calling {ExtTable}.expObject() + // RHS should be SELECT with one column of 'lineage object', e.g., the result of calling {ExtTable}.expObject() QueryRelation r = RHS._select; var map = r.getAllColumns(); var col = map.size() != 1 ? null : map.values().iterator().next(); @@ -99,7 +101,7 @@ public void appendSql(SqlBuilder builder, Query query) col.copyColumnAttributesTo(rhsCol); if (!Strings.CS.equals(rhsCol.getConceptURI(), BuiltInColumnTypes.EXPOBJECTID_CONCEPT_URI)) { - query.getParseErrors().add(new QueryParseException(operator() + " requires argument to be a lineage object", null, getLine(), getColumn())); + query.getParseErrors().add(new QueryParseException(_method + " requires argument to be a lineage object", null, getLine(), getColumn())); return; } @@ -107,10 +109,21 @@ public void appendSql(SqlBuilder builder, Query query) RHS.appendSql(subquery, query); // subquery will have surrounding parens, but the double parens don't cause a problem - int depth = 1_000; // TODO: Not sure why limit to 1,000 here. Underlying query will limit itself based on module properties configuration. - QNode depthExpr = children.size() > 2 ? getLastChild() : null; - if (depthExpr instanceof QNumber n) - depth = n.getValue().intValue(); + // Parse depth argument + int depth = 1_000; + { + QNode depthExpr = child(children, 2); + if (depthExpr != null) + { + if (!(depthExpr instanceof QNumber n)) + { + query.getParseErrors().add(new QueryParseException(_method + " requires second argument to be an integer", null, getLine(), getColumn())); + return; + } + + depth = n.getValue().intValue(); + } + } ExpLineageOptions options = new ExpLineageOptions(_parents, _children, depth); options.setUseObjectIds(true); // expObject() returns objectid not lsid @@ -149,7 +162,7 @@ public JdbcType getJdbcType() @Override public boolean equalsNode(QNode other) { - return (other instanceof QInLineage o) && _in == o._in && Objects.equals(_method, o._method); + return other instanceof QInLineage o && _in == o._in && Objects.equals(_method, o._method); } @Override diff --git a/query/src/org/labkey/query/sql/QNode.java b/query/src/org/labkey/query/sql/QNode.java index 4e61134765f..2a5e272f526 100644 --- a/query/src/org/labkey/query/sql/QNode.java +++ b/query/src/org/labkey/query/sql/QNode.java @@ -18,6 +18,7 @@ import org.antlr.runtime.CommonToken; import org.antlr.runtime.tree.CommonTree; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Assert; import org.junit.Test; @@ -34,6 +35,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import static org.labkey.query.sql.antlr.SqlBaseParser.FALSE; import static org.labkey.query.sql.antlr.SqlBaseParser.IDENT; @@ -109,7 +111,7 @@ public Iterable children() return _children; } - public List childList() + public LinkedList childList() { return _children; } @@ -373,15 +375,12 @@ protected void dump(PrintWriter out, String nl, IdentityHashMap d c.dump(out, nl + " |", dumped); } - - public void addFieldRefs(Object referant) { for (QNode child : childList()) child.addFieldRefs(referant); } - public void releaseFieldRefs(Object referant) { for (QNode child : childList()) @@ -398,6 +397,35 @@ public void setHasTransformableAggregate(boolean hasTransformableAggregate) _hasTransformableAggregate = hasTransformableAggregate; } + static @Nullable QNode child(LinkedList children, int index) + { + return children.size() > index ? children.get(index) : null; + } + + static @NotNull QNode childOrThrow(LinkedList children, int index) + { + return Objects.requireNonNull(child(children, index)); + } + + static QNode first(LinkedList children) + { + return child(children, 0); + } + + static @NotNull QNode firstOrThrow(LinkedList children) + { + return childOrThrow(children, 0); + } + + static QNode second(LinkedList children) + { + return child(children, 1); + } + + static @NotNull QNode secondOrThrow(LinkedList children) + { + return childOrThrow(children, 1); + } public static class TestCase extends Assert { diff --git a/query/src/org/labkey/query/sql/SqlParser.java b/query/src/org/labkey/query/sql/SqlParser.java index bd70f910f41..f32830ec8a8 100644 --- a/query/src/org/labkey/query/sql/SqlParser.java +++ b/query/src/org/labkey/query/sql/SqlParser.java @@ -76,9 +76,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; +import static org.labkey.query.sql.QNode.*; import static org.labkey.query.sql.antlr.SqlBaseParser.*; @@ -890,14 +890,20 @@ else if (divisorType==NUM_DOUBLE || divisorType==NUM_FLOAT || divisorType==NUM_I _parseErrors.add(new QueryParseException("Illegal syntax near 'IN'", null, node.getLine(), node.getCharPositionInLine())); return null; } + + var rhsChildren = rhs.childList(); + if (rhsChildren.size() > 3) + { + _parseErrors.add(new QueryParseException(method.getTokenText().toUpperCase() + " supports at most 2 arguments", null, node.getLine(), node.getCharPositionInLine())); + return null; + } + var qInLineage = new QInLineage(node.getType() == IN, method.getTokenType()); var qInLineageChildren = new LinkedList(); qInLineageChildren.add(lhs); - - var rhsChildren = rhs.childList(); - qInLineageChildren.add(rhsChildren.get(1)); + qInLineageChildren.add(secondOrThrow(rhsChildren)); if (rhsChildren.size() > 2) - qInLineageChildren.add(rhsChildren.get(2)); + qInLineageChildren.add(childOrThrow(rhsChildren, 2)); qInLineage._replaceChildren(qInLineageChildren); return qInLineage; @@ -1520,27 +1526,6 @@ private boolean validateTimestampConstant(QNode n) } } - - private static QNode first(LinkedList children) - { - return !children.isEmpty() ? children.get(0) : null; - } - - private static @NotNull QNode firstOrThrow(LinkedList children) - { - return Objects.requireNonNull(first(children)); - } - - private static QNode second(LinkedList children) - { - return children.size() > 1 ? children.get(1) : null; - } - - private static @NotNull QNode secondOrThrow(LinkedList children) - { - return Objects.requireNonNull(second(children)); - } - private QNode constantToStringNode(QNode node) { if (node instanceof QString) @@ -1901,7 +1886,6 @@ class delete elements fetch indices insert into limit new set update versioned b "SELECT CASE R.a WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'few' END FROM R", "SELECT R.a FROM R WHERE R.a LIKE 'a%'", -// "SELECT R.a FROM R WHERE R.a LIKE 'a%' AND R.b LIKE 'a/%' ESCAPE '/'", "SELECT MS2SearchRuns.Flag,MS2SearchRuns.Links,MS2SearchRuns.Name,MS2SearchRuns.Created,MS2SearchRuns.RunGroups FROM MS2SearchRuns", @@ -1918,12 +1902,6 @@ class delete elements fetch indices insert into limit new set update versioned b "SELECT R.value AS V FROM R WHERE R.y > (SELECT MAX(S.y) FROM S WHERE S.x=R.x)", "SELECT R.value, T.a, T.b FROM R INNER JOIN (SELECT S.a, S.b FROM S) T ON R.z=T.z", -// "SELECT R.a FROM R WHERE EXISTS (SELECT S.b FROM S WHERE S.x=R.x)", -// "SELECT R.a FROM R WHERE NOT EXISTS (SELECT S.b FROM S WHERE S.x=R.x)", -// "SELECT R.a FROM R WHERE R.value > ALL (SELECT value from S WHERE S.x=R.x)", -// "SELECT R.a FROM R WHERE R.value > ANY (SELECT value from S WHERE S.x=R.x)", -// "SELECT R.a FROM R WHERE R.value > SOME (SELECT value from S WHERE S.x=R.x)", - "SELECT a FROM R WHERE a=b AND b<>c AND b!=c AND c>d AND d=g AND g IS NULL AND h IS NOT NULL " + " AND i BETWEEN 1 AND 2 AND j+k-l=-1 AND m/n=o AND p||q=r AND (NOT s OR t) AND u LIKE '%x%' AND u NOT LIKE '%xx%' " + " AND v IN (1,2) AND v NOT IN (3,4) AND x&y=1 AND x|y=1 AND x^y=1", @@ -1951,8 +1929,6 @@ class delete elements fetch indices insert into limit new set update versioned b "SELECT a, GROUP_CONCAT(DISTINCT b, CHR(10)) FROM R GROUP BY a", "SELECT GROUP_CONCAT(b) FROM R GROUP BY a", - "BROKEN", - // nested JOINS "SELECT R.a, \"S\".b FROM R LEFT OUTER JOIN (S RIGHT OUTER JOIN T ON S.y = T.y) ON R.x = S.x", // .* @@ -1961,10 +1937,24 @@ class delete elements fetch indices insert into limit new set update versioned b // PIVOT "SELECT R.a, R.b, SUM(x) sumX FROM R GROUP BY R.a, R.b PIVOT sumX BY b", "SELECT R.a, R.b, SUM(x) sumX FROM R GROUP BY R.a, R.b PIVOT sumX BY b IN (0,1,2)", - "SELECT R.a, R.b, SUM(x) sumX FROM R GROUP BY R.a, R.b PIVOT sumX BY b IN (0 AS Zero,1 ONE,2 TWO)" + "SELECT R.a, R.b, SUM(x) sumX FROM R GROUP BY R.a, R.b PIVOT sumX BY b IN (0 AS Zero,1 ONE,2 TWO)", + + // EXPANCESTORSOF + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPANCESTORSOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0)", + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPANCESTORSOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0, 2)", + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPANCESTORSOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0, 2000)", + + // EXPDESCENDANTSOF + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPDESCENDANTSOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0)", + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPDESCENDANTSOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0, -2)", + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPDESCENDANTSOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0, 2000)", + + // EXPLINEAGEOF + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPLINEAGEOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0)", + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPLINEAGEOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0, 2)", + "SELECT M.RowId, M.Name FROM exp.Materials M WHERE M.expObject() IN EXPLINEAGEOF (SELECT DD.expObject() FROM exp.Data DD WHERE DD.RowId > 0, 2000)" }; - static String[] failSql = new String[] { "", @@ -1989,8 +1979,6 @@ class delete elements fetch indices insert into limit new set update versioned b "SELECT * FROM (WITH peeps AS (SELECT * FROM study.participant) SELECT * FROM peeps)" }; - - @SuppressWarnings("JUnitMalformedDeclaration") public static class SqlParserTestCase extends Assert { From e49569f6245c23d37fea98c4bfe294cbff86a6af Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Fri, 23 Jan 2026 17:41:30 -0800 Subject: [PATCH 4/4] Undo container filter (already applied) --- experiment/src/org/labkey/experiment/ExperimentUpgradeCode.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/experiment/src/org/labkey/experiment/ExperimentUpgradeCode.java b/experiment/src/org/labkey/experiment/ExperimentUpgradeCode.java index cbfed9e3bb7..d67dc76503c 100644 --- a/experiment/src/org/labkey/experiment/ExperimentUpgradeCode.java +++ b/experiment/src/org/labkey/experiment/ExperimentUpgradeCode.java @@ -63,14 +63,12 @@ import org.labkey.api.security.roles.SiteAdminRole; import org.labkey.api.settings.AppProps; import org.labkey.api.util.logging.LogHelper; -import org.labkey.experiment.api.ClosureQueryHelper; import org.labkey.experiment.api.ExpSampleTypeImpl; import org.labkey.experiment.api.ExperimentServiceImpl; import org.labkey.experiment.api.MaterialSource; import org.labkey.experiment.api.property.DomainImpl; import org.labkey.experiment.api.property.DomainPropertyImpl; import org.labkey.experiment.api.property.StorageProvisionerImpl; -import org.labkey.experiment.samples.SampleTimelineAuditProvider; import java.sql.Connection; import java.sql.SQLException;