From 9c03d920951ad55062761d4d3f70f5ac5115e16a Mon Sep 17 00:00:00 2001 From: Ryan Dew Date: Fri, 14 Aug 2026 14:07:30 -0700 Subject: [PATCH] MLE-29883 (GH #1938) Include document version in bulk reads --- .../marklogic/client/impl/OkHttpServices.java | 235 ++--------------- .../client/impl/okhttp/HeaderUtil.java | 240 ++++++++++++++++++ .../client/impl/okhttp/OkHttpUtil.java | 22 +- .../client/test/ConditionalDocumentTest.java | 15 +- 4 files changed, 299 insertions(+), 213 deletions(-) create mode 100644 marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/HeaderUtil.java diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java index 059a97aca..1de3b0bff 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java @@ -19,6 +19,7 @@ import com.marklogic.client.eval.EvalResult; import com.marklogic.client.eval.EvalResultIterator; import com.marklogic.client.extra.okhttpclient.OkHttpClientConfigurator; +import com.marklogic.client.impl.okhttp.HeaderUtil; import com.marklogic.client.impl.okhttp.HttpUrlBuilder; import com.marklogic.client.impl.okhttp.OkHttpUtil; import com.marklogic.client.impl.okhttp.PartIterator; @@ -36,9 +37,7 @@ import jakarta.mail.BodyPart; import jakarta.mail.Header; import jakarta.mail.MessagingException; -import jakarta.mail.internet.ContentDisposition; import jakarta.mail.internet.MimeMultipart; -import jakarta.mail.internet.ParseException; import jakarta.mail.util.ByteArrayDataSource; import jakarta.xml.bind.DatatypeConverter; import okhttp3.*; @@ -68,6 +67,20 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.marklogic.client.impl.okhttp.HeaderUtil.copyDescriptor; +import static com.marklogic.client.impl.okhttp.HeaderUtil.getHeader; +import static com.marklogic.client.impl.okhttp.HeaderUtil.getHeaderFormat; +import static com.marklogic.client.impl.okhttp.HeaderUtil.getHeaderLength; +import static com.marklogic.client.impl.okhttp.HeaderUtil.getHeaderMimetype; +import static com.marklogic.client.impl.okhttp.HeaderUtil.getHeaderUri; +import static com.marklogic.client.impl.okhttp.HeaderUtil.getHeaderVersion; +import static com.marklogic.client.impl.okhttp.HeaderUtil.updateDescriptor; +import static com.marklogic.client.impl.okhttp.HeaderUtil.updateFormat; +import static com.marklogic.client.impl.okhttp.HeaderUtil.updateLength; +import static com.marklogic.client.impl.okhttp.HeaderUtil.updateMimetype; +import static com.marklogic.client.impl.okhttp.HeaderUtil.updateServerTimestamp; +import static com.marklogic.client.impl.okhttp.HeaderUtil.updateVersion; + @SuppressWarnings({"unchecked", "rawtypes"}) public class OkHttpServices implements RESTServices { @@ -1641,18 +1654,7 @@ static private boolean isExternalDescriptor(ContentDescriptor desc) { && !((DocumentDescriptorImpl) desc).isInternal(); } - static private void updateDescriptor(ContentDescriptor desc, - Headers headers) { - if (desc == null || headers == null) return; - - updateFormat(desc, headers); - updateMimetype(desc, headers); - updateLength(desc, headers); - updateServerTimestamp(desc, headers); - } - - static private TemporalDescriptor updateTemporalSystemTime(DocumentDescriptor desc, - Headers headers) { + static private TemporalDescriptor updateTemporalSystemTime(DocumentDescriptor desc, Headers headers) { if (headers == null) return null; DocumentDescriptorImpl temporalDescriptor; @@ -1665,194 +1667,6 @@ static private TemporalDescriptor updateTemporalSystemTime(DocumentDescriptor de return temporalDescriptor; } - static private void copyDescriptor(DocumentDescriptor desc, - HandleImplementation handleBase) { - if (handleBase == null) return; - - if (desc.getFormat() != null) handleBase.setFormat(desc.getFormat()); - if (desc.getMimetype() != null) handleBase.setMimetype(desc.getMimetype()); - handleBase.setByteLength(desc.getByteLength()); - } - - static private void updateFormat(ContentDescriptor descriptor, - Headers headers) { - updateFormat(descriptor, getHeaderFormat(headers)); - } - - static private void updateFormat(ContentDescriptor descriptor, Format format) { - if (format != null) { - descriptor.setFormat(format); - } - } - - static private Format getHeaderFormat(Headers headers) { - String format = headers.get(HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT); - if (format != null && format.length() > 0) { - return Format.valueOf(format.toUpperCase()); - } - String contentType = headers.get(HEADER_CONTENT_TYPE); - if (contentType != null && contentType.length() > 0) { - return Format.getFromMimetype(contentType); - } - return null; - } - - static private Format getHeaderFormat(BodyPart part) { - String contentDisposition = getHeader(part, HEADER_CONTENT_DISPOSITION); - String formatRegex = ".* format=(text|binary|xml|json).*"; - String format = getHeader(part, HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT); - String contentType = getHeader(part, HEADER_CONTENT_TYPE); - if (format != null && format.length() > 0) { - return Format.valueOf(format.toUpperCase()); - } else if (contentDisposition != null && contentDisposition.matches(formatRegex)) { - format = contentDisposition.replaceFirst("^.*" + formatRegex + ".*$", "$1"); - return Format.valueOf(format.toUpperCase()); - } else if (contentType != null && contentType.length() > 0) { - return Format.getFromMimetype(contentType); - } - return null; - } - - static private void updateMimetype(ContentDescriptor descriptor, - Headers headers) { - updateMimetype(descriptor, getHeaderMimetype(headers.get(HEADER_CONTENT_TYPE))); - } - - static private void updateMimetype(ContentDescriptor descriptor, String mimetype) { - if (mimetype != null) { - descriptor.setMimetype(mimetype); - } - } - - static private String getHeader(Map> headers, String name) { - List values = headers.get(name); - if (values != null && values.size() > 0) { - return values.get(0); - } - return null; - } - - static private String getHeader(BodyPart part, String name) { - if (part == null) throw new MarkLogicInternalException("part must not be null"); - try { - String[] values = part.getHeader(name); - if (values != null && values.length > 0) { - return values[0]; - } - return null; - } catch (MessagingException e) { - throw new MarkLogicIOException(e); - } - } - - static private String getHeaderMimetype(String contentType) { - if (contentType != null) { - int offset = contentType.indexOf(";"); - String mimetype = (offset == -1) ? contentType : contentType.substring(0, offset); - // TODO: if "; charset=foo" set character set - if (mimetype != null && mimetype.length() > 0) { - return mimetype; - } - } - return null; - } - - static private void updateLength(ContentDescriptor descriptor, - Headers headers) { - updateLength(descriptor, getHeaderLength(headers.get(HEADER_CONTENT_LENGTH))); - } - - static private void updateLength(ContentDescriptor descriptor, long length) { - descriptor.setByteLength(length); - } - - static private void updateServerTimestamp(ContentDescriptor descriptor, - Headers headers) { - updateServerTimestamp(descriptor, getHeaderServerTimestamp(headers)); - } - - static private long getHeaderServerTimestamp(Headers headers) { - return Utilities.parseLong(headers.get(HEADER_ML_EFFECTIVE_TIMESTAMP)); - } - - static private void updateServerTimestamp(ContentDescriptor descriptor, long timestamp) { - if (descriptor instanceof HandleImplementation) { - if (descriptor != null && timestamp != -1) { - ((HandleImplementation) descriptor).setResponseServerTimestamp(timestamp); - } - } - } - - static private long getHeaderLength(String length) { - return Utilities.parseLong(length, ContentDescriptor.UNKNOWN_LENGTH); - } - - static private String getHeaderUri(BodyPart part) { - try { - if (part == null) { - return null; - } - - try { - String filename = part.getFileName(); - if (filename != null) { - return filename; - } - } catch (ParseException e) { - // Jakarta Mail's parser failed due to malformed Content-Disposition header. - // Check if MarkLogic sent a malformed "format=" parameter at the end, which violates RFC 2183. - String contentDisposition = getHeader(part, "Content-Disposition"); - if (contentDisposition != null && contentDisposition.matches(".*;\\s*format\\s*=\\s*$")) { - // Remove the trailing "; format=" to fix the malformed header - String cleaned = contentDisposition.replaceFirst(";\\s*format\\s*=\\s*$", "").trim(); - logger.debug("Removed trailing 'format=' from malformed Content-Disposition header: {} -> {}", contentDisposition, cleaned); - return extractFilenameFromContentDisposition(cleaned); - } - throw e; - } - - return null; - } catch (MessagingException e) { - throw new MarkLogicIOException(e); - } - } - - static private String extractFilenameFromContentDisposition(String contentDisposition) { - if (contentDisposition == null) { - return null; - } - try { - // Use Jakarta Mail's ContentDisposition parser to extract the filename parameter. This is the class - // that throws an error when "format=" exists in the value, but that has been removed already. - ContentDisposition cd = new ContentDisposition(contentDisposition); - return cd.getParameter("filename"); - } catch (ParseException e) { - logger.warn("Failed to parse cleaned Content-Disposition header: {}; cause: {}", - contentDisposition, e.getMessage()); - return null; - } - } - - static private void updateVersion(DocumentDescriptor descriptor, Headers headers) { - updateVersion(descriptor, extractVersion(headers.get(HEADER_ETAG))); - } - - static private void updateVersion(DocumentDescriptor descriptor, String header) { - updateVersion(descriptor, extractVersion(header)); - } - - static private void updateVersion(DocumentDescriptor descriptor, long version) { - descriptor.setVersion(version); - } - - static private long extractVersion(String header) { - if (header != null && header.length() > 0) { - // trim the double quotes - return Long.parseLong(header.substring(1, header.length() - 1)); - } - return DocumentDescriptor.UNKNOWN_VERSION; - } - static private Request.Builder addVersionHeader(DocumentDescriptor desc, Request.Builder requestBldr, String name) { if (desc != null && desc instanceof DocumentDescriptorImpl && @@ -1869,8 +1683,8 @@ static private R updateHandle(BodyPart part, R ha HandleImplementation handleBase = HandleAccessor.as(handle); updateFormat(handleBase, getHeaderFormat(part)); - updateMimetype(handleBase, getHeaderMimetype(OkHttpServices.getHeader(part, HEADER_CONTENT_TYPE))); - updateLength(handleBase, getHeaderLength(OkHttpServices.getHeader(part, HEADER_CONTENT_LENGTH))); + updateMimetype(handleBase, getHeaderMimetype(getHeader(part, HEADER_CONTENT_TYPE))); + updateLength(handleBase, getHeaderLength(getHeader(part, HEADER_CONTENT_LENGTH))); handleBase.receiveContent(getEntity(part, handleBase.receiveAs())); return handle; @@ -4436,6 +4250,7 @@ static class OkHttpResult { private Format format; private String mimetype; private long length; + private long version = DocumentDescriptor.UNKNOWN_VERSION; OkHttpResult(RequestLogger reqlog, BodyPart part) { this.reqlog = reqlog; @@ -4490,6 +4305,11 @@ public long getLength() { return length; } + public long getVersion() { + extractHeaders(); + return version; + } + public String getHeader(String name) { extractHeaders(); List values = headers.get(name); @@ -4512,9 +4332,10 @@ private void extractHeaders() { headers.put(header.getName(), header.getValue()); } format = getHeaderFormat(part); - mimetype = getHeaderMimetype(OkHttpServices.getHeader(part, HEADER_CONTENT_TYPE)); - length = getHeaderLength(OkHttpServices.getHeader(part, HEADER_CONTENT_LENGTH)); + mimetype = getHeaderMimetype(HeaderUtil.getHeader(part, HEADER_CONTENT_TYPE)); + length = getHeaderLength(HeaderUtil.getHeader(part, HEADER_CONTENT_LENGTH)); uri = getHeaderUri(part); + version = getHeaderVersion(part); extractedHeaders = true; } catch (MessagingException e) { throw new MarkLogicIOException(e); @@ -4688,7 +4509,7 @@ public DocumentDescriptor getDescriptor() { updateFormat(descriptor, getFormat()); updateMimetype(descriptor, getMimetype()); updateLength(descriptor, getLength()); - updateVersion(descriptor, content.getHeader(HEADER_ETAG)); + updateVersion(descriptor, content.getVersion()); return descriptor; } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/HeaderUtil.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/HeaderUtil.java new file mode 100644 index 000000000..ff4099e8a --- /dev/null +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/HeaderUtil.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + */ +package com.marklogic.client.impl.okhttp; + +import com.marklogic.client.MarkLogicIOException; +import com.marklogic.client.MarkLogicInternalException; +import com.marklogic.client.document.ContentDescriptor; +import com.marklogic.client.document.DocumentDescriptor; +import com.marklogic.client.impl.HandleImplementation; +import com.marklogic.client.impl.Utilities; +import com.marklogic.client.io.Format; +import jakarta.mail.BodyPart; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.ContentDisposition; +import jakarta.mail.internet.ParseException; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +import static com.marklogic.client.impl.RESTServices.HEADER_CONTENT_DISPOSITION; +import static com.marklogic.client.impl.RESTServices.HEADER_CONTENT_LENGTH; +import static com.marklogic.client.impl.RESTServices.HEADER_CONTENT_TYPE; +import static com.marklogic.client.impl.RESTServices.HEADER_ETAG; +import static com.marklogic.client.impl.RESTServices.HEADER_ML_EFFECTIVE_TIMESTAMP; +import static com.marklogic.client.impl.RESTServices.HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT; + +/** + * Contains convenience methods for working with HTTP headers in the context of OkHttp and MarkLogic. + * This code was moved here from OkHttpServices without any modification during the move. + * + * @since 8.2.0 + */ +public class HeaderUtil { + static final private Logger logger = LoggerFactory.getLogger(HeaderUtil.class); + + public static void updateDescriptor(ContentDescriptor desc, Headers headers) { + if (desc == null || headers == null) return; + + updateFormat(desc, headers); + updateMimetype(desc, headers); + updateLength(desc, headers); + updateServerTimestamp(desc, headers); + } + + @SuppressWarnings("rawtypes") + public static void copyDescriptor(DocumentDescriptor desc, HandleImplementation handleBase) { + if (handleBase == null) return; + + if (desc.getFormat() != null) handleBase.setFormat(desc.getFormat()); + if (desc.getMimetype() != null) handleBase.setMimetype(desc.getMimetype()); + handleBase.setByteLength(desc.getByteLength()); + } + + public static void updateFormat(ContentDescriptor descriptor, Headers headers) { + updateFormat(descriptor, getHeaderFormat(headers)); + } + + public static void updateFormat(ContentDescriptor descriptor, Format format) { + if (format != null) { + descriptor.setFormat(format); + } + } + + public static Format getHeaderFormat(Headers headers) { + String format = headers.get(HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT); + if (format != null && format.length() > 0) { + return Format.valueOf(format.toUpperCase()); + } + String contentType = headers.get(HEADER_CONTENT_TYPE); + if (contentType != null && contentType.length() > 0) { + return Format.getFromMimetype(contentType); + } + return null; + } + + public static Format getHeaderFormat(BodyPart part) { + String contentDisposition = getHeader(part, HEADER_CONTENT_DISPOSITION); + String formatRegex = ".* format=(text|binary|xml|json).*"; + String format = getHeader(part, HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT); + String contentType = getHeader(part, HEADER_CONTENT_TYPE); + if (format != null && format.length() > 0) { + return Format.valueOf(format.toUpperCase()); + } else if (contentDisposition != null && contentDisposition.matches(formatRegex)) { + format = contentDisposition.replaceFirst("^.*" + formatRegex + ".*$", "$1"); + return Format.valueOf(format.toUpperCase()); + } else if (contentType != null && contentType.length() > 0) { + return Format.getFromMimetype(contentType); + } + return null; + } + + // Bulk multi-document reads usually carry the version as a "versionId" param on Content-Disposition. + public static long getHeaderVersion(BodyPart part) { + String contentDisposition = getHeader(part, HEADER_CONTENT_DISPOSITION); + String versionRegex = ".* versionId=([0-9]+).*"; + if (contentDisposition != null && contentDisposition.matches(versionRegex)) { + String version = contentDisposition.replaceFirst("^.*" + versionRegex + ".*$", "$1"); + return Utilities.parseLong(version, DocumentDescriptor.UNKNOWN_VERSION); + } + return extractVersion(getHeader(part, HEADER_ETAG)); + } + + public static void updateMimetype(ContentDescriptor descriptor, Headers headers) { + updateMimetype(descriptor, getHeaderMimetype(headers.get(HEADER_CONTENT_TYPE))); + } + + public static void updateMimetype(ContentDescriptor descriptor, String mimetype) { + if (mimetype != null) { + descriptor.setMimetype(mimetype); + } + } + + public static String getHeader(Map> headers, String name) { + List values = headers.get(name); + if (values != null && values.size() > 0) { + return values.get(0); + } + return null; + } + + public static String getHeader(BodyPart part, String name) { + if (part == null) throw new MarkLogicInternalException("part must not be null"); + try { + String[] values = part.getHeader(name); + if (values != null && values.length > 0) { + return values[0]; + } + return null; + } catch (MessagingException e) { + throw new MarkLogicIOException(e); + } + } + + public static String getHeaderMimetype(String contentType) { + if (contentType != null) { + int offset = contentType.indexOf(";"); + String mimetype = (offset == -1) ? contentType : contentType.substring(0, offset); + // TODO: if "; charset=foo" set character set + if (mimetype != null && mimetype.length() > 0) { + return mimetype; + } + } + return null; + } + + public static void updateLength(ContentDescriptor descriptor, Headers headers) { + updateLength(descriptor, getHeaderLength(headers.get(HEADER_CONTENT_LENGTH))); + } + + public static void updateLength(ContentDescriptor descriptor, long length) { + descriptor.setByteLength(length); + } + + public static void updateServerTimestamp(ContentDescriptor descriptor, Headers headers) { + updateServerTimestamp(descriptor, getHeaderServerTimestamp(headers)); + } + + private static long getHeaderServerTimestamp(Headers headers) { + return Utilities.parseLong(headers.get(HEADER_ML_EFFECTIVE_TIMESTAMP)); + } + + @SuppressWarnings("rawtypes") + public static void updateServerTimestamp(ContentDescriptor descriptor, long timestamp) { + if (descriptor instanceof HandleImplementation) { + if (descriptor != null && timestamp != -1) { + ((HandleImplementation) descriptor).setResponseServerTimestamp(timestamp); + } + } + } + + public static long getHeaderLength(String length) { + return Utilities.parseLong(length, ContentDescriptor.UNKNOWN_LENGTH); + } + + public static String getHeaderUri(BodyPart part) { + try { + if (part == null) { + return null; + } + + try { + String filename = part.getFileName(); + if (filename != null) { + return filename; + } + } catch (ParseException e) { + // Jakarta Mail's parser failed due to malformed Content-Disposition header. + // Check if MarkLogic sent a malformed "format=" parameter at the end, which violates RFC 2183. + String contentDisposition = getHeader(part, "Content-Disposition"); + if (contentDisposition != null && contentDisposition.matches(".*;\\s*format\\s*=\\s*$")) { + // Remove the trailing "; format=" to fix the malformed header + String cleaned = contentDisposition.replaceFirst(";\\s*format\\s*=\\s*$", "").trim(); + logger.debug("Removed trailing 'format=' from malformed Content-Disposition header: {} -> {}", contentDisposition, cleaned); + return extractFilenameFromContentDisposition(cleaned); + } + throw e; + } + + return null; + } catch (MessagingException e) { + throw new MarkLogicIOException(e); + } + } + + private static String extractFilenameFromContentDisposition(String contentDisposition) { + if (contentDisposition == null) { + return null; + } + try { + // Use Jakarta Mail's ContentDisposition parser to extract the filename parameter. This is the class + // that throws an error when "format=" exists in the value, but that has been removed already. + ContentDisposition cd = new ContentDisposition(contentDisposition); + return cd.getParameter("filename"); + } catch (ParseException e) { + logger.warn("Failed to parse cleaned Content-Disposition header: {}; cause: {}", + contentDisposition, e.getMessage()); + return null; + } + } + + public static void updateVersion(DocumentDescriptor descriptor, Headers headers) { + updateVersion(descriptor, extractVersion(headers.get(HEADER_ETAG))); + } + + public static void updateVersion(DocumentDescriptor descriptor, long version) { + descriptor.setVersion(version); + } + + private static long extractVersion(String header) { + if (header != null && header.length() > 0) { + // trim the double quotes + return Long.parseLong(header.substring(1, header.length() - 1)); + } + return DocumentDescriptor.UNKNOWN_VERSION; + } +} diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/OkHttpUtil.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/OkHttpUtil.java index e3f9d4bd1..64bf4eab4 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/OkHttpUtil.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/OkHttpUtil.java @@ -1,12 +1,25 @@ /* - * 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.okhttp; import com.marklogic.client.DatabaseClientFactory; +import com.marklogic.client.MarkLogicIOException; +import com.marklogic.client.MarkLogicInternalException; +import com.marklogic.client.document.ContentDescriptor; +import com.marklogic.client.document.DocumentDescriptor; import com.marklogic.client.extra.okhttpclient.OkHttpClientConfigurator; +import com.marklogic.client.impl.HandleImplementation; import com.marklogic.client.impl.SSLUtil; +import com.marklogic.client.impl.Utilities; +import com.marklogic.client.io.Format; +import jakarta.mail.BodyPart; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.ContentDisposition; +import jakarta.mail.internet.ParseException; import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; @@ -22,6 +35,13 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import static com.marklogic.client.impl.RESTServices.HEADER_CONTENT_DISPOSITION; +import static com.marklogic.client.impl.RESTServices.HEADER_CONTENT_LENGTH; +import static com.marklogic.client.impl.RESTServices.HEADER_CONTENT_TYPE; +import static com.marklogic.client.impl.RESTServices.HEADER_ETAG; +import static com.marklogic.client.impl.RESTServices.HEADER_ML_EFFECTIVE_TIMESTAMP; +import static com.marklogic.client.impl.RESTServices.HEADER_VND_MARKLOGIC_DOCUMENT_FORMAT; + /** * Contains convenience methods for constructing an OkHttpClient.Builder so that it can be used in places other than * only {@code OkHttpServices}. This code was moved here from OkHttpServices without any modification during the move diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/ConditionalDocumentTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/ConditionalDocumentTest.java index 823887d14..0cbca45c4 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/ConditionalDocumentTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/ConditionalDocumentTest.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.test; @@ -192,21 +192,26 @@ public void testConditionalMultiple() { XMLDocumentManager docMgr = Common.client.newXMLDocumentManager(); - verifyDescriptors(docList, docMgr.read(docIds)); + verifyDescriptors(docList, docMgr.read(docIds), docMgr); verifyDescriptors( docList, - docMgr.search(new StructuredQueryBuilder().document(docIds), 1) + docMgr.search(new StructuredQueryBuilder().document(docIds), 1), + docMgr ); } - void verifyDescriptors(List docList, DocumentPage page) { + + void verifyDescriptors(List docList, DocumentPage page, XMLDocumentManager docMgr) { for (DocumentRecord record: page) { DocumentDescriptor desc = record.getDescriptor(); assertTrue( docList.contains(desc.getUri())); assertEquals( Format.XML, desc.getFormat()); assertTrue( desc.getMimetype().startsWith("application/xml")); assertTrue( desc.getByteLength() >= 0); - assertTrue( desc.getVersion() >= -1); + // bulk read() should report the same version as exists() for each document + assertNotEquals( DocumentDescriptor.UNKNOWN_VERSION, desc.getVersion()); + long existsVersion = docMgr.exists(record.getUri()).getVersion(); + assertEquals( existsVersion, desc.getVersion()); } } }