Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Project Guidelines

## Architecture
- `marklogic-client-api`: core Java client library for interacting with the MarkLogic REST API.
- `ml-development-tools`: Gradle plugin and generators for Data Services endpoint proxies/tests.
- `marklogic-client-api-functionaltests`: functional/regression-style tests, split into fragile/fast/slow groups.
- `test-app`: ml-gradle deployment project that provisions test infrastructure in MarkLogic.
- `examples`: supporting code used by tests and usage examples.

## Build And Test
- Prefer Gradle from the repo root.
- Quick compile verification: `./gradlew clean build -x test`
- Core module tests: `./gradlew marklogic-client-api:test`
- Plugin tests (includes generated tests workflow): `./gradlew ml-development-tools:test`
- Functional tests must run in this order to reduce flakiness:
1. `./gradlew marklogic-client-api-functionaltests:runFragileTests`
2. `./gradlew marklogic-client-api-functionaltests:runFastFunctionalTests`
3. `./gradlew marklogic-client-api-functionaltests:runSlowFunctionalTests`

## Test Environment
- Java 17+ is required for current releases.
- Most tests require a running MarkLogic instance and deployed test resources.
- Typical setup sequence:
1. `docker compose up -d --build`
2. `./gradlew -i mlWaitTillReady`
3. `./gradlew -i mlDeploy`
4. Run module tests
- Override local MarkLogic connection settings via `gradle-local.properties` (`mlHost`, `mlPassword`).

## Quality Controls
- Treat compile warnings as failures: project builds enforce `-Xlint:unchecked`, `-Xlint:deprecation`, and `-Werror`.
- Keep dependency security constraints intact (e.g. forced/excluded dependencies for CVE mitigation in Gradle files).
- When adding or changing first-party Java/Kotlin code, run security scanning steps used by this workspace workflow before finalizing changes.
- Do not relax quality gates (tests/compilation) to make a change pass; fix the underlying issue.

## Code Generation And Automation
- Data Services proxy generation is automated; use `generateEndpointProxies` instead of hand-writing proxy classes.
- `ml-development-tools` test automation uses `generateTests` and `fixMjsModulesForMarkLogic12` before `test`.
- Generated sources commonly include an "IMPORTANT: Do not edit" header. Regenerate from source declarations instead of editing generated output directly.
- For changes affecting generation logic, validate both generator behavior and generated artifact compilation/tests.

## Conventions For Changes
- Keep edits scoped to the target module; avoid cross-module churn unless required.
- Prefer existing patterns in nearby code over introducing new abstractions.
- For test-related fixes, document whether behavior changes impact unit tests, functional tests, or deployment setup.

## Docs To Link (Do Not Duplicate)
- `README.md`: product overview, dependency usage, Java compatibility.
- `CONTRIBUTING.md`: local build/test workflow and MarkLogic test setup.
- `ml-development-tools/src/test/example-project/README.md`: plugin-focused usage/testing notes.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -38,6 +38,7 @@
// 2023-10-24 Exception: Manual changes have been made to this to expose the string constructors for cts.point and
// cts.polygon. These changes can be removed once optic-defs.json in the xdmp repository is updated to define these
// constructors.
// 2026-04-15 Exception: Manual changes have been made to expose cts:param prior to generator support.

/**
* Builds expressions to call functions in the cts server library for a row
Expand Down Expand Up @@ -2545,6 +2546,24 @@ public interface CtsExpr {
* @return a server expression with the <a href="{@docRoot}/doc-files/types/cts_query.html">cts:query</a> server data type
*/
public CtsQueryExpr orQuery(ServerExpression queries, XsStringSeqVal options);
/**
* Returns a parameter placeholder for a cts expression.
*
* <p>
* Provides a client interface to the <a href="http://docs.marklogic.com/cts:param" target="mlserverdoc">cts:param</a> server function.
* @param name The parameter name. (of <a href="{@docRoot}/doc-files/types/xs_string.html">xs:string</a>)
* @return a server expression with the <a href="{@docRoot}/doc-files/types/xs_anyAtomicType.html">xs:anyAtomicType</a> server data type
*/
public ServerExpression param(String name);
/**
* Returns a parameter placeholder for a cts expression.
*
* <p>
* Provides a client interface to the <a href="http://docs.marklogic.com/cts:param" target="mlserverdoc">cts:param</a> server function.
* @param name The parameter name. (of <a href="{@docRoot}/doc-files/types/xs_string.html">xs:string</a>)
* @return a server expression with the <a href="{@docRoot}/doc-files/types/xs_anyAtomicType.html">xs:anyAtomicType</a> server data type
*/
public ServerExpression param(XsStringVal name);
Comment thread
rjdew-progress marked this conversation as resolved.
/**
* Returns the part of speech for a cts:token, if any.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -150,7 +150,7 @@ static class ServerExpressionListImpl extends BaseListImpl<BaseArgImpl> implemen
}
static class ServerExpressionCallImpl extends BaseCallImpl<BaseArgImpl> implements ServerExpression {
ServerExpressionCallImpl(String fnPrefix, String fnName, Object[] fnArgs) {
super(fnPrefix, fnName, convertList(fnArgs));
super(fnPrefix, fnName, convertList(validateNoOpticParamInCtsCall(fnPrefix, fnName, fnArgs)));
}
}

Expand Down Expand Up @@ -394,6 +394,46 @@ static private void astifyObject(StringBuilder strb, Object value) {
}
}

static private Object[] validateNoOpticParamInCtsCall(String fnPrefix, String fnName, Object[] fnArgs) {
if (!"cts".equals(fnPrefix) || fnArgs == null) {
return fnArgs;
}
if (containsPlanParam(fnArgs)) {
throw new IllegalArgumentException(
"Cannot pass op:param() to cts:" + fnName + "(). Use cts:param() for cts namespace expressions."
);
}
return fnArgs;
}

static private boolean containsPlanParam(Object value) {
if (value == null) {
return false;
}
if (value instanceof PlanParamExpr) {
return true;
}
if (value instanceof Object[]) {
for (Object item : (Object[]) value) {
if (containsPlanParam(item)) {
return true;
}
}
return false;
}
if (value instanceof BaseListImpl) {
return containsPlanParam(((BaseListImpl<?>) value).getArgsImpl());
}
if (value instanceof BaseMapImpl) {
return containsPlanParam(((BaseMapImpl) value).getMap().values().toArray());
}
if (value instanceof java.util.Map<?, ?>) {
java.util.Map<?, ?> mapValue = (java.util.Map<?, ?>) value;
return containsPlanParam(mapValue.keySet().toArray()) || containsPlanParam(mapValue.values().toArray());
}
return false;
}

static BaseArgImpl[] convertList(Object[] items) {
return convertList(items, BaseArgImpl.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,6 +41,7 @@
// 2023-10-24 Exception: Manual changes have been made to this to expose the string constructors for cts.point and
// cts.polygon. These changes can be removed once optic-defs.json in the xdmp repository is updated to define these
// constructors.
// 2026-04-15 Exception: Manual changes have been made to expose cts:param prior to generator support.

class CtsExprImpl implements CtsExpr {

Expand Down Expand Up @@ -1684,6 +1685,24 @@ public CtsQueryExpr orQuery(ServerExpression queries, XsStringSeqVal options) {
}


@Override
public ServerExpression param(String name) {
if (name == null) {
throw new IllegalArgumentException("name parameter for param() cannot be null");
}
return param(new XsValueImpl.StringValImpl(name));
}


@Override
public ServerExpression param(XsStringVal name) {
if (name == null) {
throw new IllegalArgumentException("name parameter for param() cannot be null");
}
return new XsExprImpl.AnyAtomicTypeCallImpl("cts", "param", new Object[]{ name });
Comment thread
rjdew-progress marked this conversation as resolved.
}


@Override
public ServerExpression partOfSpeech(ServerExpression token) {
if (token == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ public PlanPrefixer prefixer(String base) {
public PlanParamExpr param(String name) {
return new PlanParamBase(name);
}

@Override
public PlanParamExpr param(XsStringVal name) {
if (name == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
package com.marklogic.client.impl;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.marklogic.client.expression.PlanBuilder;
import com.marklogic.client.io.JacksonHandle;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class CtsParamExprTest {

@Test
void exportsCtsParamInCollectionQuery() {
PlanBuilderSubImpl p = new PlanBuilderSubImpl();

PlanBuilder.ModifyPlan employeesPlan = p
.fromView("main", "employees")
.select(p.col("EmployeeID"), p.col("FirstName"), p.col("LastName"))
.where(p.cts.collectionQuery(p.cts.param("collection")));

JacksonHandle handle = new JacksonHandle();
employeesPlan.export(handle);
ObjectNode exportNode = (ObjectNode) handle.get();

assertEquals("op", exportNode.path("$optic").path("ns").asText());
assertEquals("operators", exportNode.path("$optic").path("fn").asText());
assertEquals("from-view", exportNode.path("$optic").path("args").get(0).path("fn").asText());
assertEquals("select", exportNode.path("$optic").path("args").get(1).path("fn").asText());
assertEquals("where", exportNode.path("$optic").path("args").get(2).path("fn").asText());
assertEquals("collection-query", exportNode.path("$optic").path("args").get(2).path("args").get(0).path("fn").asText());
assertEquals("param", exportNode.path("$optic").path("args").get(2).path("args").get(0).path("args").get(0).path("fn").asText());
assertEquals("cts", exportNode.path("$optic").path("args").get(2).path("args").get(0).path("args").get(0).path("ns").asText());
assertEquals("collection", exportNode.path("$optic").path("args").get(2).path("args").get(0).path("args").get(0).path("args").get(0).asText());
}

@Test
void rejectsOpParamInCtsNamespace() {
PlanBuilderSubImpl p = new PlanBuilderSubImpl();

IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> p.cts.collectionQuery(p.param("collection"))
);

assertEquals(
"Cannot pass op:param() to cts:collection-query(). Use cts:param() for cts namespace expressions.",
ex.getMessage()
);
}
}
Loading