diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilder.java b/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilder.java index 58a2c533e..7ad4ed0c4 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilder.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.expression; @@ -2043,6 +2043,36 @@ public interface ModifyPlan extends PreparePlan, PlanBuilderBase.ModifyPlanBase * @since 7.2.0; requires MarkLogic 12 */ public abstract ModifyPlan shortestPath(PlanExprCol start, PlanExprCol end, PlanExprCol path, PlanExprCol length, PlanExprCol weight); +/** + * This method performs a transitive closure operation over a graph-like structure, identifying all reachable node pairs from a given start node to an end node through one or more intermediate steps. A set of (start, end) node pairs where a path exists between them with a length between minLength and maxLength, inclusive. This models the SPARQL one-or-more (+) operator, enabling recursive or chained relationships to be queried efficiently. + * @param start The column is the starting node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @param end The column is the end node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @return a ModifyPlan object + */ + public abstract ModifyPlan transitiveClosure(String start, String end); +/** + * This method performs a transitive closure operation over a graph-like structure, identifying all reachable node pairs from a given start node to an end node through one or more intermediate steps. A set of (start, end) node pairs where a path exists between them with a length between minLength and maxLength, inclusive. This models the SPARQL one-or-more (+) operator, enabling recursive or chained relationships to be queried efficiently. + * @param start The column is the starting node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @param end The column is the end node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @return a ModifyPlan object + */ + public abstract ModifyPlan transitiveClosure(PlanExprCol start, PlanExprCol end); +/** + * This method performs a transitive closure operation over a graph-like structure, identifying all reachable node pairs from a given start node to an end node through one or more intermediate steps. A set of (start, end) node pairs where a path exists between them with a length between minLength and maxLength, inclusive. This models the SPARQL one-or-more (+) operator, enabling recursive or chained relationships to be queried efficiently. + * @param start The column is the starting node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @param end The column is the end node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @param options This is either an array of strings or an object containing keys and values for the options to this operator. Options include: min-length This option is the minimum number of steps (edges) required in the path. It should be a non-negative integer, and the default is 1.max-length This option Maximum number of steps (edges) allowed in the path. It should be a non-negative integer, and the default is unlimited. + * @return a ModifyPlan object + */ + public abstract ModifyPlan transitiveClosure(String start, String end, PlanTransitiveClosureOptions options); +/** + * This method performs a transitive closure operation over a graph-like structure, identifying all reachable node pairs from a given start node to an end node through one or more intermediate steps. A set of (start, end) node pairs where a path exists between them with a length between minLength and maxLength, inclusive. This models the SPARQL one-or-more (+) operator, enabling recursive or chained relationships to be queried efficiently. + * @param start The column is the starting node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @param end The column is the end node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. See {@link PlanBuilder#col(XsStringVal)} + * @param options This is either an array of strings or an object containing keys and values for the options to this operator. Options include: min-length This option is the minimum number of steps (edges) required in the path. It should be a non-negative integer, and the default is 1.max-length This option Maximum number of steps (edges) allowed in the path. It should be a non-negative integer, and the default is unlimited. + * @return a ModifyPlan object + */ + public abstract ModifyPlan transitiveClosure(PlanExprCol start, PlanExprCol end, PlanTransitiveClosureOptions options); } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilderBase.java b/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilderBase.java index 61af7fcfa..378f7ae43 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilderBase.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/expression/PlanBuilderBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.expression; @@ -356,6 +356,15 @@ public interface PlanBuilderBase { */ PlanSparqlOptions sparqlOptions(); + /** + * Provides a transitive closure option object to configure the execution of the + * {@link PlanBuilder.ModifyPlan#transitiveClosure(PlanExprCol, PlanExprCol, PlanTransitiveClosureOptions)} + * operator. Use the fluent methods of the transitive closure option object + * to set the configuration. + * @return the configuration object + */ + PlanTransitiveClosureOptions transitiveClosureOptions(); + /** * Specifies a JavaScript or XQuery function installed on the server for use * in post-processing in a map() or reduce() operation. diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderImpl.java index 77c97d02c..61e095d2d 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.impl; @@ -2268,6 +2268,42 @@ public ModifyPlan shortestPath(PlanExprCol start, PlanExprCol end, PlanExprCol p } + @Override + public ModifyPlan transitiveClosure(String start, String end) { + return transitiveClosure((start == null) ? (PlanExprCol) null : exprCol(start), (end == null) ? null : exprCol(end)); + } + + + @Override + public ModifyPlan transitiveClosure(PlanExprCol start, PlanExprCol end) { + if (start == null) { + throw new IllegalArgumentException("start parameter for transitiveClosure() cannot be null"); + } + if (end == null) { + throw new IllegalArgumentException("end parameter for transitiveClosure() cannot be null"); + } + return new PlanBuilderSubImpl.ModifyPlanSubImpl(this, "op", "transitive-closure", new Object[]{ start, end }); + } + + + @Override + public ModifyPlan transitiveClosure(String start, String end, PlanTransitiveClosureOptions options) { + return transitiveClosure((start == null) ? null : exprCol(start), (end == null) ? null : exprCol(end), options); + } + + + @Override + public ModifyPlan transitiveClosure(PlanExprCol start, PlanExprCol end, PlanTransitiveClosureOptions options) { + if (start == null) { + throw new IllegalArgumentException("start parameter for transitiveClosure() cannot be null"); + } + if (end == null) { + throw new IllegalArgumentException("end parameter for transitiveClosure() cannot be null"); + } + return new PlanBuilderSubImpl.ModifyPlanSubImpl(this, "op", "transitive-closure", new Object[]{ start, end, PlanBuilderSubImpl.asArg(PlanBuilderSubImpl.makeMap(options)) }); + } + + @Override public ModifyPlan union(ModifyPlan right) { if (right == null) { diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderSubImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderSubImpl.java index 9a320ab43..3c11bbf38 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderSubImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/PlanBuilderSubImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.impl; @@ -487,6 +487,10 @@ public PlanSampleByOptions withLimit(XsIntVal limit) { public PlanSparqlOptions sparqlOptions() { return new PlanSparqlOptionsImpl(this); } + @Override + public PlanTransitiveClosureOptions transitiveClosureOptions() { + return new PlanTransitiveClosureOptionsImpl(this); + } static class PlanSparqlOptionsImpl implements PlanSparqlOptions { private PlanBuilderBaseImpl pb; private XsBooleanVal deduplicate; @@ -526,6 +530,45 @@ public PlanSparqlOptions withDeduplicated(XsBooleanVal deduplicate) { } } + static class PlanTransitiveClosureOptionsImpl implements PlanTransitiveClosureOptions { + private PlanBuilderBaseImpl pb; + private XsLongVal minLength; + private XsLongVal maxLength; + PlanTransitiveClosureOptionsImpl(PlanBuilderBaseImpl pb) { + this.pb = pb; + } + PlanTransitiveClosureOptionsImpl(PlanBuilderBaseImpl pb, XsLongVal minLength, XsLongVal maxLength) { + this(pb); + this.minLength = minLength; + this.maxLength = maxLength; + } + + @Override + public XsLongVal getMinLength() { + return minLength; + } + @Override + public PlanTransitiveClosureOptions withMinLength(long minLength) { + return withMinLength(pb.xs.longVal(minLength)); + } + @Override + public PlanTransitiveClosureOptions withMinLength(XsLongVal minLength) { + return new PlanTransitiveClosureOptionsImpl(this.pb, minLength, this.maxLength); + } + @Override + public XsLongVal getMaxLength() { + return maxLength; + } + @Override + public PlanTransitiveClosureOptions withMaxLength(long maxLength) { + return withMaxLength(pb.xs.longVal(maxLength)); + } + @Override + public PlanTransitiveClosureOptions withMaxLength(XsLongVal maxLength) { + return new PlanTransitiveClosureOptionsImpl(this.pb, this.minLength, maxLength); + } + } + @Override public ServerExpression caseExpr(PlanCase... cases) { int lastPos = cases.length - 1; @@ -744,6 +787,29 @@ static Map makeMap(PlanSparqlOptions options) { return mapdef; } + static Map makeMap(PlanTransitiveClosureOptions options) { + if (options == null) { + return null; + } + + Map mapdef = null; + + XsLongVal minLength = options.getMinLength(); + if (minLength != null) { + mapdef = new HashMap<>(); + mapdef.put("minLength", minLength.getLong()); + } + + XsLongVal maxLength = options.getMaxLength(); + if (maxLength != null) { + if (mapdef == null) { + mapdef = new HashMap<>(); + } + mapdef.put("maxLength", maxLength.getLong()); + } + + return mapdef; + } static Map makeMap(String key, String value) { Map map = new HashMap(); if (key != null) { diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/type/PlanTransitiveClosureOptions.java b/marklogic-client-api/src/main/java/com/marklogic/client/type/PlanTransitiveClosureOptions.java new file mode 100644 index 000000000..3772571d9 --- /dev/null +++ b/marklogic-client-api/src/main/java/com/marklogic/client/type/PlanTransitiveClosureOptions.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + */ +package com.marklogic.client.type; + +// IMPORTANT: Do not edit. This file is generated. + +/** + * Options for controlling transitive closure operations, including minimum and maximum + * path lengths. + */ +public interface PlanTransitiveClosureOptions { + XsLongVal getMinLength(); + PlanTransitiveClosureOptions withMinLength(long minLength); + PlanTransitiveClosureOptions withMinLength(XsLongVal minLength); + XsLongVal getMaxLength(); + PlanTransitiveClosureOptions withMaxLength(long maxLength); + PlanTransitiveClosureOptions withMaxLength(XsLongVal maxLength); +} diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/OpticTransitiveClosureTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/OpticTransitiveClosureTest.java new file mode 100644 index 000000000..d6f04202a --- /dev/null +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/OpticTransitiveClosureTest.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + */ + +package com.marklogic.client.test.rows; + +import com.marklogic.client.row.RowRecord; +import com.marklogic.client.row.RowTemplate; +import com.marklogic.client.test.Common; +import com.marklogic.client.type.PlanTransitiveClosureOptions; +import com.marklogic.client.type.PlanTripleOption; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the transitiveClosure Optic function introduced in MarkLogic 12. + * This test class verifies the transitive closure operation over graph-like structures, + * identifying all reachable node pairs from a given start node to an end node through + * one or more intermediate steps. The tests use a parent-child relationship graph + * loaded from transClosureTripleSet.xml using mlDeploy during setup. + */ +public class OpticTransitiveClosureTest { + + @BeforeAll + public static void setUp() { + Common.connect(); + } + + /** + * Simple full transitive closure without options. + * Expects 21 rows with person and ancestor columns. + */ + @Test + void testSimplePatternFullTransitiveClosure() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("ancestor") + ), + null, + (String) null, + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure(op.col("person"), op.col("ancestor")) + .orderBy(op.sortKeySeq(op.asc("ancestor"), op.asc("person"))), + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + assertEquals(21, rowList.size(), "Expected 21 rows for full transitive closure"); + + // Verify first row has required columns + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist"); + + return null; + }); + } + + /** + * Transitive closure with minLength=2 (grandparents and up). + * This excludes direct parent-child relationships. + * Expects 12 rows. + */ + @Test + void testTransitiveClosureWithMinLength() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> { + // Create options with minLength=2 + PlanTransitiveClosureOptions options = op.transitiveClosureOptions() + .withMinLength(2); + + return op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("ancestor") + ), + null, + (String) null, + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure(op.col("person"), op.col("ancestor"), options); + }, + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + // 2 steps or more excludes direct parent-child relationships + assertEquals(12, rowList.size(), "Expected 12 rows with minLength=2 (grandparents and up)"); + + // Verify columns exist + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist"); + + return null; + }); + } + + /** + * Transitive closure with minLength=2 and maxLength=2 (grandparents only). + * Expects 6 rows. + */ + @Test + void testTransitiveClosureWithMinAndMaxLength() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> { + // Create options with minLength=2 and maxLength=2 + PlanTransitiveClosureOptions options = op.transitiveClosureOptions() + .withMinLength(2) + .withMaxLength(2); + + return op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("ancestor") + ), + null, + "http://test.optic.tc#", + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure(op.col("person"), op.col("ancestor"), options); + }, + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + // 2 steps only is grandparent relationships only + assertEquals(6, rowList.size(), "Expected 6 rows with minLength=2 and maxLength=2 (grandparents only)"); + + // Verify columns exist + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist"); + + return null; + }); + } + + /** + * Transitive closure with column renamed using op.as(). + * Uses "parent" column and renames it to "ancestor". + * Expects 21 rows. + */ + @Test + void testTransitiveClosureWithColumnRename() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("parent") + ), + null, + (String) null, + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure(op.col("person"), op.as("ancestor", op.col("parent"))), + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + assertEquals(21, rowList.size(), "Expected 21 rows with renamed column"); + + // Verify renamed column exists + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist (renamed from parent)"); + + return null; + }); + } + + /** + * Transitive closure with joins to get labels. + * Joins with label triples to get human-readable names. + * Expects 21 rows with person, ancestor, person_name, and ancestor_name columns. + */ + @Test + void testTransitiveClosureWithJoinsForLabels() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> { + var labelIri = op.sem.iri("http://test.optic.tc#label"); + return op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("ancestor") + ) + , (String)null, + "http://test.optic.tc#", + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure(op.col("person"), op.col("ancestor")) + .joinLeftOuter( + op.fromTriples( + op.pattern(op.col("person"), labelIri, op.col("person_name")), + null, + "http://test.optic.tc#", + PlanTripleOption.DEDUPLICATED + ) + ) + .joinLeftOuter( + op.fromTriples( + op.pattern(op.col("ancestor"), labelIri, op.col("ancestor_name")), + null, + "http://test.optic.tc#", + PlanTripleOption.DEDUPLICATED + ) + ); + }, + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + assertEquals(21, rowList.size(), "Expected 21 rows with joined labels"); + + // Verify all columns exist + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist"); + assertNotNull(firstRow.get("person_name"), "person_name column should exist"); + assertNotNull(firstRow.get("ancestor_name"), "ancestor_name column should exist"); + + return null; + }); + } + + /** + * Test 6: Transitive closure using string column names instead of column expressions. + * This tests the convenience overload that accepts String parameters. + * Expects 21 rows. + */ + @Test + void testTransitiveClosureWithStringColumnNames() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("ancestor") + ), + null, + (String) null, + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure("person", "ancestor") + .orderBy(op.sortKeySeq(op.asc("ancestor"), op.asc("person"))), + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + assertEquals(21, rowList.size(), "Expected 21 rows using string column names"); + + // Verify columns exist + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist"); + + return null; + }); + } + + /** + * Test 7: Transitive closure with string column names and options. + * Expects 12 rows with minLength=2. + */ + @Test + void testTransitiveClosureWithStringNamesAndOptions() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> op.fromTriples( + op.pattern( + op.col("person"), + op.sem.iri("http://marklogic.com/transitiveClosure/parent"), + op.col("ancestor") + ), + null, + (String) null, + PlanTripleOption.DEDUPLICATED + ) + .transitiveClosure("person", "ancestor", op.transitiveClosureOptions().withMinLength(2)), + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + assertEquals(12, rowList.size(), "Expected 12 rows with string names and minLength=2"); + + return null; + }); + } + + /** + * Test 8: Transitive closure starting from a SPARQL query with minLength=2 + * Expects 21 rows. This is harder to do in SPARQL directly, so we do the closure in Optic. + */ + @Test + void testTransitiveClosureFromSparql() { + if (Common.getMarkLogicVersion().getMajor() < 12) { + return; + } + + new RowTemplate(Common.client).query(op -> op.fromSparql( + "SELECT ?person ?ancestor WHERE { ?person ?ancestor }" + ) + .transitiveClosure(op.col("person"), op.col("ancestor"), op.transitiveClosureOptions().withMinLength(2)) + .orderBy(op.sortKeySeq(op.asc("ancestor"), op.asc("person"))), + rows -> { + List rowList = new ArrayList<>(); + rows.forEach(rowList::add); + + assertEquals(12, rowList.size(), "Expected 12 rows for transitive closure from SPARQL with minLength=2 (grandparents and farther)"); + + // Verify first row has required columns + RowRecord firstRow = rowList.get(0); + assertNotNull(firstRow.get("person"), "person column should exist"); + assertNotNull(firstRow.get("ancestor"), "ancestor column should exist"); + + return null; + }); + } +} diff --git a/test-app/src/main/ml-data/optic/transitive-closure/collections.properties b/test-app/src/main/ml-data/optic/transitive-closure/collections.properties new file mode 100644 index 000000000..c64199b99 --- /dev/null +++ b/test-app/src/main/ml-data/optic/transitive-closure/collections.properties @@ -0,0 +1 @@ +transClosureTripleSet.xml=http://test.optic.tc#,test-data diff --git a/test-app/src/main/ml-data/optic/transitive-closure/permissions.properties b/test-app/src/main/ml-data/optic/transitive-closure/permissions.properties new file mode 100644 index 000000000..f775de1ee --- /dev/null +++ b/test-app/src/main/ml-data/optic/transitive-closure/permissions.properties @@ -0,0 +1 @@ +*=rest-reader,read,rest-writer,update,app-user,read,app-builder,read,app-builder,update diff --git a/test-app/src/main/ml-data/optic/transitive-closure/transClosureTripleSet.xml b/test-app/src/main/ml-data/optic/transitive-closure/transClosureTripleSet.xml new file mode 100644 index 000000000..c2130bdfe --- /dev/null +++ b/test-app/src/main/ml-data/optic/transitive-closure/transClosureTripleSet.xml @@ -0,0 +1,107 @@ + + + + + +http://test.optic.tc#Alice +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Bob + + +http://test.optic.tc#Bob +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Carol + + +http://test.optic.tc#Carol +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#David + + +http://test.optic.tc#David +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Eve + + +http://test.optic.tc#Eve +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Frank + + +http://test.optic.tc#George +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Helen + + +http://test.optic.tc#Helen +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Ian + + +http://test.optic.tc#Alice +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#Cindy + + +http://test.optic.tc#Cindy +http://marklogic.com/transitiveClosure/parent +http://test.optic.tc#John + + +http://test.optic.tc#Alice +http://test.optic.tc#label +Alice + + +http://test.optic.tc#Bob +http://test.optic.tc#label +Bob + + +http://test.optic.tc#Eve +http://test.optic.tc#label +Eve + + +http://test.optic.tc#Cindy +http://test.optic.tc#label +Cindy + + +http://test.optic.tc#Helen +http://test.optic.tc#label +Helen + + +http://test.optic.tc#Ian +http://test.optic.tc#label +Ian + + +http://test.optic.tc#John +http://test.optic.tc#label +John + + +http://test.optic.tc#David +http://test.optic.tc#label +David + + +http://test.optic.tc#George +http://test.optic.tc#label +George + + +http://test.optic.tc#Carol +http://test.optic.tc#label +Carol + + +http://test.optic.tc#Frank +http://test.optic.tc#label +Frank + + + +