diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml
index 5287bf7..52fe198 100644
--- a/.github/workflows/snyk-scan.yml
+++ b/.github/workflows/snyk-scan.yml
@@ -37,6 +37,6 @@ jobs:
- name: Upload result to GitHub Code Scanning
if: steps.out-file.outputs.exists == 'true'
- uses: github/codeql-action/upload-sarif@v3
+ uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: snyk.sarif
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c26f1ac..c4c6847 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,12 +21,13 @@
3.5.03.3.11.6
- 9.2.04.13.21.18.425.14.03.27.60.8.0
+ 2.0.1
+ 2.0.17
@@ -62,11 +63,6 @@
-
- com.aerospike
- aerospike-client-jdk8
- ${aerospike-client-jdk8.version}
- org.antlrantlr4-runtime
@@ -77,6 +73,22 @@
lombok${lombok.version}
+
+ org.gnu
+ gnu-crypto
+ ${gnu.crypto.version}
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j-simple.version}
+ test
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j-simple.version}
+ org.junit.jupiterjunit-jupiter
diff --git a/src/main/java/com/aerospike/dsl/Index.java b/src/main/java/com/aerospike/dsl/Index.java
index b408e70..6c68f84 100644
--- a/src/main/java/com/aerospike/dsl/Index.java
+++ b/src/main/java/com/aerospike/dsl/Index.java
@@ -1,8 +1,8 @@
package com.aerospike.dsl;
-import com.aerospike.client.cdt.CTX;
-import com.aerospike.client.query.IndexCollectionType;
-import com.aerospike.client.query.IndexType;
+import com.aerospike.dsl.client.cdt.CTX;
+import com.aerospike.dsl.client.query.IndexCollectionType;
+import com.aerospike.dsl.client.query.IndexType;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
diff --git a/src/main/java/com/aerospike/dsl/IndexContext.java b/src/main/java/com/aerospike/dsl/IndexContext.java
index 17abff6..d655963 100644
--- a/src/main/java/com/aerospike/dsl/IndexContext.java
+++ b/src/main/java/com/aerospike/dsl/IndexContext.java
@@ -1,24 +1,23 @@
package com.aerospike.dsl;
-import com.aerospike.client.query.Filter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Collection;
/**
- * This class stores namespace and indexes required to build secondary index {@link Filter}
+ * This class stores namespace and indexes required to build secondary index Filter
*/
@AllArgsConstructor(staticName = "of")
@Getter
public class IndexContext {
/**
- * Namespace to be used for creating {@link Filter}. Is matched with namespace of indexes
+ * Namespace to be used for creating secondary index Filter. Is matched with namespace of indexes
*/
private String namespace;
/**
- * Collection of {@link Index} objects to be used for creating {@link Filter}.
+ * Collection of {@link Index} objects to be used for creating secondary index Filter.
* Namespace of indexes is matched with the given {@link #namespace}, bin name and index type are matched
* with bins in DSL String
*/
diff --git a/src/main/java/com/aerospike/dsl/ParseResult.java b/src/main/java/com/aerospike/dsl/ParseResult.java
index d429498..c5985d9 100644
--- a/src/main/java/com/aerospike/dsl/ParseResult.java
+++ b/src/main/java/com/aerospike/dsl/ParseResult.java
@@ -1,7 +1,7 @@
package com.aerospike.dsl;
-import com.aerospike.client.exp.Exp;
-import com.aerospike.client.query.Filter;
+import com.aerospike.dsl.client.exp.Exp;
+import com.aerospike.dsl.client.query.Filter;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/src/main/java/com/aerospike/dsl/ParsedExpression.java b/src/main/java/com/aerospike/dsl/ParsedExpression.java
index 98ef7d6..e7778eb 100644
--- a/src/main/java/com/aerospike/dsl/ParsedExpression.java
+++ b/src/main/java/com/aerospike/dsl/ParsedExpression.java
@@ -1,8 +1,8 @@
package com.aerospike.dsl;
-import com.aerospike.client.exp.Exp;
-import com.aerospike.client.query.Filter;
import com.aerospike.dsl.annotation.Beta;
+import com.aerospike.dsl.client.exp.Exp;
+import com.aerospike.dsl.client.query.Filter;
import com.aerospike.dsl.parts.AbstractPart;
import com.aerospike.dsl.parts.ExpressionContainer;
import lombok.Getter;
diff --git a/src/main/java/com/aerospike/dsl/api/DSLParser.java b/src/main/java/com/aerospike/dsl/api/DSLParser.java
index 80d38b2..370bf1d 100644
--- a/src/main/java/com/aerospike/dsl/api/DSLParser.java
+++ b/src/main/java/com/aerospike/dsl/api/DSLParser.java
@@ -1,12 +1,12 @@
package com.aerospike.dsl.api;
-import com.aerospike.client.cdt.CTX;
-import com.aerospike.client.query.Filter;
import com.aerospike.dsl.DslParseException;
import com.aerospike.dsl.ExpressionContext;
import com.aerospike.dsl.Index;
import com.aerospike.dsl.IndexContext;
import com.aerospike.dsl.ParsedExpression;
+import com.aerospike.dsl.client.cdt.CTX;
+import com.aerospike.dsl.client.query.Filter;
/**
* Contains API to convert dot separated String path into an Aerospike filter -
diff --git a/src/main/java/com/aerospike/dsl/client/AerospikeException.java b/src/main/java/com/aerospike/dsl/client/AerospikeException.java
new file mode 100644
index 0000000..fc8f6c6
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/AerospikeException.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2012-2025 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client;
+
+import java.util.List;
+
+/**
+ * Aerospike exceptions that can be thrown from the client.
+ */
+public class AerospikeException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ protected List subExceptions;
+ protected int resultCode = ResultCode.CLIENT_ERROR;
+ protected int iteration = -1;
+ protected boolean inDoubt;
+
+ public AerospikeException(int resultCode, String message) {
+ super(message);
+ this.resultCode = resultCode;
+ }
+
+ public AerospikeException(int resultCode, Throwable e) {
+ super(e);
+ this.resultCode = resultCode;
+ }
+
+ public AerospikeException(int resultCode) {
+ super();
+ this.resultCode = resultCode;
+ }
+
+ public AerospikeException(int resultCode, boolean inDoubt) {
+ super();
+ this.resultCode = resultCode;
+ this.inDoubt = inDoubt;
+ }
+
+ public AerospikeException(int resultCode, String message, Throwable e) {
+ super(message, e);
+ this.resultCode = resultCode;
+ }
+
+ public AerospikeException(String message, Throwable e) {
+ super(message, e);
+ }
+
+ public AerospikeException(String message) {
+ super(message);
+ }
+
+ public AerospikeException(Throwable e) {
+ super(e);
+ }
+
+ /**
+ * Exception thrown when a Java serialization error occurs.
+ */
+ public static final class Serialize extends AerospikeException {
+ private static final long serialVersionUID = 1L;
+
+ public Serialize(Throwable e) {
+ super(ResultCode.SERIALIZE_ERROR, "Serialize error", e);
+ }
+
+ public Serialize(String message) {
+ super(ResultCode.SERIALIZE_ERROR, message);
+ }
+ }
+
+ /**
+ * Exception thrown when client can't parse data returned from server.
+ */
+ public static final class Parse extends AerospikeException {
+ private static final long serialVersionUID = 1L;
+
+ public Parse(String message) {
+ super(ResultCode.PARSE_ERROR, message);
+ }
+ }
+
+ /**
+ * Return base message without extra metadata.
+ */
+ public String getBaseMessage() {
+ String message = super.getMessage();
+ return (message != null)? message : ResultCode.getResultString(resultCode);
+ }
+
+ /**
+ * Should connection be put back into pool.
+ */
+ public final boolean keepConnection() {
+ return ResultCode.keepConnection(resultCode);
+ }
+
+ /**
+ * Get sub exceptions. Will be null if a retry did not occur.
+ */
+ public final List getSubExceptions() {
+ return subExceptions;
+ }
+
+ /**
+ * Set sub exceptions.
+ */
+ public final void setSubExceptions(List subExceptions) {
+ this.subExceptions = subExceptions;
+ }
+
+ /**
+ * Get integer result code.
+ */
+ public final int getResultCode() {
+ return resultCode;
+ }
+
+ /**
+ * Get number of attempts before failing.
+ */
+ public final int getIteration() {
+ return iteration;
+ }
+
+ /**
+ * Set number of attempts before failing.
+ */
+ public final void setIteration(int iteration) {
+ this.iteration = iteration;
+ }
+
+ /**
+ * Is it possible that write command may have completed.
+ */
+ public final boolean getInDoubt() {
+ return inDoubt;
+ }
+
+ /**
+ * Set whether it is possible that the write command may have completed
+ * even though this exception was generated. This may be the case when a
+ * client error occurs (like timeout) after the command was sent to the server.
+ */
+ public final void setInDoubt(boolean isWrite, int commandSentCounter) {
+ if (isWrite && (commandSentCounter > 1 || (commandSentCounter == 1 && (resultCode == ResultCode.TIMEOUT || resultCode <= 0)))) {
+ this.inDoubt = true;
+ }
+ }
+
+ /**
+ * Sets the inDoubt value to inDoubt.
+ */
+ public void setInDoubt(boolean inDoubt) {
+ this.inDoubt = inDoubt;
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/ResultCode.java b/src/main/java/com/aerospike/dsl/client/ResultCode.java
new file mode 100644
index 0000000..716dd56
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/ResultCode.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright 2012-2025 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client;
+
+/**
+ * Database operation error codes. The positive numbers align with the server
+ * side file proto.h.
+ */
+public final class ResultCode {
+ /**
+ * Transaction commit called, but the transaction was already aborted.
+ */
+ public static final int TXN_ALREADY_ABORTED = -19;
+
+ /**
+ * Transaction abort called, but the transaction was already committed.
+ */
+ public static final int TXN_ALREADY_COMMITTED = -18;
+
+ /**
+ * Transaction failed.
+ */
+ public static final int TXN_FAILED = -17;
+
+ /**
+ * One or more keys failed in a batch.
+ */
+ public static final int BATCH_FAILED = -16;
+
+ /**
+ * No response received from server.
+ */
+ public static final int NO_RESPONSE = -15;
+
+ /**
+ * Max errors limit reached.
+ */
+ public static final int MAX_ERROR_RATE = -12;
+
+ /**
+ * Max retries limit reached.
+ */
+ public static final int MAX_RETRIES_EXCEEDED = -11;
+
+ /**
+ * Client serialization error.
+ */
+ public static final int SERIALIZE_ERROR = -10;
+
+ /**
+ * Async delay queue is full.
+ */
+ public static final int ASYNC_QUEUE_FULL = -9;
+
+ /**
+ * Connection to server failed.
+ */
+ public static final int SERVER_NOT_AVAILABLE = -8;
+
+ /**
+ * Max connections would be exceeded. There are no more available connections.
+ */
+ public static final int NO_MORE_CONNECTIONS = -7;
+
+ /**
+ * Query was terminated by user.
+ */
+ public static final int QUERY_TERMINATED = -5;
+
+ /**
+ * Scan was terminated by user.
+ */
+ public static final int SCAN_TERMINATED = -4;
+
+ /**
+ * Chosen node is not currently active.
+ */
+ public static final int INVALID_NODE_ERROR = -3;
+
+ /**
+ * Client parse error.
+ */
+ public static final int PARSE_ERROR = -2;
+
+ /**
+ * Generic client error.
+ */
+ public static final int CLIENT_ERROR = -1;
+
+ /**
+ * Operation was successful.
+ */
+ public static final int OK = 0;
+
+ /**
+ * Unknown server failure.
+ */
+ public static final int SERVER_ERROR = 1;
+
+ /**
+ * On retrieving, touching or replacing a record that doesn't exist.
+ */
+ public static final int KEY_NOT_FOUND_ERROR = 2;
+
+ /**
+ * On modifying a record with unexpected generation.
+ */
+ public static final int GENERATION_ERROR = 3;
+
+ /**
+ * Bad parameter(s) were passed in database operation call.
+ */
+ public static final int PARAMETER_ERROR = 4;
+
+ /**
+ * On create-only (write unique) operations on a record that already
+ * exists.
+ */
+ public static final int KEY_EXISTS_ERROR = 5;
+
+ /**
+ * Bin already exists on a create-only operation.
+ */
+ public static final int BIN_EXISTS_ERROR = 6;
+
+ /**
+ * Expected cluster was not received.
+ */
+ public static final int CLUSTER_KEY_MISMATCH = 7;
+
+ /**
+ * Server has run out of memory.
+ */
+ public static final int SERVER_MEM_ERROR = 8;
+
+ /**
+ * Client or server has timed out.
+ */
+ public static final int TIMEOUT = 9;
+
+ /**
+ * Operation not allowed in current configuration.
+ */
+ public static final int ALWAYS_FORBIDDEN = 10;
+
+ /**
+ * Partition is unavailable.
+ */
+ public static final int PARTITION_UNAVAILABLE = 11;
+
+ /**
+ * Operation is not supported with configured bin type.
+ */
+ public static final int BIN_TYPE_ERROR = 12;
+
+ /**
+ * Record size exceeds limit.
+ */
+ public static final int RECORD_TOO_BIG = 13;
+
+ /**
+ * Too many concurrent operations on the same record.
+ */
+ public static final int KEY_BUSY = 14;
+
+ /**
+ * Scan aborted by server.
+ */
+ public static final int SCAN_ABORT = 15;
+
+ /**
+ * Unsupported Server Feature (e.g. Scan + UDF)
+ */
+ public static final int UNSUPPORTED_FEATURE = 16;
+
+ /**
+ * Bin not found on update-only operation.
+ */
+ public static final int BIN_NOT_FOUND = 17;
+
+ /**
+ * Device not keeping up with writes.
+ */
+ public static final int DEVICE_OVERLOAD = 18;
+
+ /**
+ * Key type mismatch.
+ */
+ public static final int KEY_MISMATCH = 19;
+
+ /**
+ * Invalid namespace.
+ */
+ public static final int INVALID_NAMESPACE = 20;
+
+ /**
+ * Bin name length greater than 15 characters or maximum bins exceeded.
+ */
+ public static final int BIN_NAME_TOO_LONG = 21;
+
+ /**
+ * Operation not allowed at this time.
+ */
+ public static final int FAIL_FORBIDDEN = 22;
+
+ /**
+ * Map element not found in UPDATE_ONLY write mode.
+ */
+ public static final int ELEMENT_NOT_FOUND = 23;
+
+ /**
+ * Map element exists in CREATE_ONLY write mode.
+ */
+ public static final int ELEMENT_EXISTS = 24;
+
+ /**
+ * Attempt to use an Enterprise feature on a Community server or a server
+ * without the applicable feature key.
+ */
+ public static final int ENTERPRISE_ONLY = 25;
+
+ /**
+ * The operation cannot be applied to the current bin value on the server.
+ */
+ public static final int OP_NOT_APPLICABLE = 26;
+
+ /**
+ * The command was not performed because the filter was false.
+ */
+ public static final int FILTERED_OUT = 27;
+
+ /**
+ * Write command loses conflict to XDR.
+ */
+ public static final int LOST_CONFLICT = 28;
+
+ /**
+ * Write can't complete until XDR finishes shipping.
+ */
+ public static final int XDR_KEY_BUSY = 32;
+
+ /**
+ * There are no more records left for query.
+ */
+ public static final int QUERY_END = 50;
+
+ /**
+ * Security functionality not supported by connected server.
+ */
+ public static final int SECURITY_NOT_SUPPORTED = 51;
+
+ /**
+ * Security functionality not enabled by connected server.
+ */
+ public static final int SECURITY_NOT_ENABLED = 52;
+
+ /**
+ * Security type not supported by connected server.
+ */
+ public static final int SECURITY_SCHEME_NOT_SUPPORTED = 53;
+
+ /**
+ * Administration command is invalid.
+ */
+ public static final int INVALID_COMMAND = 54;
+
+ /**
+ * Administration field is invalid.
+ */
+ public static final int INVALID_FIELD = 55;
+
+ /**
+ * Security protocol not followed.
+ */
+ public static final int ILLEGAL_STATE = 56;
+
+ /**
+ * User name is invalid.
+ */
+ public static final int INVALID_USER = 60;
+
+ /**
+ * User was previously created.
+ */
+ public static final int USER_ALREADY_EXISTS = 61;
+
+ /**
+ * Password is invalid.
+ */
+ public static final int INVALID_PASSWORD = 62;
+
+ /**
+ * Password has expired.
+ */
+ public static final int EXPIRED_PASSWORD = 63;
+
+ /**
+ * Forbidden password (e.g. recently used)
+ */
+ public static final int FORBIDDEN_PASSWORD = 64;
+
+ /**
+ * Security credential is invalid.
+ */
+ public static final int INVALID_CREDENTIAL = 65;
+
+ /**
+ * Login session expired.
+ */
+ public static final int EXPIRED_SESSION = 66;
+
+ /**
+ * Role name is invalid.
+ */
+ public static final int INVALID_ROLE = 70;
+
+ /**
+ * Role already exists.
+ */
+ public static final int ROLE_ALREADY_EXISTS = 71;
+
+ /**
+ * Privilege is invalid.
+ */
+ public static final int INVALID_PRIVILEGE = 72;
+
+ /**
+ * Invalid IP address whitelist.
+ */
+ public static final int INVALID_WHITELIST = 73;
+
+ /**
+ * Quotas not enabled on server.
+ */
+ public static final int QUOTAS_NOT_ENABLED = 74;
+
+ /**
+ * Invalid quota value.
+ */
+ public static final int INVALID_QUOTA = 75;
+
+ /**
+ * User must be authentication before performing database operations.
+ */
+ public static final int NOT_AUTHENTICATED = 80;
+
+ /**
+ * User does not possess the required role to perform the database operation.
+ */
+ public static final int ROLE_VIOLATION = 81;
+
+ /**
+ * Command not allowed because sender IP address not whitelisted.
+ */
+ public static final int NOT_WHITELISTED = 82;
+
+ /**
+ * Quota exceeded.
+ */
+ public static final int QUOTA_EXCEEDED = 83;
+
+ /**
+ * A user defined function returned an error code.
+ */
+ public static final int UDF_BAD_RESPONSE = 100;
+
+ /**
+ * Transaction record blocked by a different transaction.
+ */
+ public static final int MRT_BLOCKED = 120;
+
+ /**
+ * Transaction read version mismatch identified during commit.
+ * Some other command changed the record outside of the transaction.
+ */
+ public static final int MRT_VERSION_MISMATCH = 121;
+
+ /**
+ * Transaction deadline reached without a successful commit or abort.
+ */
+ public static final int MRT_EXPIRED = 122;
+
+ /**
+ * Transaction write command limit (4096) exceeded.
+ */
+ public static final int MRT_TOO_MANY_WRITES = 123;
+
+ /**
+ * Transaction was already committed.
+ */
+ public static final int MRT_COMMITTED = 124;
+
+ /**
+ * Transaction was already aborted.
+ */
+ public static final int MRT_ABORTED = 125;
+
+ /**
+ * This record has been locked by a previous update in this transaction.
+ */
+ public static final int MRT_ALREADY_LOCKED = 126;
+
+ /**
+ * This transaction has already started. Writing to the same transaction with independent threads is unsafe.
+ */
+ public static final int MRT_MONITOR_EXISTS = 127;
+
+ /**
+ * Batch functionality has been disabled.
+ */
+ public static final int BATCH_DISABLED = 150;
+
+ /**
+ * Batch max requests have been exceeded.
+ */
+ public static final int BATCH_MAX_REQUESTS_EXCEEDED = 151;
+
+ /**
+ * All batch queues are full.
+ */
+ public static final int BATCH_QUEUES_FULL = 152;
+
+ /**
+ * Secondary index already exists.
+ */
+ public static final int INDEX_ALREADY_EXISTS = 200;
+ public static final int INDEX_FOUND = 200; // For legacy reasons.
+
+ /**
+ * Requested secondary index does not exist.
+ */
+ public static final int INDEX_NOTFOUND = 201;
+
+ /**
+ * Secondary index memory space exceeded.
+ */
+ public static final int INDEX_OOM = 202;
+
+ /**
+ * Secondary index not available.
+ */
+ public static final int INDEX_NOTREADABLE = 203;
+
+ /**
+ * Generic secondary index error.
+ */
+ public static final int INDEX_GENERIC = 204;
+
+ /**
+ * Index name maximum length exceeded.
+ */
+ public static final int INDEX_NAME_MAXLEN = 205;
+
+ /**
+ * Maximum number of indicies exceeded.
+ */
+ public static final int INDEX_MAXCOUNT = 206;
+
+ /**
+ * Secondary index query aborted.
+ */
+ public static final int QUERY_ABORTED = 210;
+
+ /**
+ * Secondary index queue full.
+ */
+ public static final int QUERY_QUEUEFULL = 211;
+
+ /**
+ * Secondary index query timed out on server.
+ */
+ public static final int QUERY_TIMEOUT = 212;
+
+ /**
+ * Generic query error.
+ */
+ public static final int QUERY_GENERIC = 213;
+
+ /**
+ * Should connection be put back into pool.
+ */
+ public static boolean keepConnection(int resultCode) {
+ if (resultCode <= 0) {
+ // Do not keep connection on client errors.
+ return false;
+ }
+
+ switch (resultCode) {
+ case SCAN_ABORT:
+ case QUERY_ABORTED:
+ return false;
+
+ default:
+ // Keep connection on TIMEOUT because it can be server response which does not
+ // close socket. Also, client timeout code path does not call this method.
+ return true;
+ }
+ }
+
+ /**
+ * Return result code as a string.
+ */
+ public static String getResultString(int resultCode) {
+ switch (resultCode) {
+ case TXN_ALREADY_ABORTED:
+ return "Transaction already aborted";
+
+ case TXN_ALREADY_COMMITTED:
+ return "Transaction already committed";
+
+ case TXN_FAILED:
+ return "Transaction failed";
+
+ case BATCH_FAILED:
+ return "One or more keys failed in a batch";
+
+ case NO_RESPONSE:
+ return "No response received from server";
+
+ case MAX_ERROR_RATE:
+ return "Max error rate exceeded";
+
+ case MAX_RETRIES_EXCEEDED:
+ return "Max retries exceeded";
+
+ case SERIALIZE_ERROR:
+ return "Serialize error";
+
+ case ASYNC_QUEUE_FULL:
+ return "Async delay queue is full";
+
+ case SERVER_NOT_AVAILABLE:
+ return "Connection to server failed";
+
+ case NO_MORE_CONNECTIONS:
+ return "No more available connections";
+
+ case QUERY_TERMINATED:
+ return "Query terminated";
+
+ case SCAN_TERMINATED:
+ return "Scan terminated";
+
+ case INVALID_NODE_ERROR:
+ return "Invalid node";
+
+ case PARSE_ERROR:
+ return "Parse error";
+
+ case CLIENT_ERROR:
+ return "Client error";
+
+ case OK:
+ return "ok";
+
+ case SERVER_ERROR:
+ return "Server error";
+
+ case KEY_NOT_FOUND_ERROR:
+ return "Key not found";
+
+ case GENERATION_ERROR:
+ return "Generation error";
+
+ case PARAMETER_ERROR:
+ return "Parameter error";
+
+ case KEY_EXISTS_ERROR:
+ return "Key already exists";
+
+ case BIN_EXISTS_ERROR:
+ return "Bin already exists";
+
+ case CLUSTER_KEY_MISMATCH:
+ return "Cluster key mismatch";
+
+ case SERVER_MEM_ERROR:
+ return "Server memory error";
+
+ case TIMEOUT:
+ return "Timeout";
+
+ case ALWAYS_FORBIDDEN:
+ return "Operation not allowed";
+
+ case PARTITION_UNAVAILABLE:
+ return "Partition unavailable";
+
+ case BIN_TYPE_ERROR:
+ return "Bin type error";
+
+ case RECORD_TOO_BIG:
+ return "Record too big";
+
+ case KEY_BUSY:
+ return "Hot key";
+
+ case SCAN_ABORT:
+ return "Scan aborted";
+
+ case UNSUPPORTED_FEATURE:
+ return "Unsupported Server Feature";
+
+ case BIN_NOT_FOUND:
+ return "Bin not found";
+
+ case DEVICE_OVERLOAD:
+ return "Device overload";
+
+ case KEY_MISMATCH:
+ return "Key mismatch";
+
+ case INVALID_NAMESPACE:
+ return "Namespace not found";
+
+ case BIN_NAME_TOO_LONG:
+ return "Bin name length greater than 15 characters or maximum bins exceeded";
+
+ case FAIL_FORBIDDEN:
+ return "Operation not allowed at this time";
+
+ case ELEMENT_NOT_FOUND:
+ return "Map key not found";
+
+ case ELEMENT_EXISTS:
+ return "Map key exists";
+
+ case ENTERPRISE_ONLY:
+ return "Enterprise only";
+
+ case OP_NOT_APPLICABLE:
+ return "Operation not applicable";
+
+ case FILTERED_OUT:
+ return "Command filtered out";
+
+ case LOST_CONFLICT:
+ return "Command failed due to conflict with XDR";
+
+ case XDR_KEY_BUSY:
+ return "Write can't complete until XDR finishes shipping";
+
+ case QUERY_END:
+ return "Query end";
+
+ case SECURITY_NOT_SUPPORTED:
+ return "Security not supported";
+
+ case SECURITY_NOT_ENABLED:
+ return "Security not enabled";
+
+ case SECURITY_SCHEME_NOT_SUPPORTED:
+ return "Security scheme not supported";
+
+ case INVALID_COMMAND:
+ return "Invalid command";
+
+ case INVALID_FIELD:
+ return "Invalid field";
+
+ case ILLEGAL_STATE:
+ return "Illegal state";
+
+ case INVALID_USER:
+ return "Invalid user";
+
+ case USER_ALREADY_EXISTS:
+ return "User already exists";
+
+ case INVALID_PASSWORD:
+ return "Invalid password";
+
+ case EXPIRED_PASSWORD:
+ return "Password expired";
+
+ case FORBIDDEN_PASSWORD:
+ return "Password can't be reused";
+
+ case INVALID_CREDENTIAL:
+ return "Invalid credential";
+
+ case EXPIRED_SESSION:
+ return "Login session expired";
+
+ case INVALID_ROLE:
+ return "Invalid role";
+
+ case ROLE_ALREADY_EXISTS:
+ return "Role already exists";
+
+ case INVALID_PRIVILEGE:
+ return "Invalid privilege";
+
+ case INVALID_WHITELIST:
+ return "Invalid whitelist";
+
+ case QUOTAS_NOT_ENABLED:
+ return "Quotas not enabled";
+
+ case INVALID_QUOTA:
+ return "Invalid quota";
+
+ case NOT_AUTHENTICATED:
+ return "Not authenticated";
+
+ case ROLE_VIOLATION:
+ return "Role violation";
+
+ case NOT_WHITELISTED:
+ return "Command not whitelisted";
+
+ case QUOTA_EXCEEDED:
+ return "Quota exceeded";
+
+ case UDF_BAD_RESPONSE:
+ return "UDF returned error";
+
+ case MRT_BLOCKED:
+ return "Transaction record blocked by a different transaction";
+
+ case MRT_VERSION_MISMATCH:
+ return "Transaction version mismatch";
+
+ case MRT_EXPIRED:
+ return "Transaction expired";
+
+ case MRT_TOO_MANY_WRITES:
+ return "Transaction write command limit exceeded";
+
+ case MRT_COMMITTED:
+ return "Transaction already committed";
+
+ case MRT_ABORTED:
+ return "Transaction already aborted";
+
+ case MRT_ALREADY_LOCKED:
+ return "This record has been locked by a previous update in this transaction";
+
+ case MRT_MONITOR_EXISTS:
+ return "This transaction has already started. Writing to the same transaction with independent threads is unsafe";
+
+ case BATCH_DISABLED:
+ return "Batch functionality has been disabled";
+
+ case BATCH_MAX_REQUESTS_EXCEEDED:
+ return "Batch max requests have been exceeded";
+
+ case BATCH_QUEUES_FULL:
+ return "All batch queues are full";
+
+ case INDEX_ALREADY_EXISTS:
+ return "Index already exists";
+
+ case INDEX_NOTFOUND:
+ return "Index not found";
+
+ case INDEX_OOM:
+ return "Index out of memory";
+
+ case INDEX_NOTREADABLE:
+ return "Index not readable";
+
+ case INDEX_GENERIC:
+ return "Index error";
+
+ case INDEX_NAME_MAXLEN:
+ return "Index name max length exceeded";
+
+ case INDEX_MAXCOUNT:
+ return "Index count exceeds max";
+
+ case QUERY_ABORTED:
+ return "Query aborted";
+
+ case QUERY_QUEUEFULL:
+ return "Query queue full";
+
+ case QUERY_TIMEOUT:
+ return "Query timeout";
+
+ case QUERY_GENERIC:
+ return "Query error";
+
+ default:
+ return "";
+ }
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/Value.java b/src/main/java/com/aerospike/dsl/client/Value.java
new file mode 100644
index 0000000..600ba33
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/Value.java
@@ -0,0 +1,1647 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client;
+
+import com.aerospike.dsl.client.cdt.MapOrder;
+import com.aerospike.dsl.client.cdt.MapReturnType;
+import com.aerospike.dsl.client.command.Buffer;
+import com.aerospike.dsl.client.command.ParticleType;
+import com.aerospike.dsl.client.util.Packer;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.UUID;
+
+/**
+ * Polymorphic value classes used to efficiently serialize objects into the wire protocol.
+ */
+public abstract class Value {
+ /**
+ * Should client send boolean particle type for a boolean bin. If false,
+ * an integer particle type (1 or 0) is sent instead. Must be false for server
+ * versions less than 5.6 which do not support boolean bins. Can set to true for
+ * server 5.6+.
+ */
+ public static boolean UseBoolBin = true;
+
+ /**
+ * Should the client return a map when {@link MapReturnType#KEY_VALUE}
+ * is specified in a map read operation and the server returns a list of key/value pairs.
+ */
+ public static boolean ReturnMapForKeyValue = false;
+
+ /**
+ * Null value.
+ */
+ public static final Value NULL = NullValue.INSTANCE;
+
+ /**
+ * Infinity value to be used in CDT range comparisons only.
+ */
+ public static final Value INFINITY = new InfinityValue();
+
+ /**
+ * Wildcard value to be used in CDT range comparisons only.
+ */
+ public static final Value WILDCARD = new WildcardValue();
+
+ /**
+ * Get string or null value instance.
+ */
+ public static Value get(String value) {
+ return (value == null)? NullValue.INSTANCE : new StringValue(value);
+ }
+
+ /**
+ * Get byte array or null value instance.
+ */
+ public static Value get(byte[] value) {
+ return (value == null)? NullValue.INSTANCE : new BytesValue(value);
+ }
+
+ /**
+ * Get byte array with type or null value instance.
+ */
+ public static Value get(byte[] value, int type) {
+ return (value == null)? NullValue.INSTANCE : new BytesValue(value, type);
+ }
+
+ /**
+ * Get byte segment or null value instance.
+ */
+ public static Value get(byte[] value, int offset, int length) {
+ return (value == null)? NullValue.INSTANCE : new ByteSegmentValue(value, offset, length);
+ }
+
+ /**
+ * Get byte segment or null value instance.
+ */
+ public static Value get(ByteBuffer bb) {
+ return (bb == null)? NullValue.INSTANCE : new BytesValue(bb.array());
+ }
+
+ /**
+ * Get byte value instance.
+ */
+ public static Value get(byte value) {
+ return new ByteValue(value);
+ }
+
+ /**
+ * Get short value instance.
+ */
+ public static Value get(short value) {
+ return new ShortValue(value);
+ }
+
+ /**
+ * Get integer value instance.
+ */
+ public static Value get(int value) {
+ return new IntegerValue(value);
+ }
+
+ /**
+ * Get long value instance.
+ */
+ public static Value get(long value) {
+ return new LongValue(value);
+ }
+
+ /**
+ * Get double value instance.
+ */
+ public static Value get(double value) {
+ return new DoubleValue(value);
+ }
+
+ /**
+ * Get float value instance.
+ */
+ public static Value get(float value) {
+ return new FloatValue(value);
+ }
+
+ /**
+ * Get boolean value instance.
+ */
+ public static Value get(boolean value) {
+ if (UseBoolBin) {
+ return new BooleanValue(value);
+ }
+ else {
+ return new BoolIntValue(value);
+ }
+ }
+
+ /**
+ * Get enum value string instance.
+ */
+ public static Value get(Enum> value) {
+ return (value == null)? NullValue.INSTANCE : new StringValue(value.toString());
+ }
+
+ /**
+ * Get UUID value string instance.
+ */
+ public static Value get(UUID value) {
+ return (value == null)? NullValue.INSTANCE : new StringValue(value.toString());
+ }
+
+ /**
+ * Get list or null value instance.
+ */
+ public static Value get(List> value) {
+ return (value == null)? NullValue.INSTANCE : new ListValue(value);
+ }
+
+ /**
+ * Get map or null value instance.
+ */
+ public static Value get(Map,?> value) {
+ return (value == null)? NullValue.INSTANCE : new MapValue(value);
+ }
+
+ /**
+ * Get sorted map or null value instance.
+ */
+ public static Value get(SortedMap,?> value) {
+ return (value == null)? NullValue.INSTANCE : new MapValue(value, MapOrder.KEY_ORDERED);
+ }
+
+ /**
+ * This method is deprecated.
+ * Use {@link #get(Map)} if the map is unsorted (like HashMap).
+ * Use {@link #get(SortedMap)} if the map is sorted (like TreeMap).
+ *
+ * Get map or null value instance.
+ */
+ @Deprecated
+ public static Value get(Map,?> value, MapOrder order) {
+ return (value == null)? NullValue.INSTANCE : new MapValue(value, order);
+ }
+
+ /**
+ * Get sorted map or null value instance.
+ */
+ public static Value get(List extends Entry,?>> value, MapOrder mapOrder) {
+ return (value == null)? NullValue.INSTANCE : new SortedMapValue(value, mapOrder);
+ }
+
+ /**
+ * Get value array instance.
+ */
+ public static Value get(Value[] value) {
+ return (value == null)? NullValue.INSTANCE : new ValueArray(value);
+ }
+
+ /**
+ * Get GeoJSON or null value instance.
+ */
+ public static Value getAsGeoJSON(String value) {
+ return (value == null)? NullValue.INSTANCE : new GeoJSONValue(value);
+ }
+
+ /**
+ * Get HyperLogLog or null value instance.
+ */
+ public static Value getAsHLL(byte[] value) {
+ return (value == null)? NullValue.INSTANCE : new HLLValue(value);
+ }
+
+ /**
+ * Get null value instance.
+ */
+ public static Value getAsNull() {
+ return NullValue.INSTANCE;
+ }
+
+ /**
+ * Determine value given generic object.
+ * This is the slowest of the Value get() methods.
+ * Useful when copying records from one cluster to another.
+ */
+ public static Value get(Object value) {
+ if (value == null) {
+ return NullValue.INSTANCE;
+ }
+
+ if (value instanceof Value) {
+ return (Value)value;
+ }
+
+ if (value instanceof byte[]) {
+ return new BytesValue((byte[])value);
+ }
+
+ if (value instanceof String) {
+ return new StringValue((String)value);
+ }
+
+ if (value instanceof Integer) {
+ return new IntegerValue((Integer)value);
+ }
+
+ if (value instanceof Long) {
+ return new LongValue((Long)value);
+ }
+
+ if (value instanceof List>) {
+ return new ListValue((List>)value);
+ }
+
+ if (value instanceof Map,?>) {
+ return new MapValue((Map,?>)value);
+ }
+
+ if (value instanceof Double) {
+ return new DoubleValue((Double)value);
+ }
+
+ if (value instanceof Float) {
+ return new FloatValue((Float)value);
+ }
+
+ if (value instanceof Short) {
+ return new ShortValue((Short)value);
+ }
+
+ if (value instanceof Boolean) {
+ if (UseBoolBin) {
+ return new BooleanValue((Boolean)value);
+ }
+ else {
+ return new BoolIntValue((Boolean)value);
+ }
+ }
+
+ if (value instanceof Byte) {
+ return new ByteValue((byte)value);
+ }
+
+ if (value instanceof Character) {
+ return Value.get(((Character)value).charValue());
+ }
+
+ if (value instanceof Enum) {
+ return new StringValue(value.toString());
+ }
+
+ if (value instanceof UUID) {
+ return new StringValue(value.toString());
+ }
+
+ if (value instanceof ByteBuffer) {
+ ByteBuffer bb = (ByteBuffer)value;
+ return new BytesValue(bb.array());
+ }
+
+ throw new AerospikeException("Unsupported type: " + value.getClass().getName());
+ }
+
+ /**
+ * Get value from Record object. Useful when copying records from one cluster to another.
+ * @deprecated
+ *
Use {@link Value#get(Object)} instead.
+ */
+ @Deprecated
+ public static Value getFromRecordObject(Object value) {
+ return Value.get(value);
+ }
+
+ /**
+ * Calculate number of bytes necessary to serialize the value in the wire protocol.
+ */
+ public abstract int estimateSize() throws AerospikeException;
+
+ /**
+ * Serialize the value in the wire protocol.
+ */
+ public abstract int write(byte[] buffer, int offset) throws AerospikeException;
+
+ /**
+ * Serialize the value using MessagePack.
+ */
+ public abstract void pack(Packer packer);
+
+ /**
+ * Validate if value type can be used as a key.
+ * @throws AerospikeException if type can't be used as a key.
+ */
+ public void validateKeyType() throws AerospikeException {
+ }
+
+ /**
+ * Get wire protocol value type.
+ */
+ public abstract int getType();
+
+ /**
+ * Return original value as an Object.
+ */
+ public abstract Object getObject();
+
+ /**
+ * Return value as an integer.
+ */
+ public int toInteger() {
+ return 0;
+ }
+
+ /**
+ * Return value as a long.
+ */
+ public long toLong() {
+ return 0;
+ }
+
+ /**
+ * Empty value.
+ */
+ public static final class NullValue extends Value {
+ public static final NullValue INSTANCE = new NullValue();
+
+ @Override
+ public int estimateSize() {
+ return 0;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ return 0;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packNil();
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: null");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.NULL;
+ }
+
+ @Override
+ public Object getObject() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return true;
+ }
+ return this.getClass().equals(other.getClass());
+ }
+
+ @Override
+ public final int hashCode() {
+ return 0;
+ }
+ }
+
+ /**
+ * Byte array value.
+ */
+ public static final class BytesValue extends Value {
+ private final byte[] bytes;
+ private final int type;
+
+ public BytesValue(byte[] bytes) {
+ this.bytes = bytes;
+ this.type = ParticleType.BLOB;
+ }
+
+ public BytesValue(byte[] bytes, int type) {
+ this.bytes = bytes;
+ this.type = type;
+ }
+
+ @Override
+ public int estimateSize() {
+ return bytes.length;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+ return bytes.length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packParticleBytes(bytes, type);
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ @Override
+ public Object getObject() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return Buffer.bytesToHexString(bytes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ Arrays.equals(this.bytes, ((BytesValue)other).bytes));
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(bytes);
+ }
+ }
+
+ /**
+ * Byte segment value.
+ */
+ public static final class ByteSegmentValue extends Value {
+ private final byte[] bytes;
+ private final int offset;
+ private final int length;
+
+ public ByteSegmentValue(byte[] bytes, int offset, int length) {
+ this.bytes = bytes;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ @Override
+ public int estimateSize() {
+ return length;
+
+ }
+
+ @Override
+ public int write(byte[] buffer, int targetOffset) {
+ System.arraycopy(bytes, offset, buffer, targetOffset, length);
+ return length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packParticleBytes(bytes, offset, length);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.BLOB;
+ }
+
+ @Override
+ public Object getObject() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return Buffer.bytesToHexString(bytes, offset, length);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (! this.getClass().equals(obj.getClass())) {
+ return false;
+ }
+ ByteSegmentValue other = (ByteSegmentValue)obj;
+
+ if (this.length != other.length) {
+ return false;
+ }
+
+ for (int i = 0; i < length; i++) {
+ if (this.bytes[this.offset + i] != other.bytes[other.offset + i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ for (int i = 0; i < length; i++) {
+ result = 31 * result + bytes[offset+i];
+ }
+ return result;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public int getLength() {
+ return length;
+ }
+ }
+
+ /**
+ * Byte value.
+ */
+ public static final class ByteValue extends Value {
+ private final byte value;
+
+ public ByteValue(byte value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.longToBytes(value & 0xff, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packInt(value & 0xff);
+ }
+
+ @Override
+ public int getType() {
+ // The server does not natively handle one byte, so store as long (8 byte integer).
+ return ParticleType.INTEGER;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(value & 0xff);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((ByteValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value & 0xff;
+ }
+
+ @Override
+ public int toInteger() {
+ return value & 0xff;
+ }
+
+ @Override
+ public long toLong() {
+ return value & 0xff;
+ }
+ }
+
+ /**
+ * String value.
+ */
+ public static final class StringValue extends Value {
+ private final String value;
+
+ public StringValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return Buffer.estimateSizeUtf8(value);
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ return Buffer.stringToUtf8(value, buffer, offset);
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packParticleString(value);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.STRING;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value.equals(((StringValue)other).value));
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+ }
+
+ /**
+ * Short value.
+ */
+ public static final class ShortValue extends Value {
+ private final short value;
+
+ public ShortValue(short value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.longToBytes(value, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packInt(value);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.INTEGER;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Short.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((ShortValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ @Override
+ public int toInteger() {
+ return value;
+ }
+
+ @Override
+ public long toLong() {
+ return value;
+ }
+ }
+
+ /**
+ * Integer value.
+ */
+ public static final class IntegerValue extends Value {
+ private final int value;
+
+ public IntegerValue(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.longToBytes(value, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packInt(value);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.INTEGER;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((IntegerValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ @Override
+ public int toInteger() {
+ return value;
+ }
+
+ @Override
+ public long toLong() {
+ return value;
+ }
+ }
+
+ /**
+ * Long value.
+ */
+ public static final class LongValue extends Value {
+ private final long value;
+
+ public LongValue(long value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.longToBytes(value, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packLong(value);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.INTEGER;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Long.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((LongValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)(value ^ (value >>> 32));
+ }
+
+ @Override
+ public int toInteger() {
+ return (int)value;
+ }
+
+ @Override
+ public long toLong() {
+ return value;
+ }
+ }
+
+ /**
+ * Double value.
+ */
+ public static final class DoubleValue extends Value {
+ private final double value;
+
+ public DoubleValue(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.doubleToBytes(value, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packDouble(value);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.DOUBLE;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Double.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((DoubleValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ long bits = Double.doubleToLongBits(value);
+ return (int)(bits ^ (bits >>> 32));
+ }
+
+ @Override
+ public int toInteger() {
+ return (int)value;
+ }
+
+ @Override
+ public long toLong() {
+ return (long)value;
+ }
+ }
+
+ /**
+ * Float value.
+ */
+ public static final class FloatValue extends Value {
+ private final float value;
+
+ public FloatValue(float value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.doubleToBytes(value, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packFloat(value);
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.DOUBLE;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Float.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((FloatValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(value);
+ }
+
+ @Override
+ public int toInteger() {
+ return (int)value;
+ }
+
+ @Override
+ public long toLong() {
+ return (long)value;
+ }
+ }
+
+ /**
+ * Boolean value.
+ */
+ public static final class BooleanValue extends Value {
+ private final boolean value;
+
+ public BooleanValue(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 1;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ buffer[offset] = value? (byte)1 : (byte)0;
+ return 1;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packBoolean(value);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: boolean");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.BOOL;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Boolean.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((BooleanValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value ? 1231 : 1237;
+ }
+
+ @Override
+ public int toInteger() {
+ return value? 1 : 0;
+ }
+
+ @Override
+ public long toLong() {
+ return value? 1L : 0L;
+ }
+ }
+
+ /**
+ * Boolean value that converts to integer when sending a bin to the server.
+ * This class will be deleted once full conversion to boolean particle type
+ * is complete.
+ */
+ public static final class BoolIntValue extends Value {
+ private final boolean value;
+
+ public BoolIntValue(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ return 8;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ Buffer.longToBytes(value? 1L : 0L, buffer, offset);
+ return 8;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packBoolean(value);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: BoolIntValue");
+ }
+
+ @Override
+ public int getType() {
+ // The server does not natively handle boolean, so store as long (8 byte integer).
+ return ParticleType.INTEGER;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Boolean.toString(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value == ((BoolIntValue)other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value ? 1231 : 1237;
+ }
+
+ @Override
+ public int toInteger() {
+ return value? 1 : 0;
+ }
+
+ @Override
+ public long toLong() {
+ return value? 1L : 0L;
+ }
+ }
+
+ /**
+ * GeoJSON value.
+ */
+ public static final class GeoJSONValue extends Value {
+ private final String value;
+
+ public GeoJSONValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public int estimateSize() {
+ // flags + ncells + jsonstr
+ return 1 + 2 + Buffer.estimateSizeUtf8(value);
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ buffer[offset] = 0; // flags
+ Buffer.shortToBytes(0, buffer, offset + 1); // ncells
+ return 1 + 2 + Buffer.stringToUtf8(value, buffer, offset + 3); // jsonstr
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packGeoJSON(value);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: GeoJson");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.GEOJSON;
+ }
+
+ @Override
+ public Object getObject() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.value.equals(((GeoJSONValue)other).value));
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+ }
+
+ /**
+ * HyperLogLog value.
+ */
+ public static final class HLLValue extends Value {
+ private final byte[] bytes;
+
+ public HLLValue(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public int estimateSize() {
+ return bytes.length;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+ return bytes.length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packParticleBytes(bytes);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: HLL");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.HLL;
+ }
+
+ @Override
+ public Object getObject() {
+ return bytes;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return Buffer.bytesToHexString(bytes);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ Arrays.equals(this.bytes, ((HLLValue)other).bytes));
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(bytes);
+ }
+ }
+
+ /**
+ * Value array.
+ */
+ public static final class ValueArray extends Value {
+ private final Value[] array;
+ private byte[] bytes;
+
+ public ValueArray(Value[] array) {
+ this.array = array;
+ }
+
+ @Override
+ public int estimateSize() throws AerospikeException {
+ bytes = Packer.pack(array);
+ return bytes.length;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+ return bytes.length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packValueArray(array);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: value[]");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.LIST;
+ }
+
+ @Override
+ public Object getObject() {
+ return array;
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(array);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ Arrays.equals(this.array, ((ValueArray)other).array));
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(array);
+ }
+ }
+
+ /**
+ * List value.
+ */
+ public static final class ListValue extends Value {
+ private final List> list;
+ private byte[] bytes;
+
+ public ListValue(List> list) {
+ this.list = list;
+ }
+
+ @Override
+ public int estimateSize() throws AerospikeException {
+ bytes = Packer.pack(list);
+ return bytes.length;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+ return bytes.length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packList(list);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: list");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.LIST;
+ }
+
+ @Override
+ public Object getObject() {
+ return list;
+ }
+
+ @Override
+ public String toString() {
+ return list.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.list.equals(((ListValue)other).list));
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+ }
+
+ /**
+ * Map value.
+ */
+ public static final class MapValue extends Value {
+ private final Map,?> map;
+ private final MapOrder order;
+ private byte[] bytes;
+
+ public MapValue(Map,?> map) {
+ this.map = map;
+ this.order = getMapOrder(map);
+ }
+
+ public MapValue(Map,?> map, MapOrder order) {
+ this.map = map;
+ this.order = order;
+ }
+
+ @Override
+ public int estimateSize() throws AerospikeException {
+ bytes = Packer.pack(map, order);
+ return bytes.length;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+ return bytes.length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packMap(map, order);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: map");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.MAP;
+ }
+
+ @Override
+ public Object getObject() {
+ return map;
+ }
+
+ @Override
+ public String toString() {
+ return map.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()) &&
+ this.map.equals(((MapValue)other).map));
+ }
+
+ @Override
+ public int hashCode() {
+ return map.hashCode();
+ }
+
+ public static MapOrder getMapOrder(Map,?> map) {
+ return (map instanceof SortedMap,?>)? MapOrder.KEY_ORDERED : MapOrder.UNORDERED;
+ }
+ }
+
+ /**
+ * Sorted map value.
+ */
+ public static final class SortedMapValue extends Value {
+ private final List extends Entry,?>> list;
+ private byte[] bytes;
+ private final MapOrder order;
+
+ public SortedMapValue(List extends Entry,?>> list, MapOrder order) {
+ this.list = list;
+ this.order = order;
+ }
+
+ @Override
+ public int estimateSize() throws AerospikeException {
+ bytes = Packer.pack(list, order);
+ return bytes.length;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ System.arraycopy(bytes, 0, buffer, offset, bytes.length);
+ return bytes.length;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packMap(list, order);
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: map");
+ }
+
+ @Override
+ public int getType() {
+ return ParticleType.MAP;
+ }
+
+ @Override
+ public Object getObject() {
+ return list;
+ }
+
+ @Override
+ public String toString() {
+ return list.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || ! this.getClass().equals(other.getClass())) {
+ return false;
+ }
+ SortedMapValue o = (SortedMapValue)other;
+ return this.order == o.order && this.list.equals(o.list);
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+ }
+
+ /**
+ * Infinity value.
+ */
+ public static final class InfinityValue extends Value {
+ @Override
+ public int estimateSize() {
+ return 0;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ return 0;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packInfinity();
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: INF");
+ }
+
+ @Override
+ public int getType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid particle type: INF");
+ }
+
+ @Override
+ public Object getObject() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "INF";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()));
+ }
+
+ @Override
+ public final int hashCode() {
+ return 0;
+ }
+ }
+
+ /**
+ * Wildcard value.
+ */
+ public static final class WildcardValue extends Value {
+ @Override
+ public int estimateSize() {
+ return 0;
+ }
+
+ @Override
+ public int write(byte[] buffer, int offset) {
+ return 0;
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packWildcard();
+ }
+
+ @Override
+ public void validateKeyType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid key type: wildcard");
+ }
+
+ @Override
+ public int getType() {
+ throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid particle type: wildcard");
+ }
+
+ @Override
+ public Object getObject() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "*";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other != null &&
+ this.getClass().equals(other.getClass()));
+ }
+
+ @Override
+ public final int hashCode() {
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/cdt/CTX.java b/src/main/java/com/aerospike/dsl/client/cdt/CTX.java
new file mode 100644
index 0000000..e6554ff
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/cdt/CTX.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2012-2022 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.cdt;
+
+import com.aerospike.dsl.client.AerospikeException;
+import com.aerospike.dsl.client.Value;
+import com.aerospike.dsl.client.util.Crypto;
+import com.aerospike.dsl.client.util.Pack;
+import com.aerospike.dsl.client.util.Unpacker;
+
+import java.util.List;
+
+/**
+ * Nested CDT context. Identifies the location of nested list/map to apply the operation.
+ * for the current level. An array of CTX identifies location of the list/map on multiple
+ * levels on nesting.
+ */
+public final class CTX {
+ /**
+ * Lookup list by index offset.
+ *
+ * If the index is negative, the resolved index starts backwards from end of list.
+ * If an index is out of bounds, a parameter error will be returned. Examples:
+ *
+ *
0: First item.
+ *
4: Fifth item.
+ *
-1: Last item.
+ *
-3: Third to last item.
+ *
+ */
+ public static CTX listIndex(int index) {
+ return new CTX(0x10, Value.get(index));
+ }
+
+ /**
+ * Lookup list by rank.
+ *
+ *
0 = smallest value
+ *
N = Nth smallest value
+ *
-1 = largest value
+ *
+ */
+ public static CTX listRank(int rank) {
+ return new CTX(0x11, Value.get(rank));
+ }
+
+ /**
+ * Lookup list by value.
+ */
+ public static CTX listValue(Value key) {
+ return new CTX(0x13, key);
+ }
+
+ /**
+ * Lookup map by index offset.
+ *
+ * If the index is negative, the resolved index starts backwards from end of list.
+ * If an index is out of bounds, a parameter error will be returned. Examples:
+ *
+ *
0: First item.
+ *
4: Fifth item.
+ *
-1: Last item.
+ *
-3: Third to last item.
+ *
+ */
+ public static CTX mapIndex(int index) {
+ return new CTX(0x20, Value.get(index));
+ }
+
+ /**
+ * Lookup map by rank.
+ *
+ *
0 = smallest value
+ *
N = Nth smallest value
+ *
-1 = largest value
+ *
+ */
+ public static CTX mapRank(int rank) {
+ return new CTX(0x21, Value.get(rank));
+ }
+
+ /**
+ * Lookup map by key.
+ */
+ public static CTX mapKey(Value key) {
+ return new CTX(0x22, key);
+ }
+
+ /**
+ * Lookup map by value.
+ */
+ public static CTX mapValue(Value key) {
+ return new CTX(0x23, key);
+ }
+
+ /**
+ * Serialize context array to bytes.
+ */
+ public static byte[] toBytes(CTX[] ctx) {
+ return Pack.pack(ctx);
+ }
+
+ /**
+ * Deserialize bytes to context array.
+ */
+ public static CTX[] fromBytes(byte[] bytes) {
+ List> list = (List>) Unpacker.unpackObjectList(bytes, 0, bytes.length);
+ int max = list.size();
+ CTX[] ctx = new CTX[max / 2];
+ int i = 0;
+ int count = 0;
+
+ while (i < max) {
+ int id = (int) (long) (Long) list.get(i);
+
+ if (++i >= max) {
+ throw new AerospikeException.Parse("List count must be even");
+ }
+
+
+ Object obj = list.get(i);
+ Value val = Value.get(obj);
+
+ ctx[count++] = new CTX(id, val);
+ i++;
+ }
+ return ctx;
+ }
+
+ /**
+ * Serialize context array to base64 encoded string.
+ */
+ public static String toBase64(CTX[] ctx) {
+ byte[] bytes = Pack.pack(ctx);
+ return Crypto.encodeBase64(bytes);
+ }
+
+ /**
+ * Deserialize base64 encoded string to context array.
+ */
+ public static CTX[] fromBase64(String base64) {
+ byte[] b64 = base64.getBytes();
+ byte[] bytes = Crypto.decodeBase64(b64, 0, b64.length);
+ return fromBytes(bytes);
+ }
+
+ public final int id;
+ public final Value value;
+
+ private CTX(int id, Value value) {
+ this.id = id;
+ this.value = value;
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/cdt/ListReturnType.java b/src/main/java/com/aerospike/dsl/client/cdt/ListReturnType.java
new file mode 100644
index 0000000..0d098eb
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/cdt/ListReturnType.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012-2022 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.cdt;
+
+/**
+ * List return type. Type of data to return when selecting or removing items from the list.
+ */
+public final class ListReturnType {
+ /**
+ * Do not return a result.
+ */
+ public static final int NONE = 0;
+
+ /**
+ * Return index offset order.
+ *
+ *
0 = first key
+ *
N = Nth key
+ *
-1 = last key
+ *
+ */
+ public static final int INDEX = 1;
+
+ /**
+ * Return reverse index offset order.
+ *
+ *
0 = last key
+ *
-1 = first key
+ *
+ */
+ public static final int REVERSE_INDEX = 2;
+
+ /**
+ * Return value order.
+ *
+ *
0 = smallest value
+ *
N = Nth smallest value
+ *
-1 = largest value
+ *
+ */
+ public static final int RANK = 3;
+
+ /**
+ * Return reverse value order.
+ *
+ *
0 = largest value
+ *
N = Nth largest value
+ *
-1 = smallest value
+ *
+ */
+ public static final int REVERSE_RANK = 4;
+
+ /**
+ * Return count of items selected.
+ */
+ public static final int COUNT = 5;
+
+ /**
+ * Return value for single key read and value list for range read.
+ */
+ public static final int VALUE = 7;
+
+ /**
+ * Return true if count > 0.
+ */
+ public static final int EXISTS = 13;
+
+ /**
+ * Invert meaning of list command and return values. For example:
+ *
+ * With the INVERTED flag enabled, the items outside of the specified index range will be removed and returned.
+ */
+ public static final int INVERTED = 0x10000;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/cdt/MapOrder.java b/src/main/java/com/aerospike/dsl/client/cdt/MapOrder.java
new file mode 100644
index 0000000..04c136e
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/cdt/MapOrder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012-2021 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.cdt;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Map storage order.
+ */
+@Slf4j
+public enum MapOrder {
+ /**
+ * Map is not ordered. This is the default.
+ */
+ UNORDERED(0, 0x40),
+
+ /**
+ * Order map by key.
+ */
+ KEY_ORDERED(1, 0x80),
+
+ /**
+ * Order map by key, then value.
+ */
+ KEY_VALUE_ORDERED(3, 0xc0);
+
+ public final int attributes;
+ public final int flag;
+
+ private MapOrder(int attributes, int flag) {
+ this.attributes = attributes;
+ this.flag = flag;
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/cdt/MapReturnType.java b/src/main/java/com/aerospike/dsl/client/cdt/MapReturnType.java
new file mode 100644
index 0000000..7148962
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/cdt/MapReturnType.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.cdt;
+
+/**
+ * Map return type. Type of data to return when selecting or removing items from the map.
+ */
+public final class MapReturnType {
+ /**
+ * Do not return a result.
+ */
+ public static final int NONE = 0;
+
+ /**
+ * Return key index order.
+ *
+ *
0 = first key
+ *
N = Nth key
+ *
-1 = last key
+ *
+ */
+ public static final int INDEX = 1;
+
+ /**
+ * Return reverse key order.
+ *
+ *
0 = last key
+ *
-1 = first key
+ *
+ */
+ public static final int REVERSE_INDEX = 2;
+
+ /**
+ * Return value order.
+ *
+ *
0 = smallest value
+ *
N = Nth smallest value
+ *
-1 = largest value
+ *
+ */
+ public static final int RANK = 3;
+
+ /**
+ * Return reverse value order.
+ *
+ *
0 = largest value
+ *
N = Nth largest value
+ *
-1 = smallest value
+ *
+ */
+ public static final int REVERSE_RANK = 4;
+
+ /**
+ * Return count of items selected.
+ */
+ public static final int COUNT = 5;
+
+ /**
+ * Return key for single key read and key list for range read.
+ */
+ public static final int KEY = 6;
+
+ /**
+ * Return value for single key read and value list for range read.
+ */
+ public static final int VALUE = 7;
+
+ /**
+ * Return key/value items. The possible return types are:
+ *
+ *
HashMap : Returned for unordered maps
+ *
TreeMap : Returned for key ordered maps
+ *
List<Entry> : Returned for range results where range order needs to be preserved.
+ *
+ */
+ public static final int KEY_VALUE = 8;
+
+ /**
+ * Return true if count > 0.
+ */
+ public static final int EXISTS = 13;
+
+ /**
+ * Return an unordered map.
+ */
+ public static final int UNORDERED_MAP = 16;
+
+ /**
+ * Return an ordered map.
+ */
+ public static final int ORDERED_MAP = 17;
+
+ /**
+ * Invert meaning of map command and return values. For example:
+ *
+ * With the INVERTED flag enabled, the keys outside of the specified key range will be removed and returned.
+ */
+ public static final int INVERTED = 0x10000;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/command/Buffer.java b/src/main/java/com/aerospike/dsl/client/command/Buffer.java
new file mode 100644
index 0000000..5fcbf5f
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/command/Buffer.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2012-2025 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.command;
+
+import com.aerospike.dsl.client.AerospikeException;
+import com.aerospike.dsl.client.Value;
+import com.aerospike.dsl.client.util.Unpacker;
+import com.aerospike.dsl.client.util.Utf8;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public final class Buffer {
+
+ public static Value bytesToKeyValue(int type, byte[] buf, int offset, int len)
+ throws AerospikeException {
+
+ switch (type) {
+ case ParticleType.STRING:
+ return Value.get(Buffer.utf8ToString(buf, offset, len));
+
+ case ParticleType.INTEGER:
+ return bytesToLongValue(buf, offset, len);
+
+ case ParticleType.DOUBLE:
+ return new Value.DoubleValue(Buffer.bytesToDouble(buf, offset));
+
+ case ParticleType.BLOB:
+ return Value.get(Arrays.copyOfRange(buf, offset, offset+len));
+
+ default:
+ return null;
+ }
+ }
+
+ public static Object bytesToParticle(int type, byte[] buf, int offset, int len)
+ throws AerospikeException {
+
+ switch (type) {
+ case ParticleType.STRING:
+ return Buffer.utf8ToString(buf, offset, len);
+
+ case ParticleType.INTEGER:
+ return Buffer.bytesToNumber(buf, offset, len);
+
+ case ParticleType.BOOL:
+ return Buffer.bytesToBool(buf, offset, len);
+
+ case ParticleType.DOUBLE:
+ return Buffer.bytesToDouble(buf, offset);
+
+ case ParticleType.BLOB:
+ return Arrays.copyOfRange(buf, offset, offset+len);
+
+ case ParticleType.JBLOB:
+ // Java deserialization is no longer allowed, so return java serialized blob as a byte[].
+ // The user can deserialize the byte[] from the record bin using the following code:
+ //
+ // try (ByteArrayInputStream bastream = new ByteArrayInputStream(bytes, 0, len)) {
+ // try (ObjectInputStream oistream = new ObjectInputStream(bastream)) {
+ // return oistream.readObject();
+ // }
+ // }
+ return Arrays.copyOfRange(buf, offset, offset+len);
+
+ case ParticleType.GEOJSON:
+ return Buffer.bytesToGeoJSON(buf, offset, len);
+
+ case ParticleType.HLL:
+ return Buffer.bytesToHLL(buf, offset, len);
+
+ case ParticleType.LIST:
+ return Unpacker.unpackObjectList(buf, offset, len);
+
+ case ParticleType.MAP:
+ return Unpacker.unpackObjectMap(buf, offset, len);
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Estimate size of Utf8 encoded bytes without performing the actual encoding.
+ */
+ public static int estimateSizeUtf8(String s) {
+ if (s == null || s.length() == 0) {
+ return 0;
+ }
+ return Utf8.encodedLength(s);
+ }
+
+ public static byte[] stringToUtf8(String s) {
+ if (s == null || s.length() == 0) {
+ return new byte[0];
+ }
+ int size = Utf8.encodedLength(s);
+ byte[] bytes = new byte[size];
+ stringToUtf8(s, bytes, 0);
+ return bytes;
+ }
+
+ /**
+ * Convert input string to UTF-8, copies into buffer (at given offset).
+ * Returns number of bytes in the string.
+ *
+ * Java's internal UTF8 conversion is very, very slow.
+ * This is, rather amazingly, 8x faster than the to-string method.
+ * Returns the number of bytes this translated into.
+ */
+ public static int stringToUtf8(String s, byte[] buf, int offset) {
+ if (s == null) {
+ return 0;
+ }
+ int length = s.length();
+ int startOffset = offset;
+
+ for (int i = 0; i < length; i++) {
+ int c = s.charAt(i);
+ if (c < 0x80) {
+ buf[offset++] = (byte) c;
+ }
+ else if (c < 0x800) {
+ buf[offset++] = (byte)(0xc0 | ((c >> 6)));
+ buf[offset++] = (byte)(0x80 | (c & 0x3f));
+ }
+ else {
+ // Encountered a different encoding other than 2-byte UTF8. Let java handle it.
+ byte[] value = s.getBytes(StandardCharsets.UTF_8);
+ System.arraycopy(value, 0, buf, startOffset, value.length);
+ return value.length;
+ }
+ }
+ return offset - startOffset;
+ }
+
+ public static String utf8ToString(byte[] buf, int offset, int length) {
+ // A Thread local implementation does not help here, so
+ // allocate character buffer each time.
+ if (length == 0) {
+ return "";
+ }
+
+ char[] charBuffer = new char[length];
+ int charCount = 0;
+ int limit = offset + length;
+ int origoffset = offset;
+
+ while (offset < limit ) {
+ int b1 = buf[offset];
+
+ if (b1 >= 0) {
+ charBuffer[charCount++] = (char)b1;
+ offset++;
+ }
+ else if ((b1 >> 5) == -2) {
+ int b2 = buf[offset + 1];
+ charBuffer[charCount++] = (char) (((b1 << 6) ^ b2) ^ 0x0f80);
+ offset += 2;
+ }
+ else {
+ // Encountered an UTF encoding which uses more than 2 bytes.
+ // Use a native function to do the conversion.
+ return new String(buf, origoffset, length, StandardCharsets.UTF_8);
+ }
+ }
+ return new String(charBuffer, 0, charCount);
+ }
+
+ public static String utf8ToString(byte[] buf, int offset, int length, StringBuilder sb) {
+ if (length == 0) {
+ return "";
+ }
+
+ // This method is designed to accommodate multiple string conversions on the same
+ // thread, but without the ThreadLocal overhead. The StringBuilder instance is
+ // created on the stack and passed in each method invocation.
+ sb.setLength(0);
+ int limit = offset + length;
+ int origoffset = offset;
+
+ while (offset < limit ) {
+ if ((buf[offset] & 0x80) == 0) { // 1 byte
+ char c = (char) buf[offset];
+ sb.append(c);
+ offset++;
+ }
+ else if ((buf[offset] & 0xE0) == 0xC0) { // 2 bytes
+ char c = (char) (((buf[offset] & 0x1f) << 6) | (buf[offset+1] & 0x3f));
+ sb.append(c);
+ offset += 2;
+ }
+ else {
+ // Encountered an UTF encoding which uses more than 2 bytes.
+ // Use a native function to do the conversion.
+ return new String(buf, origoffset, length, StandardCharsets.UTF_8);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convert UTF8 numeric digits to an integer. Negative integers are not supported.
+ *
+ * Input format: 1234
+ */
+ public static int utf8DigitsToInt(byte[] buf, int begin, int end) {
+ int val = 0;
+ int mult = 1;
+
+ for (int i = end - 1; i >= begin; i--) {
+ val += (buf[i] - 48) * mult;
+ mult *= 10;
+ }
+ return val;
+ }
+
+ public static String bytesToHexString(byte[] buf) {
+ if (buf == null || buf.length == 0) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder(buf.length * 2);
+
+ for (int i = 0; i < buf.length; i++) {
+ sb.append(String.format("%02x", buf[i]));
+ }
+ return sb.toString();
+ }
+
+ public static String bytesToHexString(byte[] buf, int offset, int length) {
+ StringBuilder sb = new StringBuilder(length * 2);
+
+ for (int i = offset; i < length; i++) {
+ sb.append(String.format("%02x", buf[i]));
+ }
+ return sb.toString();
+ }
+
+ public static Value bytesToLongValue(byte[] buf, int offset, int len) {
+ long val = 0;
+
+ for (int i = 0; i < len; i++) {
+ val <<= 8;
+ val |= buf[offset+i] & 0xFF;
+ }
+
+ return new Value.LongValue(val);
+ }
+
+ public static Object bytesToGeoJSON(byte[] buf, int offset, int len) {
+ // Ignore the flags for now
+ int ncells = bytesToShort(buf, offset + 1);
+ int hdrsz = 1 + 2 + (ncells * 8);
+ return Value.getAsGeoJSON(Buffer.utf8ToString(buf, offset + hdrsz, len - hdrsz));
+ }
+
+ public static Object bytesToHLL(byte[] buf, int offset, int len) {
+ byte[] bytes = Arrays.copyOfRange(buf, offset, offset+len);
+ return Value.getAsHLL(bytes);
+ }
+
+ public static Object bytesToNumber(byte[] buf, int offset, int len) {
+ // Server always returns 8 for integer length.
+ if (len == 8) {
+ return bytesToLong(buf, offset);
+ }
+
+ // Handle other lengths just in case server changes.
+ if (len < 8) {
+ // Handle variable length long.
+ long val = 0;
+
+ for (int i = 0; i < len; i++) {
+ val <<= 8;
+ val |= buf[offset+i] & 0xFF;
+ }
+ return val;
+ }
+
+ // Handle huge numbers.
+ return bytesToBigInteger(buf, offset, len);
+ }
+
+ public static Object bytesToBigInteger(byte[] buf, int offset, int len) {
+ boolean negative = false;
+
+ if ((buf[offset] & 0x80) != 0) {
+ negative = true;
+ buf[offset] &= 0x7f;
+ }
+ byte[] bytes = new byte[len];
+ System.arraycopy(buf, offset, bytes, 0, len);
+
+ BigInteger big = new BigInteger(bytes);
+
+ if (negative) {
+ big = big.negate();
+ }
+ return big;
+ }
+
+ public static boolean bytesToBool(byte[] buf, int offset, int len) {
+ if (len <= 0) {
+ return false;
+ }
+ return (buf[offset] == 0)? false : true;
+ }
+
+ //-------------------------------------------------------
+ // 64 bit double conversions.
+ //-------------------------------------------------------
+
+ public static double bytesToDouble(byte[] buf, int offset) {
+ return Double.longBitsToDouble(bytesToLong(buf, offset));
+ }
+
+ public static void doubleToBytes(double v, byte[] buf, int offset) {
+ Buffer.longToBytes(Double.doubleToLongBits(v), buf, offset);
+ }
+
+ //-------------------------------------------------------
+ // 64 bit number conversions.
+ //-------------------------------------------------------
+
+ /**
+ * Convert long to big endian signed or unsigned 64 bits.
+ * The bit pattern will be the same regardless of sign.
+ */
+ public static void longToBytes(long v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 56);
+ buf[offset++] = (byte)(v >>> 48);
+ buf[offset++] = (byte)(v >>> 40);
+ buf[offset++] = (byte)(v >>> 32);
+ buf[offset++] = (byte)(v >>> 24);
+ buf[offset++] = (byte)(v >>> 16);
+ buf[offset++] = (byte)(v >>> 8);
+ buf[offset] = (byte)(v >>> 0);
+ }
+
+ /**
+ * Convert long to little endian signed or unsigned 64 bits.
+ * The bit pattern will be the same regardless of sign.
+ */
+ public static void longToLittleBytes(long v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 0);
+ buf[offset++] = (byte)(v >>> 8);
+ buf[offset++] = (byte)(v >>> 16);
+ buf[offset++] = (byte)(v >>> 24);
+ buf[offset++] = (byte)(v >>> 32);
+ buf[offset++] = (byte)(v >>> 40);
+ buf[offset++] = (byte)(v >>> 48);
+ buf[offset] = (byte)(v >>> 56);
+ }
+
+ /**
+ * Convert big endian signed 64 bits to long.
+ */
+ public static long bytesToLong(byte[] buf, int offset) {
+ return (
+ ((long)(buf[offset] & 0xFF) << 56) |
+ ((long)(buf[offset+1] & 0xFF) << 48) |
+ ((long)(buf[offset+2] & 0xFF) << 40) |
+ ((long)(buf[offset+3] & 0xFF) << 32) |
+ ((long)(buf[offset+4] & 0xFF) << 24) |
+ ((long)(buf[offset+5] & 0xFF) << 16) |
+ ((long)(buf[offset+6] & 0xFF) << 8) |
+ ((long)(buf[offset+7] & 0xFF) << 0)
+ );
+ }
+
+ /**
+ * Convert little endian signed 64 bits to long.
+ */
+ public static long littleBytesToLong(byte[] buf, int offset) {
+ return (
+ ((long)(buf[offset] & 0xFF) << 0) |
+ ((long)(buf[offset+1] & 0xFF) << 8) |
+ ((long)(buf[offset+2] & 0xFF) << 16) |
+ ((long)(buf[offset+3] & 0xFF) << 24) |
+ ((long)(buf[offset+4] & 0xFF) << 32) |
+ ((long)(buf[offset+5] & 0xFF) << 40) |
+ ((long)(buf[offset+6] & 0xFF) << 48) |
+ ((long)(buf[offset+7] & 0xFF) << 56)
+ );
+ }
+
+ //-------------------------------------------------------
+ // Transaction version conversions.
+ //-------------------------------------------------------
+
+ /**
+ * Convert long to a 7 byte record version for transaction.
+ */
+ public static void longToVersionBytes(long v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 0);
+ buf[offset++] = (byte)(v >>> 8);
+ buf[offset++] = (byte)(v >>> 16);
+ buf[offset++] = (byte)(v >>> 24);
+ buf[offset++] = (byte)(v >>> 32);
+ buf[offset++] = (byte)(v >>> 40);
+ buf[offset] = (byte)(v >>> 48);
+ }
+
+ /**
+ * Convert 7 byte record version to a long for transaction.
+ */
+ public static long versionBytesToLong(byte[] buf, int offset) {
+ return (
+ ((long)(buf[offset] & 0xFF) << 0) |
+ ((long)(buf[offset+1] & 0xFF) << 8) |
+ ((long)(buf[offset+2] & 0xFF) << 16) |
+ ((long)(buf[offset+3] & 0xFF) << 24) |
+ ((long)(buf[offset+4] & 0xFF) << 32) |
+ ((long)(buf[offset+5] & 0xFF) << 40) |
+ ((long)(buf[offset+6] & 0xFF) << 48)
+ );
+ }
+
+ //-------------------------------------------------------
+ // 32 bit number conversions.
+ //-------------------------------------------------------
+
+ /**
+ * Convert int to big endian signed or unsigned 32 bits.
+ * The bit pattern will be the same regardless of sign.
+ */
+ public static void intToBytes(int v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 24);
+ buf[offset++] = (byte)(v >>> 16);
+ buf[offset++] = (byte)(v >>> 8);
+ buf[offset] = (byte)(v >>> 0);
+ }
+
+ /**
+ * Convert int to little endian signed or unsigned 32 bits.
+ * The bit pattern will be the same regardless of sign.
+ */
+ public static void intToLittleBytes(int v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 0);
+ buf[offset++] = (byte)(v >>> 8);
+ buf[offset++] = (byte)(v >>> 16);
+ buf[offset] = (byte)(v >>> 24);
+ }
+
+ /**
+ * Convert big endian signed 32 bits to int.
+ */
+ public static int bytesToInt(byte[] buf, int offset) {
+ return (
+ ((buf[offset] & 0xFF) << 24) |
+ ((buf[offset+1] & 0xFF) << 16) |
+ ((buf[offset+2] & 0xFF) << 8) |
+ ((buf[offset+3] & 0xFF) << 0)
+ );
+ }
+
+ /**
+ * Convert little endian signed 32 bits to int.
+ */
+ public static int littleBytesToInt(byte[] buf, int offset) {
+ return (
+ ((buf[offset] & 0xFF) << 0) |
+ ((buf[offset+1] & 0xFF) << 8) |
+ ((buf[offset+2] & 0xFF) << 16) |
+ ((buf[offset+3] & 0xFF) << 24)
+ );
+ }
+
+ /**
+ * Convert big endian unsigned 32 bits to long.
+ */
+ public static long bigUnsigned32ToLong(byte[] buf, int offset) {
+ return (
+ ((long)(buf[offset] & 0xFF) << 24) |
+ ((long)(buf[offset+1] & 0xFF) << 16) |
+ ((long)(buf[offset+2] & 0xFF) << 8) |
+ ((long)(buf[offset+3] & 0xFF) << 0)
+ );
+ }
+
+ //-------------------------------------------------------
+ // 16 bit number conversions.
+ //-------------------------------------------------------
+
+ /**
+ * Convert int to big endian signed or unsigned 16 bits.
+ * The bit pattern will be the same regardless of sign.
+ */
+ public static void shortToBytes(int v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 8);
+ buf[offset] = (byte)(v >>> 0);
+ }
+
+ /**
+ * Convert int to little endian signed or unsigned 16 bits.
+ * The bit pattern will be the same regardless of sign.
+ */
+ public static void shortToLittleBytes(int v, byte[] buf, int offset) {
+ buf[offset++] = (byte)(v >>> 0);
+ buf[offset] = (byte)(v >>> 8);
+ }
+
+ /**
+ * Convert big endian unsigned 16 bits to int.
+ */
+ public static int bytesToShort(byte[] buf, int offset) {
+ return (
+ ((buf[offset] & 0xFF) << 8) |
+ ((buf[offset+1] & 0xFF) << 0)
+ );
+ }
+
+ /**
+ * Convert little endian unsigned 16 bits to int.
+ */
+ public static int littleBytesToShort(byte[] buf, int offset) {
+ return (
+ ((buf[offset] & 0xFF) << 0) |
+ ((buf[offset+1] & 0xFF) << 8)
+ );
+ }
+
+ /**
+ * Convert big endian signed 16 bits to short.
+ */
+ public static short bigSigned16ToShort(byte[] buf, int offset) {
+ return (short)(
+ ((buf[offset] & 0xFF) << 8) |
+ ((buf[offset+1] & 0xFF) << 0)
+ );
+ }
+
+ //-------------------------------------------------------
+ // Variable byte number conversions.
+ //-------------------------------------------------------
+
+ /**
+ * Encode an integer in variable 7-bit format.
+ * The high bit indicates if more bytes are used.
+ * Return byte size of integer.
+ */
+ public static int intToVarBytes(int v, byte[] buf, int offset) {
+ int i = offset;
+
+ while (i < buf.length && v >= 0x80) {
+ buf[i++] = (byte)(v | 0x80);
+ v >>>= 7;
+ }
+
+ if (i < buf.length) {
+ buf[i++] = (byte)v;
+ return i - offset;
+ }
+ return 0;
+ }
+
+ /**
+ * Decode an integer in variable 7-bit format.
+ * The high bit indicates if more bytes are used.
+ * Return value and byte size in array.
+ */
+ public static int[] varBytesToInt(byte[] buf, int offset) {
+ int i = offset;
+ int val = 0;
+ int shift = 0;
+ byte b;
+
+ do {
+ b = buf[i++];
+ val |= (b & 0x7F) << shift;
+ shift += 7;
+ } while ((b & 0x80) != 0);
+
+ return new int[] {val, i - offset};
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/command/ParticleType.java b/src/main/java/com/aerospike/dsl/client/command/ParticleType.java
new file mode 100644
index 0000000..13122ba
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/command/ParticleType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.command;
+
+/**
+ * Server particle types.
+ */
+public final class ParticleType {
+ public static final int NULL = 0;
+ public static final int INTEGER = 1;
+ public static final int DOUBLE = 2;
+ public static final int STRING = 3;
+ public static final int BLOB = 4;
+ public static final int JBLOB = 7;
+ public static final int BOOL = 17;
+ public static final int HLL = 18;
+ public static final int MAP = 19;
+ public static final int LIST = 20;
+ public static final int GEOJSON = 23;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/exp/Exp.java b/src/main/java/com/aerospike/dsl/client/exp/Exp.java
new file mode 100644
index 0000000..9a186b1
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/exp/Exp.java
@@ -0,0 +1,1829 @@
+/*
+ * Copyright 2012-2024 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.exp;
+
+import com.aerospike.dsl.client.command.ParticleType;
+import com.aerospike.dsl.client.query.RegexFlag;
+import com.aerospike.dsl.client.util.Packer;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Expression generator.
+ */
+public abstract class Exp {
+ /**
+ * Expression type.
+ */
+ public enum Type {
+ NIL(0),
+ BOOL(1),
+ INT(2),
+ STRING(3),
+ LIST(4),
+ MAP(5),
+ BLOB(6),
+ FLOAT(7),
+ GEO(8),
+ HLL(9);
+
+ public final int code;
+
+ Type(int code) {
+ this.code = code;
+ }
+ }
+
+ //--------------------------------------------------
+ // Build
+ //--------------------------------------------------
+
+ /**
+ * Create final expression that contains packed byte instructions used in the wire protocol.
+ */
+ public static Expression build(Exp exp) {
+ return new Expression(exp);
+ }
+
+ //--------------------------------------------------
+ // Record Key
+ //--------------------------------------------------
+
+ /**
+ * Create record key expression of specified type.
+ *
+ *
+ */
+ public static Exp key(Type type) {
+ return new CmdInt(KEY, type.code);
+ }
+
+ /**
+ * Create expression that returns if the primary key is stored in the record meta data
+ * as a boolean expression.
+ *
+ *
{@code
+ * // Key exists in record meta data
+ * Exp.keyExists()
+ * }
+ */
+ public static Exp keyExists() {
+ return new Cmd(KEY_EXISTS);
+ }
+
+ //--------------------------------------------------
+ // Record Bin
+ //--------------------------------------------------
+
+ /**
+ * Create bin expression of specified type.
+ *
+ *
{@code
+ * // String bin "a" == "views"
+ * Exp.eq(Exp.bin("a", Type.STRING), Exp.val("views"))
+ * }
+ */
+ public static Exp bin(String name, Type type) {
+ return new Bin(name, type);
+ }
+
+ /**
+ * Create 64 bit integer bin expression.
+ *
+ *
{@code
+ * // Integer bin "a" == 200
+ * Exp.eq(Exp.intBin("a"), Exp.val(200))
+ * }
+ */
+ public static Exp intBin(String name) {
+ return new Bin(name, Type.INT);
+ }
+
+ /**
+ * Create 64 bit float bin expression.
+ *
+ *
{@code
+ * // Float bin "a" >= 1.5
+ * Exp.ge(Exp.floatBin("a"), Exp.val(1.5))
+ * }
+ */
+ public static Exp floatBin(String name) {
+ return new Bin(name, Type.FLOAT);
+ }
+
+ /**
+ * Create string bin expression.
+ *
+ *
{@code
+ * // String bin "a" == "views"
+ * Exp.eq(Exp.stringBin("a"), Exp.val("views"))
+ * }
+ */
+ public static Exp stringBin(String name) {
+ return new Bin(name, Type.STRING);
+ }
+
+ /**
+ * Create boolean bin expression.
+ *
+ *
{@code
+ * // Boolean bin "a" == true
+ * Exp.eq(Exp.boolBin("a"), Exp.val(true))
+ * }
+ */
+ public static Exp boolBin(String name) {
+ return new Bin(name, Type.BOOL);
+ }
+
+ /**
+ * Create byte[] bin expression.
+ *
+ *
{@code
+ * // Blob bin "a" == [1,2,3]
+ * Exp.eq(Exp.blobBin("a"), Exp.val(new byte[] {1, 2, 3}))
+ * }
+ */
+ public static Exp blobBin(String name) {
+ return new Bin(name, Type.BLOB);
+ }
+
+ /**
+ * Create geospatial bin expression.
+ *
+ *
{@code
+ * // Geo bin "a" == region
+ * String region = "{ \"type\": \"AeroCircle\", \"coordinates\": [[-122.0, 37.5], 50000.0] }";
+ * Exp.geoCompare(Exp.geoBin("loc"), Exp.geo(region))
+ * }
+ */
+ public static Exp geoBin(String name) {
+ return new Bin(name, Type.GEO);
+ }
+
+ /**
+ * Create list bin expression.
+ *
+ *
+ */
+ public static Exp mapBin(String name) {
+ return new Bin(name, Type.MAP);
+ }
+
+ /**
+ * Create hll bin expression.
+ *
+ *
{@code
+ * // HLL bin "a" count > 7
+ * Exp.gt(HLLExp.getCount(Exp.hllBin("a")), Exp.val(7))
+ * }
+ */
+ public static Exp hllBin(String name) {
+ return new Bin(name, Type.HLL);
+ }
+
+ /**
+ * Create expression that returns if bin of specified name exists.
+ *
+ *
{@code
+ * // Bin "a" exists in record
+ * Exp.binExists("a")
+ * }
{@code
+ * // Bin "a" particle type is a list
+ * Exp.eq(Exp.binType("a"), Exp.val(ParticleType.LIST))
+ * }
+ */
+ public static Exp binType(String name) {
+ return new CmdStr(BIN_TYPE, name);
+ }
+
+ //--------------------------------------------------
+ // Misc
+ //--------------------------------------------------
+
+ /**
+ * Create expression that returns record set name string. This expression usually
+ * evaluates quickly because record meta data is cached in memory.
+ *
+ *
{@code
+ * // Record set name == "myset"
+ * Exp.eq(Exp.setName(), Exp.val("myset"))
+ * }
+ */
+ public static Exp setName() {
+ return new Cmd(SET_NAME);
+ }
+
+ /**
+ * Create expression that returns the record size. This expression usually evaluates
+ * quickly because record meta data is cached in memory.
+ *
+ * Requires server version 7.0+. This expression replaces {@link #deviceSize()} and
+ * {@link #memorySize()} since those older expressions are equivalent on server version 7.0+.
+ *
+ *
+ */
+ public static Exp recordSize() {
+ return new Cmd(RECORD_SIZE);
+ }
+
+ /**
+ * Create expression that returns record size on disk. If server storage-engine is
+ * memory, then zero is returned. This expression usually evaluates quickly because
+ * record meta data is cached in memory.
+ *
+ * This expression should only be used for server versions less than 7.0. Use
+ * {@link #recordSize()} for server version 7.0+.
+ *
+ *
+ */
+ @Deprecated
+ public static Exp deviceSize() {
+ return new Cmd(DEVICE_SIZE);
+ }
+
+ /**
+ * Create expression that returns record size in memory. If server storage-engine is
+ * not memory nor data-in-memory, then zero is returned. This expression usually evaluates
+ * quickly because record meta data is cached in memory.
+ *
+ * Requires server version between 5.3 inclusive and 7.0 exclusive.
+ * Use {@link #recordSize()} for server version 7.0+.
+ *
+ *
+ */
+ @Deprecated
+ public static Exp memorySize() {
+ return new Cmd(MEMORY_SIZE);
+ }
+
+ /**
+ * Create expression that returns record last update time expressed as 64 bit integer
+ * nanoseconds since 1970-01-01 epoch. This expression usually evaluates quickly because
+ * record meta data is cached in memory.
+ *
+ *
{@code
+ * // Record last update time >= 2020-01-15
+ * Exp.ge(Exp.lastUpdate(), Exp.val(new GregorianCalendar(2020, 0, 15)))
+ * }
+ */
+ public static Exp lastUpdate() {
+ return new Cmd(LAST_UPDATE);
+ }
+
+ /**
+ * Create expression that returns milliseconds since the record was last updated.
+ * This expression usually evaluates quickly because record meta data is cached in memory.
+ *
+ *
{@code
+ * // Record last updated more than 2 hours ago
+ * Exp.gt(Exp.sinceUpdate(), Exp.val(2 * 60 * 60 * 1000))
+ * }
+ */
+ public static Exp sinceUpdate() {
+ return new Cmd(SINCE_UPDATE);
+ }
+
+ /**
+ * Create expression that returns record expiration time expressed as 64 bit integer
+ * nanoseconds since 1970-01-01 epoch. This expression usually evaluates quickly because
+ * record meta data is cached in memory.
+ *
+ *
+ */
+ public static Exp voidTime() {
+ return new Cmd(VOID_TIME);
+ }
+
+ /**
+ * Create expression that returns record expiration time (time to live) in integer seconds.
+ * This expression usually evaluates quickly because record meta data is cached in memory.
+ *
+ *
{@code
+ * // Record expires in less than 1 hour
+ * Exp.lt(Exp.ttl(), Exp.val(60 * 60))
+ * }
+ */
+ public static Exp ttl() {
+ return new Cmd(TTL);
+ }
+
+ /**
+ * Create expression that returns if record has been deleted and is still in tombstone state.
+ * This expression usually evaluates quickly because record meta data is cached in memory.
+ *
+ *
{@code
+ * // Deleted records that are in tombstone state.
+ * Exp.isTombstone()
+ * }
+ */
+ public static Exp isTombstone() {
+ return new Cmd(IS_TOMBSTONE);
+ }
+
+ /**
+ * Create expression that returns record digest modulo as integer. This expression usually
+ * evaluates quickly because record meta data is cached in memory.
+ *
+ *
{@code
+ * // Records that have digest(key) % 3 == 1
+ * Exp.eq(Exp.digestModulo(3), Exp.val(1))
+ * }
+ */
+ public static Exp digestModulo(int mod) {
+ return new CmdInt(DIGEST_MODULO, mod);
+ }
+
+ /**
+ * Create expression that performs a regex match on a string bin or string value expression.
+ *
+ *
{@code
+ * // Select string bin "a" that starts with "prefix" and ends with "suffix".
+ * // Ignore case and do not match newline.
+ * Exp.regexCompare("prefix.*suffix", RegexFlag.ICASE | RegexFlag.NEWLINE, Exp.stringBin("a"))
+ * }
+ *
+ * @param regex regular expression string
+ * @param flags regular expression bit flags. See {@link RegexFlag}
+ * @param bin string bin or string value expression
+ */
+ public static Exp regexCompare(String regex, int flags, Exp bin) {
+ return new Regex(bin, regex, flags);
+ }
+
+ protected String expsAsString(Exp[] exps) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < exps.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(exps[i].toString());
+ }
+ return sb.toString();
+ }
+
+ //--------------------------------------------------
+ // GEO Spatial
+ //--------------------------------------------------
+
+ /**
+ * Create compare geospatial operation.
+ *
+ *
+ */
+ public static Exp or(Exp... exps) {
+ return new CmdExp(OR, exps);
+ }
+
+ /**
+ * Create expression that returns true if only one of the expressions are true.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp le(Exp left, Exp right) {
+ return new CmdExp(LE, left, right);
+ }
+
+ //--------------------------------------------------
+ // Number Operator
+ //--------------------------------------------------
+
+ /**
+ * Create "add" (+) operator that applies to a variable number of expressions.
+ * Return sum of all arguments. All arguments must resolve to the same type (integer or float).
+ * Requires server version 5.6.0+.
+ *
+ *
{@code
+ * // a + b + c == 10
+ * Exp.eq(
+ * Exp.add(Exp.intBin("a"), Exp.intBin("b"), Exp.intBin("c")),
+ * Exp.val(10));
+ * }
+ */
+ public static Exp add(Exp... exps) {
+ return new CmdExp(ADD, exps);
+ }
+
+ /**
+ * Create "subtract" (-) operator that applies to a variable number of expressions.
+ * If only one argument is provided, return the negation of that argument.
+ * Otherwise, return the sum of the 2nd to Nth argument subtracted from the 1st
+ * argument. All arguments must resolve to the same type (integer or float).
+ * Requires server version 5.6.0+.
+ *
+ *
{@code
+ * // a - b - c > 10
+ * Exp.gt(
+ * Exp.sub(Exp.intBin("a"), Exp.intBin("b"), Exp.intBin("c")),
+ * Exp.val(10));
+ * }
+ */
+ public static Exp sub(Exp... exps) {
+ return new CmdExp(SUB, exps);
+ }
+
+ /**
+ * Create "multiply" (*) operator that applies to a variable number of expressions.
+ * Return the product of all arguments. If only one argument is supplied, return
+ * that argument. All arguments must resolve to the same type (integer or float).
+ * Requires server version 5.6.0+.
+ *
+ *
{@code
+ * // a * b * c < 100
+ * Exp.lt(
+ * Exp.mul(Exp.intBin("a"), Exp.intBin("b"), Exp.intBin("c")),
+ * Exp.val(100));
+ * }
+ */
+ public static Exp mul(Exp... exps) {
+ return new CmdExp(MUL, exps);
+ }
+
+ /**
+ * Create "divide" (/) operator that applies to a variable number of expressions.
+ * If there is only one argument, returns the reciprocal for that argument.
+ * Otherwise, return the first argument divided by the product of the rest.
+ * All arguments must resolve to the same type (integer or float).
+ * Requires server version 5.6.0+.
+ *
+ *
{@code
+ * // a / b / c > 1
+ * Exp.gt(
+ * Exp.div(Exp.intBin("a"), Exp.intBin("b"), Exp.intBin("c")),
+ * Exp.val(1));
+ * }
+ */
+ public static Exp div(Exp... exps) {
+ return new CmdExp(DIV, exps);
+ }
+
+ /**
+ * Create "power" operator that raises a "base" to the "exponent" power.
+ * All arguments must resolve to floats.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp pow(Exp base, Exp exponent) {
+ return new CmdExp(POW, base, exponent);
+ }
+
+ /**
+ * Create "log" operator for logarithm of "num" with base "base".
+ * All arguments must resolve to floats.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp log(Exp num, Exp base) {
+ return new CmdExp(LOG, num, base);
+ }
+
+ /**
+ * Create "modulo" (%) operator that determines the remainder of "numerator"
+ * divided by "denominator". All arguments must resolve to integers.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp mod(Exp numerator, Exp denominator) {
+ return new CmdExp(MOD, numerator, denominator);
+ }
+
+ /**
+ * Create operator that returns absolute value of a number.
+ * All arguments must resolve to integer or float.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp abs(Exp value) {
+ return new CmdExp(ABS, value);
+ }
+
+ /**
+ * Create expression that rounds a floating point number down to the closest integer value.
+ * The return type is float. Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp floor(Exp num) {
+ return new CmdExp(FLOOR, num);
+ }
+
+ /**
+ * Create expression that rounds a floating point number up to the closest integer value.
+ * The return type is float. Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp ceil(Exp num) {
+ return new CmdExp(CEIL, num);
+ }
+
+ /**
+ * Create expression that converts a float to an integer.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp toInt(Exp num) {
+ return new CmdExp(TO_INT, num);
+ }
+
+ /**
+ * Create expression that converts an integer to a float.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp toFloat(Exp num) {
+ return new CmdExp(TO_FLOAT, num);
+ }
+
+ /**
+ * Create integer "and" (&) operator that is applied to two or more integers.
+ * All arguments must resolve to integers.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp intAnd(Exp... exps) {
+ return new CmdExp(INT_AND, exps);
+ }
+
+ /**
+ * Create integer "or" (|) operator that is applied to two or more integers.
+ * All arguments must resolve to integers.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp intOr(Exp... exps) {
+ return new CmdExp(INT_OR, exps);
+ }
+
+ /**
+ * Create integer "xor" (^) operator that is applied to two or more integers.
+ * All arguments must resolve to integers.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp arshift(Exp value, Exp shift) {
+ return new CmdExp(INT_ARSHIFT, value, shift);
+ }
+
+ /**
+ * Create expression that returns count of integer bits that are set to 1.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp count(Exp exp) {
+ return new CmdExp(INT_COUNT, exp);
+ }
+
+ /**
+ * Create expression that scans integer bits from left (most significant bit) to
+ * right (least significant bit), looking for a search bit value. When the
+ * search value is found, the index of that bit (where the most significant bit is
+ * index 0) is returned. If "search" is true, the scan will search for the bit
+ * value 1. If "search" is false it will search for bit value 0.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp lscan(Exp value, Exp search) {
+ return new CmdExp(INT_LSCAN, value, search);
+ }
+
+ /**
+ * Create expression that scans integer bits from right (least significant bit) to
+ * left (most significant bit), looking for a search bit value. When the
+ * search value is found, the index of that bit (where the most significant bit is
+ * index 0) is returned. If "search" is true, the scan will search for the bit
+ * value 1. If "search" is false it will search for bit value 0.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp rscan(Exp value, Exp search) {
+ return new CmdExp(INT_RSCAN, value, search);
+ }
+
+ /**
+ * Create expression that returns the minimum value in a variable number of expressions.
+ * All arguments must be the same type (integer or float).
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp min(Exp... exps) {
+ return new CmdExp(MIN, exps);
+ }
+
+ /**
+ * Create expression that returns the maximum value in a variable number of expressions.
+ * All arguments must be the same type (integer or float).
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp max(Exp... exps) {
+ return new CmdExp(MAX, exps);
+ }
+
+ //--------------------------------------------------
+ // Variables
+ //--------------------------------------------------
+
+ /**
+ * Conditionally select an action expression from a variable number of expression pairs
+ * followed by a default action expression. Every action expression must return the same type.
+ * The only exception is {@link #unknown()} which can be mixed with other types.
+ *
+ */
+ public static Exp let(Exp... exps) {
+ return new Let(exps);
+ }
+
+ /**
+ * Assign variable to a {@link Exp#let(Exp...)} expression that can be accessed later.
+ * Requires server version 5.6.0+.
+ *
+ *
+ */
+ public static Exp unknown() {
+ return new Cmd(UNKNOWN);
+ }
+
+ /**
+ * Merge precompiled expression into a new expression tree.
+ * Useful for storing common precompiled expressions and then reusing
+ * these expressions as part of a greater expression.
+ *
+ *
{@code
+ * // Merge precompiled expression into new expression.
+ * Expression e = Exp.build(Exp.eq(Exp.intBin("a"), Exp.val(200)));
+ * Expression merged = Exp.build(Exp.and(Exp.expr(e), Exp.eq(Exp.intBin("b"), Exp.val(100))));
+ * }
+ */
+ public static Exp expr(Expression e) {
+ return new ExpBytes(e);
+ }
+
+ //--------------------------------------------------
+ // Internal
+ //--------------------------------------------------
+
+ private static final int UNKNOWN = 0;
+ private static final int EQ = 1;
+ private static final int NE = 2;
+ private static final int GT = 3;
+ private static final int GE = 4;
+ private static final int LT = 5;
+ private static final int LE = 6;
+ private static final int REGEX = 7;
+ private static final int GEO = 8;
+ private static final int AND = 16;
+ private static final int OR = 17;
+ private static final int NOT = 18;
+ private static final int EXCLUSIVE = 19;
+ private static final int ADD = 20;
+ private static final int SUB = 21;
+ private static final int MUL = 22;
+ private static final int DIV = 23;
+ private static final int POW = 24;
+ private static final int LOG = 25;
+ private static final int MOD = 26;
+ private static final int ABS = 27;
+ private static final int FLOOR = 28;
+ private static final int CEIL = 29;
+ private static final int TO_INT = 30;
+ private static final int TO_FLOAT = 31;
+ private static final int INT_AND = 32;
+ private static final int INT_OR = 33;
+ private static final int INT_XOR = 34;
+ private static final int INT_NOT = 35;
+ private static final int INT_LSHIFT = 36;
+ private static final int INT_RSHIFT = 37;
+ private static final int INT_ARSHIFT = 38;
+ private static final int INT_COUNT = 39;
+ private static final int INT_LSCAN = 40;
+ private static final int INT_RSCAN = 41;
+ private static final int MIN = 50;
+ private static final int MAX = 51;
+ private static final int DIGEST_MODULO = 64;
+ private static final int DEVICE_SIZE = 65;
+ private static final int LAST_UPDATE = 66;
+ private static final int SINCE_UPDATE = 67;
+ private static final int VOID_TIME = 68;
+ private static final int TTL = 69;
+ private static final int SET_NAME = 70;
+ private static final int KEY_EXISTS = 71;
+ private static final int IS_TOMBSTONE = 72;
+ private static final int MEMORY_SIZE = 73;
+ private static final int RECORD_SIZE = 74;
+ private static final int KEY = 80;
+ private static final int BIN = 81;
+ private static final int BIN_TYPE = 82;
+ private static final int COND = 123;
+ private static final int VAR = 124;
+ private static final int LET = 125;
+ private static final int QUOTED = 126;
+ private static final int CALL = 127;
+ public static final int MODIFY = 0x40;
+ private static final long NANOS_PER_MILLIS = 1000000L;
+
+ public abstract void pack(Packer packer);
+
+ // Package visible
+ static String cmdAsString(int cmd) {
+ return switch (cmd) {
+ case UNKNOWN -> "UNKNOWN";
+ case EQ -> "EQ";
+ case NE -> "NE";
+ case GT -> "GT";
+ case GE -> "GE";
+ case LT -> "LT";
+ case LE -> "LE";
+ case REGEX -> "REGEX";
+ case GEO -> "GEO";
+ case AND -> "AND";
+ case OR -> "OR";
+ case NOT -> "NOT";
+ case EXCLUSIVE -> "EXCLUSIVE";
+ case ADD -> "ADD";
+ case SUB -> "SUB";
+ case MUL -> "MUL";
+ case DIV -> "DIV";
+ case POW -> "POW";
+ case LOG -> "LOG";
+ case MOD -> "MOD";
+ case ABS -> "ABS";
+ case FLOOR -> "FLOOR";
+ case CEIL -> "CEIL";
+ case TO_INT -> "TO_INT";
+ case TO_FLOAT -> "TO_FLOAT";
+ case INT_AND -> "INT_AND";
+ case INT_OR -> "INT_OR";
+ case INT_XOR -> "INT_XOR";
+ case INT_NOT -> "INT_NOT";
+ case INT_LSHIFT -> "INT_LSHIFT";
+ case INT_RSHIFT -> "INT_RSHIFT";
+ case INT_ARSHIFT -> "INT_ARSHIFT";
+ case INT_COUNT -> "INT_COUNT";
+ case INT_LSCAN -> "INT_LSCAN";
+ case INT_RSCAN -> "INT_RSCAN";
+ case MIN -> "MIN";
+ case MAX -> "MAX";
+ case DIGEST_MODULO -> "DIGEST_MODULO";
+ case DEVICE_SIZE -> "DEVICE_SIZE";
+ case LAST_UPDATE -> "LAST_UPDATE";
+ case SINCE_UPDATE -> "SINCE_UPDATE";
+ case VOID_TIME -> "VOID_TIME";
+ case TTL -> "TTL";
+ case SET_NAME -> "SET_NAME";
+ case KEY_EXISTS -> "KEY_EXISTS";
+ case IS_TOMBSTONE -> "IS_TOMBSTONE";
+ case MEMORY_SIZE -> "MEMORY_SIZE";
+ case RECORD_SIZE -> "RECORD_SIZE";
+ case KEY -> "KEY";
+ case BIN -> "BIN";
+ case BIN_TYPE -> "BIN_TYPE";
+ case COND -> "COND";
+ case VAR -> "VAR";
+ case LET -> "LET";
+ case QUOTED -> "QUOTED";
+ case CALL -> "CALL";
+ default -> null;
+ };
+ }
+
+ /**
+ * For internal use only.
+ */
+ static class Module extends Exp {
+ private final Exp bin;
+ private final byte[] bytes;
+ private final int retType;
+ private final int module;
+
+ public Module(Exp bin, byte[] bytes, int retType, int module) {
+ this.bin = bin;
+ this.bytes = bytes;
+ this.retType = retType;
+ this.module = module;
+ }
+
+ public String toString() {
+ return "Module [bin=" + bin + ", bytes=" + Arrays.toString(bytes) + ", retType=" + retType +
+ ", module=" + module + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(5);
+ packer.packInt(Exp.CALL);
+ packer.packInt(retType);
+ packer.packInt(module);
+ packer.packByteArray(bytes, 0, bytes.length);
+ bin.pack(packer);
+ }
+ }
+
+ private static final class Bin extends Exp {
+ private final String name;
+ private final Type type;
+
+ public Bin(String name, Type type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ public String toString() {
+ return "Bin(" + type.toString().charAt(0) + "\"" + name + "\")";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(3);
+ packer.packInt(BIN);
+ packer.packInt(type.code);
+ packer.packString(name);
+ }
+ }
+
+ private static final class Regex extends Exp {
+ private final Exp bin;
+ private final String regex;
+ private final int flags;
+
+ private Regex(Exp bin, String regex, int flags) {
+ this.bin = bin;
+ this.regex = regex;
+ this.flags = flags;
+ }
+
+ public String toString() {
+ return "Regex [bin=" + bin + ", regex=" + regex + ", flags=" + flags + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(4);
+ packer.packInt(REGEX);
+ packer.packInt(flags);
+ packer.packString(regex);
+ bin.pack(packer);
+ }
+ }
+
+ private static final class Let extends Exp {
+ private final Exp[] exps;
+
+ private Let(Exp... exps) {
+ this.exps = exps;
+ }
+
+ @Override
+ public String toString() {
+ return "Let [exps=" + expsAsString(exps) + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ // Let wire format: LET , , , , ...,
+ int count = (exps.length - 1) * 2 + 2;
+ packer.packArrayBegin(count);
+ packer.packInt(LET);
+
+ for (Exp exp : exps) {
+ exp.pack(packer);
+ }
+ }
+ }
+
+ private static final class Def extends Exp {
+ private final String name;
+ private final Exp exp;
+
+ private Def(String name, Exp exp) {
+ this.name = name;
+ this.exp = exp;
+ }
+
+ @Override
+ public String toString() {
+ return "Def [name=" + name + ", exp=" + exp + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packString(name);
+ exp.pack(packer);
+ }
+ }
+
+ private static final class CmdExp extends Exp {
+ private final Exp[] exps;
+ private final int cmd;
+
+ private CmdExp(int cmd, Exp... exps) {
+ this.exps = exps;
+ this.cmd = cmd;
+ }
+
+ @Override
+ public String toString() {
+ String cmdStr = cmdAsString(cmd);
+ if (cmdStr == null) {
+ return "CmdExp [exps=" + expsAsString(exps) + ", cmd=" + cmd + "]";
+ }
+ switch (cmd) {
+ case GT:
+ return multiArgOp(">");
+ case GE:
+ return multiArgOp(">=");
+ case LE:
+ return multiArgOp("<=");
+ case LT:
+ return multiArgOp("<");
+ case EQ:
+ return multiArgOp("==");
+ case NE:
+ return multiArgOp("!=");
+ case ADD:
+ return multiArgOp("+");
+ case SUB:
+ return multiArgOp("-");
+ case MUL:
+ return multiArgOp("*");
+ case DIV:
+ return multiArgOp("/");
+ case AND:
+ return multiArgOp(" AND ");
+ case OR:
+ return multiArgOp(" OR ");
+ case INT_AND:
+ return multiArgOp("&");
+ case INT_ARSHIFT:
+ return multiArgOp(">>");
+ case INT_LSHIFT:
+ return multiArgOp("<<");
+ case INT_OR:
+ return multiArgOp("|");
+ case INT_RSHIFT:
+ return multiArgOp(">>>");
+ case COND:
+ // Special case
+ return formatCond();
+ default:
+ return cmdStr + "(" + expsAsString(exps) + ")";
+
+ }
+ }
+
+ private String multiArgOp(String separator) {
+ StringBuilder sb = new StringBuilder().append('(');
+ for (int i = 0; i < exps.length; i++) {
+ if (i > 0) {
+ sb.append(' ').append(separator).append(' ');
+ }
+ sb.append(exps[i].toString());
+ }
+ return sb.append(')').toString();
+ }
+
+ private String formatCond() {
+ StringBuilder sb = new StringBuilder();
+ if (exps.length == 0) {
+ return "COND()";
+ } else if (exps.length == 1) {
+ return "COND(" + exps[0].toString() + ")";
+ }
+ for (int i = 0; i < exps.length; i += 2) {
+ boolean needsThen = true;
+ int index = i + 1;
+ if (i == 0) {
+ sb.append("IF (");
+ } else if (i < (exps.length - 1)) {
+ sb.append(" ELSE IF (");
+ } else {
+ needsThen = false;
+ }
+ if (needsThen) {
+ sb.append(exps[i].toString()).append(") THEN ");
+ } else {
+ sb.append(" ELSE ");
+ index = i;
+ }
+ sb.append(exps[index].toString());
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(exps.length + 1);
+ packer.packInt(cmd);
+
+ for (Exp exp : exps) {
+ exp.pack(packer);
+ }
+ }
+ }
+
+ private static final class CmdInt extends Exp {
+ private final int cmd;
+ private final int val;
+
+ private CmdInt(int cmd, int val) {
+ this.cmd = cmd;
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ String cmdStr = cmdAsString(cmd);
+ if (cmdStr == null) {
+ return "CmdInt [cmd=" + cmd + ", val=" + val + "]";
+ }
+ return cmdStr + "(" + val + ")";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(2);
+ packer.packInt(cmd);
+ packer.packInt(val);
+ }
+ }
+
+ private static final class CmdStr extends Exp {
+ private final String str;
+ private final int cmd;
+
+ private CmdStr(int cmd, String str) {
+ this.str = str;
+ this.cmd = cmd;
+ }
+
+ @Override
+ public String toString() {
+ String cmdStr = cmdAsString(cmd);
+ if (cmdStr == null) {
+ return "CmdStr [str=" + str + ", cmd=" + cmd + "]";
+ }
+ return cmdStr + "(\"" + str + "\")";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(2);
+ packer.packInt(cmd);
+ packer.packString(str);
+ }
+ }
+
+ private static final class Cmd extends Exp {
+ private final int cmd;
+
+ private Cmd(int cmd) {
+ this.cmd = cmd;
+ }
+
+ @Override
+ public String toString() {
+ String cmdStr = cmdAsString(cmd);
+ if (cmdStr == null) {
+ return "Cmd [cmd=" + cmd + "]";
+
+ } else {
+ return cmdStr;
+ }
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packArrayBegin(1);
+ packer.packInt(cmd);
+ }
+ }
+
+ private static final class Bool extends Exp {
+ private final boolean val;
+
+ private Bool(boolean val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return "Bool [val=" + val + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packBoolean(val);
+ }
+ }
+
+ private static final class Int extends Exp {
+ private final long val;
+
+ private Int(long val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return val + "L";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packLong(val);
+ }
+ }
+
+ private static final class Float extends Exp {
+ private final double val;
+
+ private Float(double val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return val + "f";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packDouble(val);
+
+ }
+ }
+
+ private static final class Str extends Exp {
+ private final String val;
+
+ private Str(String val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return "\"" + val + "\"";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packParticleString(val);
+ }
+ }
+
+ private static final class Geo extends Exp {
+ private final String val;
+
+ private Geo(String val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return "Geo [val=" + val + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packGeoJSON(val);
+ }
+ }
+
+ private static final class Blob extends Exp {
+ private final byte[] val;
+
+ private Blob(byte[] val) {
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return "Blob [val=" + Arrays.toString(val) + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packParticleBytes(val);
+
+ }
+ }
+
+ private static final class ListVal extends Exp {
+ private final List> list;
+
+ private ListVal(List> list) {
+ this.list = list;
+ }
+
+ @Override
+ public String toString() {
+ return "ListVal [list=" + list + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ // List values need an extra array and QUOTED in order to distinguish
+ // between a multiple argument array call and a local list.
+ packer.packArrayBegin(2);
+ packer.packInt(QUOTED);
+ packer.packList(list);
+ }
+ }
+
+ private static final class MapVal extends Exp {
+ private final Map, ?> map;
+
+ private MapVal(Map, ?> map) {
+ this.map = map;
+ }
+
+ @Override
+ public String toString() {
+ return "MapVal [map=" + map + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packMap(map);
+ }
+ }
+
+ private static final class Nil extends Exp {
+ @Override
+ public String toString() {
+ return "Nil";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packNil();
+ }
+ }
+
+ private static final class Infinity extends Exp {
+ @Override
+ public String toString() {
+ return "Infinity []";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packInfinity();
+ }
+ }
+
+ private static final class Wildcard extends Exp {
+ @Override
+ public String toString() {
+ return "Wildcard []";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packWildcard();
+ }
+ }
+
+ private static final class ExpBytes extends Exp {
+ private final byte[] bytes;
+
+ private ExpBytes(Expression e) {
+ this.bytes = e.getBytes();
+ }
+
+ @Override
+ public String toString() {
+ return "ExpBytes [bytes=" + Arrays.toString(bytes) + "]";
+ }
+
+ @Override
+ public void pack(Packer packer) {
+ packer.packByteArray(bytes, 0, bytes.length);
+ }
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/exp/ExpReadFlags.java b/src/main/java/com/aerospike/dsl/client/exp/ExpReadFlags.java
new file mode 100644
index 0000000..8a7f6ae
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/exp/ExpReadFlags.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012-2021 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.exp;
+
+/**
+ * Expression read flags.
+ */
+public final class ExpReadFlags {
+ /**
+ * Default.
+ */
+ public static final int DEFAULT = 0;
+
+ /**
+ * Ignore failures caused by the expression resolving to unknown or a non-bin type.
+ */
+ public static final int EVAL_NO_FAIL = 16;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/exp/ExpWriteFlags.java b/src/main/java/com/aerospike/dsl/client/exp/ExpWriteFlags.java
new file mode 100644
index 0000000..20b4133
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/exp/ExpWriteFlags.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.exp;
+
+import com.aerospike.dsl.client.ResultCode;
+
+/**
+ * Expression write bit flags. Use BITWISE OR to combine flags. Example:
+ *
+ *
+ */
+public final class ExpWriteFlags {
+ /**
+ * Default. Allow create or update.
+ */
+ public static final int DEFAULT = 0;
+
+ /**
+ * If bin does not exist, a new bin will be created.
+ * If bin exists, the operation will be denied.
+ * If bin exists, fail with {@link ResultCode#BIN_EXISTS_ERROR}
+ * when {@link #POLICY_NO_FAIL} is not set.
+ */
+ public static final int CREATE_ONLY = 1;
+
+ /**
+ * If bin exists, the bin will be overwritten.
+ * If bin does not exist, the operation will be denied.
+ * If bin does not exist, fail with {@link ResultCode#BIN_NOT_FOUND}
+ * when {@link #POLICY_NO_FAIL} is not set.
+ */
+ public static final int UPDATE_ONLY = 2;
+
+ /**
+ * If expression results in nil value, then delete the bin. Otherwise, fail with
+ * {@link ResultCode#OP_NOT_APPLICABLE} when {@link #POLICY_NO_FAIL} is not set.
+ */
+ public static final int ALLOW_DELETE = 4;
+
+ /**
+ * Do not raise error if operation is denied.
+ */
+ public static final int POLICY_NO_FAIL = 8;
+
+ /**
+ * Ignore failures caused by the expression resolving to unknown or a non-bin type.
+ */
+ public static final int EVAL_NO_FAIL = 16;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/exp/Expression.java b/src/main/java/com/aerospike/dsl/client/exp/Expression.java
new file mode 100644
index 0000000..10171ce
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/exp/Expression.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012-2025 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.exp;
+
+import com.aerospike.dsl.client.util.Crypto;
+import com.aerospike.dsl.client.util.Packer;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * Packed expression byte instructions.
+ */
+public final class Expression implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final byte[] bytes;
+
+ /**
+ * Expression constructor used by {@link Exp#build(Exp)}
+ */
+ Expression(Exp exp) {
+ Packer packer = new Packer();
+ exp.pack(packer);
+ bytes = packer.toByteArray();
+ /*
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i] & 0xff;
+ System.out.println("" + b);
+ }
+ */
+ }
+
+ /**
+ * Expression constructor for packed expression instructions.
+ */
+ Expression(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * Return a new expression from packed expression instructions in bytes.
+ */
+ public static Expression fromBytes(byte[] bytes) {
+ return new Expression(bytes);
+ }
+
+ /**
+ * Return a new expression from packed expression instructions in base64 encoded bytes.
+ */
+ public static Expression fromBase64(byte[] bytes) {
+ return Expression.fromBytes(Crypto.decodeBase64(bytes, 0, bytes.length));
+ }
+
+ /**
+ * Return a new expression from packed expression instructions in base64 encoded string.
+ */
+ public static Expression fromBase64(String s) {
+ return Expression.fromBase64(s.getBytes());
+ }
+
+ /**
+ * Return packed byte instructions.
+ */
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ /**
+ * Return byte instructions in base64 encoding.
+ */
+ public String getBase64() {
+ return Crypto.encodeBase64(bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(bytes);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Expression other = (Expression) obj;
+ return Arrays.equals(bytes, other.bytes);
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/exp/ListExp.java b/src/main/java/com/aerospike/dsl/client/exp/ListExp.java
new file mode 100644
index 0000000..2173b23
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/exp/ListExp.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.exp;
+
+import com.aerospike.dsl.client.AerospikeException;
+import com.aerospike.dsl.client.cdt.CTX;
+import com.aerospike.dsl.client.cdt.ListReturnType;
+import com.aerospike.dsl.client.util.Pack;
+import com.aerospike.dsl.client.util.Packer;
+
+/**
+ * List expression generator. See {@link Exp}.
+ *
+ * The bin expression argument in these methods can be a reference to a bin or the
+ * result of another expression. Expressions that modify bin values are only used
+ * for temporary expression evaluation and are not permanently applied to the bin.
+ *
+ * List modify expressions return the bin's value. This value will be a list except
+ * when the list is nested within a map. In that case, a map is returned for the
+ * list modify expression.
+ *
+ * List expressions support negative indexing. If the index is negative, the
+ * resolved index starts backwards from end of list. If an index is out of bounds,
+ * a parameter error will be returned. If a range is partially out of bounds, the
+ * valid part of the range will be returned. Index/Range examples:
+ *
+ *
Index 0: First item in list.
+ *
Index 4: Fifth item in list.
+ *
Index -1: Last item in list.
+ *
Index -3: Third to last item in list.
+ *
Index 1 Count 2: Second and third items in list.
+ *
Index -3 Count 3: Last three items in list.
+ *
Index -5 Count 4: Range between fifth to last item to second to last item inclusive.
+ *
+ *
+ * Nested expressions are supported by optional CTX context arguments. Example:
+ *
+ */
+public final class ListExp {
+ private static final int MODULE = 0;
+ private static final int APPEND = 1;
+ private static final int APPEND_ITEMS = 2;
+ private static final int INSERT = 3;
+ private static final int INSERT_ITEMS = 4;
+ private static final int SET = 9;
+ private static final int CLEAR = 11;
+ private static final int INCREMENT = 12;
+ private static final int SORT = 13;
+ private static final int SIZE = 16;
+ private static final int GET_BY_INDEX = 19;
+ private static final int GET_BY_RANK = 21;
+ private static final int GET_BY_VALUE = 22; // GET_ALL_BY_VALUE on server.
+ private static final int GET_BY_VALUE_LIST = 23;
+ private static final int GET_BY_INDEX_RANGE = 24;
+ private static final int GET_BY_VALUE_INTERVAL = 25;
+ private static final int GET_BY_RANK_RANGE = 26;
+ private static final int GET_BY_VALUE_REL_RANK_RANGE = 27;
+ private static final int REMOVE_BY_INDEX = 32;
+ private static final int REMOVE_BY_RANK = 34;
+ private static final int REMOVE_BY_VALUE = 35;
+ private static final int REMOVE_BY_VALUE_LIST = 36;
+ private static final int REMOVE_BY_INDEX_RANGE = 37;
+ private static final int REMOVE_BY_VALUE_INTERVAL = 38;
+ private static final int REMOVE_BY_RANK_RANGE = 39;
+ private static final int REMOVE_BY_VALUE_REL_RANK_RANGE = 40;
+
+ /**
+ * Create expression that returns list size.
+ *
+ *
{@code
+ * // List bin "a" size > 7
+ * Exp.gt(ListExp.size(Exp.listBin("a")), Exp.val(7))
+ * }
+ */
+ public static Exp size(Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(SIZE, ctx);
+ return addRead(bin, bytes, Exp.Type.INT);
+ }
+
+ /**
+ * Create expression that selects list items identified by value and returns selected
+ * data specified by returnType.
+ *
+ *
{@code
+ * // List bin "a" contains at least one item == "abc"
+ * ListExp.getByValue(ListReturnType.EXISTS, Exp.val("abc"), Exp.listBin("a"))
+ * }
+ *
+ * @param returnType metadata attributes to return. See {@link ListReturnType}
+ * @param value search expression
+ * @param bin list bin or list value expression
+ * @param ctx optional context path for nested CDT
+ */
+ public static Exp getByValue(int returnType, Exp value, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE, returnType, value, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects list items identified by value range and returns selected data
+ * specified by returnType.
+ *
+ *
{@code
+ * // List bin "a" items >= 10 && items < 20
+ * ListExp.getByValueRange(ListReturnType.VALUE, Exp.val(10), Exp.val(20), Exp.listBin("a"))
+ * }
+ *
+ * @param returnType metadata attributes to return. See {@link ListReturnType}
+ * @param valueBegin begin expression inclusive. If null, range is less than valueEnd.
+ * @param valueEnd end expression exclusive. If null, range is greater than equal to valueBegin.
+ * @param bin bin or list value expression
+ * @param ctx optional context path for nested CDT
+ */
+ public static Exp getByValueRange(int returnType, Exp valueBegin, Exp valueEnd, Exp bin, CTX... ctx) {
+ byte[] bytes = ListExp.packRangeOperation(GET_BY_VALUE_INTERVAL, returnType, valueBegin, valueEnd, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects list items identified by values and returns selected data
+ * specified by returnType.
+ */
+ public static Exp getByValueList(int returnType, Exp values, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE_LIST, returnType, values, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects list items nearest to value and greater by relative rank
+ * and returns selected data specified by returnType (See {@link ListReturnType}).
+ *
+ * Examples for ordered list [0,4,5,9,11,15]:
+ *
+ *
(value,rank) = [selected items]
+ *
(5,0) = [5,9,11,15]
+ *
(5,1) = [9,11,15]
+ *
(5,-1) = [4,5,9,11,15]
+ *
(3,0) = [4,5,9,11,15]
+ *
(3,3) = [11,15]
+ *
(3,-3) = [0,4,5,9,11,15]
+ *
+ */
+ public static Exp getByValueRelativeRankRange(int returnType, Exp value, Exp rank, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE_REL_RANK_RANGE, returnType, value, rank, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects list items nearest to value and greater by relative rank with a count limit
+ * and returns selected data specified by returnType (See {@link ListReturnType}).
+ *
+ * Examples for ordered list [0,4,5,9,11,15]:
+ *
+ *
(value,rank,count) = [selected items]
+ *
(5,0,2) = [5,9]
+ *
(5,1,1) = [9]
+ *
(5,-1,2) = [4,5]
+ *
(3,0,1) = [4]
+ *
(3,3,7) = [11,15]
+ *
(3,-3,2) = []
+ *
+ */
+ public static Exp getByValueRelativeRankRange(int returnType, Exp value, Exp rank, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE_REL_RANK_RANGE, returnType, value, rank, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects list item identified by index and returns
+ * selected data specified by returnType.
+ *
+ *
+ *
+ * @param returnType metadata attributes to return. See {@link ListReturnType}
+ * @param valueType expected type of return value
+ * @param index list index expression
+ * @param bin list bin or list value expression
+ * @param ctx optional context path for nested CDT
+ */
+ public static Exp getByIndex(int returnType, Exp.Type valueType, Exp index, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_INDEX, returnType, index, ctx);
+ return addRead(bin, bytes, valueType);
+ }
+
+ /**
+ * Create expression that selects list items starting at specified index to the end of list
+ * and returns selected data specified by returnType (See {@link ListReturnType}).
+ */
+ public static Exp getByIndexRange(int returnType, Exp index, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_INDEX_RANGE, returnType, index, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects "count" list items starting at specified index
+ * and returns selected data specified by returnType (See {@link ListReturnType}).
+ */
+ public static Exp getByIndexRange(int returnType, Exp index, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_INDEX_RANGE, returnType, index, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects list item identified by rank and returns selected
+ * data specified by returnType.
+ *
+ *
{@code
+ * // Player with lowest score.
+ * ListExp.getByRank(ListReturnType.VALUE, Type.STRING, Exp.val(0), Exp.listBin("a"))
+ * }
+ *
+ * @param returnType metadata attributes to return. See {@link ListReturnType}
+ * @param valueType expected type of return value
+ * @param rank rank expression
+ * @param bin list bin or list value expression
+ * @param ctx optional context path for nested CDT
+ */
+ public static Exp getByRank(int returnType, Exp.Type valueType, Exp rank, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_RANK, returnType, rank, ctx);
+ return addRead(bin, bytes, valueType);
+ }
+
+ /**
+ * Create expression that selects list items starting at specified rank to the last ranked item
+ * and returns selected data specified by returnType (See {@link ListReturnType}).
+ */
+ public static Exp getByRankRange(int returnType, Exp rank, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_RANK_RANGE, returnType, rank, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects "count" list items starting at specified rank and returns
+ * selected data specified by returnType (See {@link ListReturnType}).
+ */
+ public static Exp getByRankRange(int returnType, Exp rank, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_RANK_RANGE, returnType, rank, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ private static Exp addWrite(Exp bin, byte[] bytes, CTX[] ctx) {
+ int retType;
+
+ if (ctx == null || ctx.length == 0) {
+ retType = Exp.Type.LIST.code;
+ }
+ else {
+ retType = ((ctx[0].id & 0x10) == 0)? Exp.Type.MAP.code : Exp.Type.LIST.code;
+ }
+ return new Exp.Module(bin, bytes, retType, MODULE | Exp.MODIFY);
+ }
+
+ private static Exp addRead(Exp bin, byte[] bytes, Exp.Type retType) {
+ return new Exp.Module(bin, bytes, retType.code, MODULE);
+ }
+
+ private static Exp.Type getValueType(int returnType) {
+ int t = returnType & ~ListReturnType.INVERTED;
+
+ switch (t) {
+ case ListReturnType.INDEX:
+ case ListReturnType.REVERSE_INDEX:
+ case ListReturnType.RANK:
+ case ListReturnType.REVERSE_RANK:
+ // This method only called from expressions that can return multiple integers (ie list).
+ return Exp.Type.LIST;
+
+ case ListReturnType.COUNT:
+ return Exp.Type.INT;
+
+ case ListReturnType.VALUE:
+ // This method only called from expressions that can return multiple objects (ie list).
+ return Exp.Type.LIST;
+
+ case ListReturnType.EXISTS:
+ return Exp.Type.BOOL;
+
+ default:
+ case ListReturnType.NONE:
+ throw new AerospikeException("Invalid ListReturnType: " + returnType);
+ }
+ }
+
+ protected static byte[] packRangeOperation(int command, int returnType, Exp begin, Exp end, CTX[] ctx) {
+ Packer packer = new Packer();
+ Pack.init(packer, ctx);
+ packer.packArrayBegin((end != null)? 4 : 3);
+ packer.packInt(command);
+ packer.packInt(returnType);
+
+ if (begin != null) {
+ begin.pack(packer);
+ }
+ else {
+ packer.packNil();
+ }
+
+ if (end != null) {
+ end.pack(packer);
+ }
+ return packer.toByteArray();
+ }
+
+}
diff --git a/src/main/java/com/aerospike/dsl/client/exp/MapExp.java b/src/main/java/com/aerospike/dsl/client/exp/MapExp.java
new file mode 100644
index 0000000..8fe82c4
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/exp/MapExp.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.exp;
+
+import com.aerospike.dsl.client.AerospikeException;
+import com.aerospike.dsl.client.cdt.CTX;
+import com.aerospike.dsl.client.cdt.MapReturnType;
+import com.aerospike.dsl.client.util.Pack;
+
+/**
+ * Map expression generator. See {@link Exp}.
+ *
+ * The bin expression argument in these methods can be a reference to a bin or the
+ * result of another expression. Expressions that modify bin values are only used
+ * for temporary expression evaluation and are not permanently applied to the bin.
+ *
+ * Map modify expressions return the bin's value. This value will be a map except
+ * when the map is nested within a list. In that case, a list is returned for the
+ * map modify expression.
+ *
+ * Valid map key types are:
+ *
+ *
String
+ *
Integer
+ *
byte[]
+ *
+ *
+ * The server will validate map key types in an upcoming release.
+ *
+ * All maps maintain an index and a rank. The index is the item offset from the start of the map,
+ * for both unordered and ordered maps. The rank is the sorted index of the value component.
+ * Map supports negative indexing for index and rank.
+ *
+ * Index examples:
+ *
+ *
Index 0: First item in map.
+ *
Index 4: Fifth item in map.
+ *
Index -1: Last item in map.
+ *
Index -3: Third to last item in map.
+ *
Index 1 Count 2: Second and third items in map.
+ *
Index -3 Count 3: Last three items in map.
+ *
Index -5 Count 4: Range between fifth to last item to second to last item inclusive.
+ *
+ *
+ * Rank examples:
+ *
+ *
Rank 0: Item with lowest value rank in map.
+ *
Rank 4: Fifth lowest ranked item in map.
+ *
Rank -1: Item with highest ranked value in map.
+ *
Rank -3: Item with third highest ranked value in map.
+ *
Rank 1 Count 2: Second and third lowest ranked items in map.
+ *
Rank -3 Count 3: Top three ranked items in map.
+ *
+ *
+ * Nested expressions are supported by optional CTX context arguments. Example:
+ *
+ *
bin = {key1={key11=9,key12=4}, key2={key21=3,key22=5}}
+ *
Set map value to 11 for map key "key21" inside of map key "key2".
+ */
+public final class MapExp {
+ private static final int MODULE = 0;
+ private static final int PUT = 67;
+ private static final int PUT_ITEMS = 68;
+ private static final int REPLACE = 69;
+ private static final int REPLACE_ITEMS = 70;
+ private static final int INCREMENT = 73;
+ private static final int CLEAR = 75;
+ private static final int REMOVE_BY_KEY = 76;
+ private static final int REMOVE_BY_INDEX = 77;
+ private static final int REMOVE_BY_RANK = 79;
+ private static final int REMOVE_BY_KEY_LIST = 81;
+ private static final int REMOVE_BY_VALUE = 82;
+ private static final int REMOVE_BY_VALUE_LIST = 83;
+ private static final int REMOVE_BY_KEY_INTERVAL = 84;
+ private static final int REMOVE_BY_INDEX_RANGE = 85;
+ private static final int REMOVE_BY_VALUE_INTERVAL = 86;
+ private static final int REMOVE_BY_RANK_RANGE = 87;
+ private static final int REMOVE_BY_KEY_REL_INDEX_RANGE = 88;
+ private static final int REMOVE_BY_VALUE_REL_RANK_RANGE = 89;
+ private static final int SIZE = 96;
+ private static final int GET_BY_KEY = 97;
+ private static final int GET_BY_INDEX = 98;
+ private static final int GET_BY_RANK = 100;
+ private static final int GET_BY_VALUE = 102; // GET_ALL_BY_VALUE on server.
+ private static final int GET_BY_KEY_INTERVAL = 103;
+ private static final int GET_BY_INDEX_RANGE = 104;
+ private static final int GET_BY_VALUE_INTERVAL = 105;
+ private static final int GET_BY_RANK_RANGE = 106;
+ private static final int GET_BY_KEY_LIST = 107;
+ private static final int GET_BY_VALUE_LIST = 108;
+ private static final int GET_BY_KEY_REL_INDEX_RANGE = 109;
+ private static final int GET_BY_VALUE_REL_RANK_RANGE = 110;
+
+ /**
+ * Create expression that returns list size.
+ *
+ *
{@code
+ * // Map bin "a" size > 7
+ * Exp.gt(MapExp.size(Exp.mapBin("a")), Exp.val(7))
+ * }
+ */
+ public static Exp size(Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(SIZE, ctx);
+ return addRead(bin, bytes, Exp.Type.INT);
+ }
+
+ /**
+ * Create expression that selects map item identified by key and returns selected data
+ * specified by returnType.
+ *
+ *
{@code
+ * // Map bin "a" contains key "B"
+ * MapExp.getByKey(MapReturnType.EXISTS, Exp.Type.BOOL, Exp.val("B"), Exp.mapBin("a"))
+ * }
+ *
+ * @param returnType metadata attributes to return. See {@link MapReturnType}
+ * @param valueType expected type of return value
+ * @param key map key expression
+ * @param bin bin or map value expression
+ * @param ctx optional context path for nested CDT
+ */
+ public static Exp getByKey(int returnType, Exp.Type valueType, Exp key, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_KEY, returnType, key, ctx);
+ return addRead(bin, bytes, valueType);
+ }
+
+ /**
+ * Create expression that selects map items identified by key range (keyBegin inclusive, keyEnd exclusive).
+ * If keyBegin is null, the range is less than keyEnd.
+ * If keyEnd is null, the range is greater than equal to keyBegin.
+ *
+ * Expression returns selected data specified by returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByKeyRange(int returnType, Exp keyBegin, Exp keyEnd, Exp bin, CTX... ctx) {
+ byte[] bytes = ListExp.packRangeOperation(GET_BY_KEY_INTERVAL, returnType, keyBegin, keyEnd, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items identified by keys and returns selected data specified by
+ * returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByKeyList(int returnType, Exp keys, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_KEY_LIST, returnType, keys, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items nearest to key and greater by index.
+ * Expression returns selected data specified by returnType (See {@link MapReturnType}).
+ *
+ * Examples for ordered map [{0=17},{4=2},{5=15},{9=10}]:
+ *
+ *
(value,index) = [selected items]
+ *
(5,0) = [{5=15},{9=10}]
+ *
(5,1) = [{9=10}]
+ *
(5,-1) = [{4=2},{5=15},{9=10}]
+ *
(3,2) = [{9=10}]
+ *
(3,-2) = [{0=17},{4=2},{5=15},{9=10}]
+ *
+ */
+ public static Exp getByKeyRelativeIndexRange(int returnType, Exp key, Exp index, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_KEY_REL_INDEX_RANGE, returnType, key, index, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items nearest to key and greater by index with a count limit.
+ * Expression returns selected data specified by returnType (See {@link MapReturnType}).
+ *
+ * Examples for ordered map [{0=17},{4=2},{5=15},{9=10}]:
+ *
+ *
(value,index,count) = [selected items]
+ *
(5,0,1) = [{5=15}]
+ *
(5,1,2) = [{9=10}]
+ *
(5,-1,1) = [{4=2}]
+ *
(3,2,1) = [{9=10}]
+ *
(3,-2,2) = [{0=17}]
+ *
+ */
+ public static Exp getByKeyRelativeIndexRange(int returnType, Exp key, Exp index, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_KEY_REL_INDEX_RANGE, returnType, key, index, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items identified by value and returns selected data
+ * specified by returnType.
+ *
+ *
{@code
+ * // Map bin "a" contains value "BBB"
+ * MapExp.getByValue(MapReturnType.EXISTS, Exp.val("BBB"), Exp.mapBin("a"))
+ * }
+ *
+ * @param returnType metadata attributes to return. See {@link MapReturnType}
+ * @param value value expression
+ * @param bin bin or map value expression
+ * @param ctx optional context path for nested CDT
+ */
+ public static Exp getByValue(int returnType, Exp value, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE, returnType, value, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items identified by value range (valueBegin inclusive, valueEnd exclusive)
+ * If valueBegin is null, the range is less than valueEnd.
+ * If valueEnd is null, the range is greater than equal to valueBegin.
+ *
+ * Expression returns selected data specified by returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByValueRange(int returnType, Exp valueBegin, Exp valueEnd, Exp bin, CTX... ctx) {
+ byte[] bytes = ListExp.packRangeOperation(GET_BY_VALUE_INTERVAL, returnType, valueBegin, valueEnd, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items identified by values and returns selected data specified by
+ * returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByValueList(int returnType, Exp values, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE_LIST, returnType, values, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items nearest to value and greater by relative rank.
+ * Expression returns selected data specified by returnType (See {@link MapReturnType}).
+ *
+ * Examples for map [{4=2},{9=10},{5=15},{0=17}]:
+ *
+ *
(value,rank) = [selected items]
+ *
(11,1) = [{0=17}]
+ *
(11,-1) = [{9=10},{5=15},{0=17}]
+ *
+ */
+ public static Exp getByValueRelativeRankRange(int returnType, Exp value, Exp rank, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE_REL_RANK_RANGE, returnType, value, rank, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map items nearest to value and greater by relative rank with a count limit.
+ * Expression returns selected data specified by returnType (See {@link MapReturnType}).
+ *
+ * Examples for map [{4=2},{9=10},{5=15},{0=17}]:
+ *
+ *
(value,rank,count) = [selected items]
+ *
(11,1,1) = [{0=17}]
+ *
(11,-1,1) = [{9=10}]
+ *
+ */
+ public static Exp getByValueRelativeRankRange(int returnType, Exp value, Exp rank, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_VALUE_REL_RANK_RANGE, returnType, value, rank, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map item identified by index and returns selected data specified by
+ * returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByIndex(int returnType, Exp.Type valueType, Exp index, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_INDEX, returnType, index, ctx);
+ return addRead(bin, bytes, valueType);
+ }
+
+ /**
+ * Create expression that selects map items starting at specified index to the end of map and returns selected
+ * data specified by returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByIndexRange(int returnType, Exp index, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_INDEX_RANGE, returnType, index, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects "count" map items starting at specified index and returns selected data
+ * specified by returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByIndexRange(int returnType, Exp index, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_INDEX_RANGE, returnType, index, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects map item identified by rank and returns selected data specified by
+ * returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByRank(int returnType, Exp.Type valueType, Exp rank, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_RANK, returnType, rank, ctx);
+ return addRead(bin, bytes, valueType);
+ }
+
+ /**
+ * Create expression that selects map items starting at specified rank to the last ranked item and
+ * returns selected data specified by returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByRankRange(int returnType, Exp rank, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_RANK_RANGE, returnType, rank, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ /**
+ * Create expression that selects "count" map items starting at specified rank and returns selected
+ * data specified by returnType (See {@link MapReturnType}).
+ */
+ public static Exp getByRankRange(int returnType, Exp rank, Exp count, Exp bin, CTX... ctx) {
+ byte[] bytes = Pack.pack(GET_BY_RANK_RANGE, returnType, rank, count, ctx);
+ return addRead(bin, bytes, getValueType(returnType));
+ }
+
+ private static Exp addWrite(Exp bin, byte[] bytes, CTX[] ctx) {
+ int retType;
+
+ if (ctx == null || ctx.length == 0) {
+ retType = Exp.Type.MAP.code;
+ }
+ else {
+ retType = ((ctx[0].id & 0x10) == 0)? Exp.Type.MAP.code : Exp.Type.LIST.code;
+ }
+ return new Exp.Module(bin, bytes, retType, MODULE | Exp.MODIFY);
+ }
+
+ private static Exp addRead(Exp bin, byte[] bytes, Exp.Type retType) {
+ return new Exp.Module(bin, bytes, retType.code, MODULE);
+ }
+
+ private static Exp.Type getValueType(int returnType) {
+ int t = returnType & ~MapReturnType.INVERTED;
+
+ switch (t) {
+ case MapReturnType.INDEX:
+ case MapReturnType.REVERSE_INDEX:
+ case MapReturnType.RANK:
+ case MapReturnType.REVERSE_RANK:
+ // This method only called from expressions that can return multiple integers (ie list).
+ return Exp.Type.LIST;
+
+ case MapReturnType.COUNT:
+ return Exp.Type.INT;
+
+ case MapReturnType.KEY:
+ case MapReturnType.VALUE:
+ // This method only called from expressions that can return multiple objects (ie list).
+ return Exp.Type.LIST;
+
+ case MapReturnType.KEY_VALUE:
+ case MapReturnType.ORDERED_MAP:
+ case MapReturnType.UNORDERED_MAP:
+ return Exp.Type.MAP;
+
+ case MapReturnType.EXISTS:
+ return Exp.Type.BOOL;
+
+ default:
+ case MapReturnType.NONE:
+ throw new AerospikeException("Invalid MapReturnType: " + returnType);
+ }
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/query/Filter.java b/src/main/java/com/aerospike/dsl/client/query/Filter.java
new file mode 100644
index 0000000..5d22c4a
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/query/Filter.java
@@ -0,0 +1,799 @@
+/*
+ * Copyright 2012-2025 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.query;
+
+import com.aerospike.dsl.client.Value;
+import com.aerospike.dsl.client.cdt.CTX;
+import com.aerospike.dsl.client.command.Buffer;
+import com.aerospike.dsl.client.command.ParticleType;
+import com.aerospike.dsl.client.exp.Expression;
+import com.aerospike.dsl.client.util.Pack;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * Query filter definition.
+ *
+ * Currently, only one filter is allowed in a Statement, and must be on bin which has a secondary index defined.
+ */
+@Getter
+public final class Filter {
+
+ FilterType filterType;
+
+ public enum FilterType {
+ EQ, EQ_BY_INDEX, CONTAINS, CONTAINS_BY_INDEX, RANGE, RANGE_BY_INDEX,
+ GEO_CONTAINS, GEO_CONTAINS_BY_INDEX, GEO_WITHIN_REGION, GEO_WITHIN_REGION_BY_INDEX,
+ GEO_WITHIN_RADIUS, GEO_WITHIN_RADIUS_BY_INDEX
+ }
+
+
+ /**
+ * Create long equality filter for query.
+ *
+ * @param name bin name
+ * @param value filter value
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter equal(String name, long value, CTX... ctx) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ, name, IndexCollectionType.DEFAULT, val.getType(), val, val, ctx);
+ }
+
+ /**
+ * Create long equality filter for query with Expression.
+ *
+ * @param exp the Expression
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter equal(Expression exp, long value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ, null, exp.getBytes(), IndexCollectionType.DEFAULT, val.getType(), val, val);
+ }
+
+ /**
+ * Create long equality filter for query with index name.
+ *
+ * @param indexName the name that was assigned to the Secondary Index (SI)
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter equalByIndex(String indexName, long value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, val.getType(), val, val);
+ }
+
+ /**
+ * Create string equality filter for query.
+ *
+ * @param name bin name
+ * @param value filter value
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter equal(String name, String value, CTX... ctx) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ, name, IndexCollectionType.DEFAULT, val.getType(), val, val, ctx);
+ }
+
+ /**
+ * Create string equality filter for query with Expression.
+ *
+ * @param exp the Expression
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter equal(Expression exp, String value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ, null, exp.getBytes(), IndexCollectionType.DEFAULT, val.getType(), val, val);
+ }
+
+ /**
+ * Create string equality filter for query with index name.
+ *
+ * @param indexName the name that was assigned to the Secondary Index (SI)
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter equalByIndex(String indexName, String value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, val.getType(), val, val);
+ }
+
+ /**
+ * Create blob equality filter for query.
+ * Requires server version 7.0+.
+ *
+ * @param name bin name
+ * @param value filter value
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter equal(String name, byte[] value, CTX... ctx) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ, name, IndexCollectionType.DEFAULT, val.getType(), val, val, ctx);
+ }
+
+ /**
+ * Create blob equality filter for query with Expression.
+ *
+ * @param exp the Expression
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter equal(Expression exp, byte[] value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ, null, exp.getBytes(), IndexCollectionType.DEFAULT, val.getType(), val, val);
+ }
+
+ /**
+ * Create blob equality filter for query with index name.
+ *
+ * @param indexName the name that was assigned to the Secondary Index (SI)
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter equalByIndex(String indexName, byte[] value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.EQ_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, val.getType(), val, val);
+ }
+
+ /**
+ * Create contains number filter for query on collection index.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param value filter value
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter contains(String name, IndexCollectionType type, long value, CTX... ctx) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS, name, type, val.getType(), val, val, ctx);
+ }
+
+ /**
+ * Create contains number filter for query on collection index with Expression.
+ *
+ * @param exp expression to use for the filter
+ * @param type index collection type
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter contains(Expression exp, IndexCollectionType type, long value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS, null, exp.getBytes(), type, val.getType(), val, val);
+ }
+
+ /**
+ * Create contains number filter for query on collection index with index name.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter containsByIndex(String indexName, IndexCollectionType type, long value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS_BY_INDEX, indexName, null, type, val.getType(), val, val);
+ }
+
+ /**
+ * Create contains string filter for query on collection index.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param value filter value
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter contains(String name, IndexCollectionType type, String value, CTX... ctx) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS, name, type, val.getType(), val, val, ctx);
+ }
+
+ /**
+ * Create contains string filter for query on collection index with Expression.
+ *
+ * @param exp expression to use for the filter
+ * @param type index collection type
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter contains(Expression exp, IndexCollectionType type, String value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS, null, exp.getBytes(), type, val.getType(), val, val);
+ }
+
+ /**
+ * Create contains string filter for query on collection index with index name.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter containsByIndex(String indexName, IndexCollectionType type, String value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS_BY_INDEX, indexName, null, type, val.getType(), val, val);
+ }
+
+ /**
+ * Create contains byte[] filter for query on collection index.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param value filter value
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter contains(String name, IndexCollectionType type, byte[] value, CTX... ctx) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS, name, type, val.getType(), val, val, ctx);
+ }
+
+ /**
+ * Create contains byte[] filter for query on collection index with expression.
+ *
+ * @param exp expression to use for the filter
+ * @param type index collection type
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter contains(Expression exp, IndexCollectionType type, byte[] value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS, null, exp.getBytes(), type, val.getType(), val, val);
+ }
+
+ /**
+ * Create contains byte[] filter for query on collection index with index name.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param value filter value
+ * @return filter instance
+ */
+ public static Filter containsByIndex(String indexName, IndexCollectionType type, byte[] value) {
+ Value val = Value.get(value);
+ return new Filter(FilterType.CONTAINS_BY_INDEX, indexName, null, type, val.getType(), val, val);
+ }
+
+ /**
+ * Create range filter for query.
+ * Range arguments must be longs or integers which can be cast to longs.
+ * String ranges are not supported.
+ *
+ * @param name bin name
+ * @param begin filter begin value inclusive
+ * @param end filter end value inclusive
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter range(String name, long begin, long end, CTX... ctx) {
+ return new Filter(FilterType.RANGE, name, IndexCollectionType.DEFAULT, ParticleType.INTEGER, Value.get(begin), Value.get(end), ctx);
+ }
+
+ /**
+ * Create a range filter for an Expression-based Secondary Index (SI) query, with the actual Expression that was
+ * used to create the SI.
+ * Range arguments must be longs or integers which can be cast to longs.
+ * String ranges are not supported.
+ *
+ * @param exp the Expression
+ * @param begin filter begin value inclusive
+ * @param end filter end value inclusive
+ * @return filter instance
+ */
+ public static Filter range(Expression exp, long begin, long end) {
+ return new Filter(FilterType.RANGE, null, exp.getBytes(), IndexCollectionType.DEFAULT, ParticleType.INTEGER,
+ Value.get(begin), Value.get(end));
+ }
+
+ /**
+ * Create a range filter for an Expression-based Secondary Index (SI) query, using the SI name
+ * Range arguments must be longs or integers which can be cast to longs.
+ * String ranges are not supported.
+ *
+ * @param indexName the name that was assigned to the SI
+ * @param begin filter begin value inclusive
+ * @param end filter end value inclusive
+ * @return filter instance
+ */
+ public static Filter rangeByIndex(String indexName, long begin, long end) {
+ return new Filter(FilterType.RANGE_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, ParticleType.INTEGER,
+ Value.get(begin), Value.get(end));
+ }
+
+ /**
+ * Create range filter for query on collection index.
+ * Range arguments must be longs or integers which can be cast to longs.
+ * String ranges are not supported.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param begin filter begin value inclusive
+ * @param end filter end value inclusive
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter range(String name, IndexCollectionType type, long begin, long end, CTX... ctx) {
+ return new Filter(FilterType.RANGE, name, type, ParticleType.INTEGER, Value.get(begin), Value.get(end), ctx);
+ }
+
+ /**
+ * Create range filter for query on collection index with expression.
+ * Range arguments must be longs or integers which can be cast to longs.
+ * String ranges are not supported.
+ *
+ * @param exp the Expression to use for the filter
+ * @param type index collection type
+ * @param begin filter begin value inclusive
+ * @param end filter end value inclusive
+ * @return filter instance
+ */
+ public static Filter range(Expression exp, IndexCollectionType type, long begin, long end) {
+ return new Filter(FilterType.RANGE, null, exp.getBytes(), type, ParticleType.INTEGER, Value.get(begin), Value.get(end));
+ }
+
+ /**
+ * Create range filter for query on collection index with index name.
+ * Range arguments must be longs or integers which can be cast to longs.
+ * String ranges are not supported.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param begin filter begin value inclusive
+ * @param end filter end value inclusive
+ * @return filter instance
+ */
+ public static Filter rangeByIndex(String indexName, IndexCollectionType type, long begin, long end) {
+ return new Filter(FilterType.RANGE_BY_INDEX, indexName, null, type, ParticleType.INTEGER, Value.get(begin), Value.get(end));
+ }
+
+ /**
+ * Create geospatial "within region" filter for query.
+ *
+ * @param name bin name
+ * @param region GeoJSON region
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter geoWithinRegion(String name, String region, CTX... ctx) {
+ return new Filter(FilterType.GEO_WITHIN_REGION, name, IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(region), Value.get(region), ctx);
+ }
+
+ /**
+ * Create geospatial "within region" filter for query with expression.
+ *
+ * @param exp the Expression to use for the filter
+ * @param region GeoJSON region
+ * @return filter instance
+ */
+ public static Filter geoWithinRegion(Expression exp, String region) {
+ return new Filter(FilterType.GEO_WITHIN_REGION, null, exp.getBytes(), IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(region), Value.get(region));
+ }
+
+ /**
+ * Create geospatial "within region" filter for query with index name.
+ *
+ * @param indexName index name
+ * @param region GeoJSON region
+ * @return filter instance
+ */
+ public static Filter geoWithinRegionByIndex(String indexName, String region) {
+ return new Filter(FilterType.GEO_WITHIN_REGION_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(region), Value.get(region));
+ }
+
+ /**
+ * Create geospatial "within region" filter for query on collection index.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param region GeoJSON region
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter geoWithinRegion(String name, IndexCollectionType type, String region, CTX... ctx) {
+ return new Filter(FilterType.GEO_WITHIN_REGION, name, type, ParticleType.GEOJSON, Value.get(region), Value.get(region), ctx);
+ }
+
+ /**
+ * Create geospatial "within region" filter for query on collection index with expression.
+ *
+ * @param exp the Expression to use for the filter
+ * @param type index collection type
+ * @param region GeoJSON region
+ * @return filter instance
+ */
+ public static Filter geoWithinRegion(Expression exp, IndexCollectionType type, String region) {
+ return new Filter(FilterType.GEO_WITHIN_REGION, null, exp.getBytes(), type, ParticleType.GEOJSON, Value.get(region), Value.get(region));
+ }
+
+ /**
+ * Create geospatial "within region" filter for query on collection index with index name.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param region GeoJSON region
+ * @return filter instance
+ */
+ public static Filter geoWithinRegionByIndex(String indexName, IndexCollectionType type, String region) {
+ return new Filter(FilterType.GEO_WITHIN_REGION_BY_INDEX, indexName, null, type, ParticleType.GEOJSON, Value.get(region), Value.get(region));
+ }
+
+ /**
+ * Create geospatial "within radius" filter for query.
+ *
+ * @param name bin name
+ * @param lng longitude
+ * @param lat latitude
+ * @param radius radius (meters)
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter geoWithinRadius(String name, double lng, double lat, double radius, CTX... ctx) {
+ String rgnstr =
+ String.format("{ \"type\": \"AeroCircle\", "
+ + "\"coordinates\": [[%.8f, %.8f], %f] }",
+ lng, lat, radius);
+ return new Filter(FilterType.GEO_WITHIN_RADIUS, name, IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(rgnstr), Value.get(rgnstr), ctx);
+ }
+
+ /**
+ * Create geospatial "within radius" filter for query with expression.
+ *
+ * @param exp the Expression to use for the filter
+ * @param lng longitude
+ * @param lat latitude
+ * @param radius radius (meters)
+ * @return filter instance
+ */
+ public static Filter geoWithinRadius(Expression exp, double lng, double lat, double radius) {
+ String rgnstr =
+ String.format("{ \"type\": \"AeroCircle\", "
+ + "\"coordinates\": [[%.8f, %.8f], %f] }",
+ lng, lat, radius);
+ return new Filter(FilterType.GEO_WITHIN_RADIUS, null, exp.getBytes(), IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(rgnstr), Value.get(rgnstr));
+ }
+
+ /**
+ * Create geospatial "within radius" filter for query with index name.
+ *
+ * @param indexName index name
+ * @param lng longitude
+ * @param lat latitude
+ * @param radius radius (meters)
+ * @return filter instance
+ */
+ public static Filter geoWithinRadiusByIndex(String indexName, double lng, double lat, double radius) {
+ String rgnstr =
+ String.format("{ \"type\": \"AeroCircle\", "
+ + "\"coordinates\": [[%.8f, %.8f], %f] }",
+ lng, lat, radius);
+ return new Filter(FilterType.GEO_WITHIN_RADIUS_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(rgnstr), Value.get(rgnstr));
+ }
+
+ /**
+ * Create geospatial "within radius" filter for query on collection index.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param lng longitude
+ * @param lat latitude
+ * @param radius radius (meters)
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter geoWithinRadius(String name, IndexCollectionType type, double lng, double lat, double radius, CTX... ctx) {
+ String rgnstr =
+ String.format("{ \"type\": \"AeroCircle\", "
+ + "\"coordinates\": [[%.8f, %.8f], %f] }",
+ lng, lat, radius);
+ return new Filter(FilterType.GEO_WITHIN_RADIUS, name, type, ParticleType.GEOJSON, Value.get(rgnstr), Value.get(rgnstr), ctx);
+ }
+
+ /**
+ * Create geospatial "within radius" filter for query on collection index with expression.
+ *
+ * @param exp the Expression to use for the filter
+ * @param type index collection type
+ * @param lng longitude
+ * @param lat latitude
+ * @param radius radius (meters)
+ * @return filter instance
+ */
+ public static Filter geoWithinRadius(Expression exp, IndexCollectionType type, double lng, double lat, double radius) {
+ String rgnstr =
+ String.format("{ \"type\": \"AeroCircle\", "
+ + "\"coordinates\": [[%.8f, %.8f], %f] }",
+ lng, lat, radius);
+ return new Filter(FilterType.GEO_WITHIN_RADIUS, null, exp.getBytes(), type, ParticleType.GEOJSON, Value.get(rgnstr), Value.get(rgnstr));
+ }
+
+ /**
+ * Create geospatial "within radius" filter for query on collection index with index name.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param lng longitude
+ * @param lat latitude
+ * @param radius radius (meters)
+ * @return filter instance
+ */
+ public static Filter geoWithinRadiusByIndex(String indexName, IndexCollectionType type, double lng, double lat, double radius) {
+ String rgnstr =
+ String.format("{ \"type\": \"AeroCircle\", "
+ + "\"coordinates\": [[%.8f, %.8f], %f] }",
+ lng, lat, radius);
+ return new Filter(FilterType.GEO_WITHIN_RADIUS_BY_INDEX, indexName, null, type, ParticleType.GEOJSON, Value.get(rgnstr), Value.get(rgnstr));
+ }
+
+ /**
+ * Create geospatial "containing point" filter for query.
+ *
+ * @param name bin name
+ * @param point GeoJSON point
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter geoContains(String name, String point, CTX... ctx) {
+ return new Filter(FilterType.GEO_CONTAINS, name, IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(point), Value.get(point), ctx);
+ }
+
+ /**
+ * Create geospatial "containing point" filter for query with expression.
+ *
+ * @param exp the Expression to use for the filter
+ * @param point GeoJSON point
+ * @return filter instance
+ */
+ public static Filter geoContains(Expression exp, String point) {
+ return new Filter(FilterType.GEO_CONTAINS, null, exp.getBytes(), IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(point), Value.get(point));
+ }
+
+ /**
+ * Create geospatial "containing point" filter for query with index name.
+ *
+ * @param indexName index name
+ * @param point GeoJSON point
+ * @return filter instance
+ */
+ public static Filter geoContainsByIndex(String indexName, String point) {
+ return new Filter(FilterType.GEO_CONTAINS_BY_INDEX, indexName, null, IndexCollectionType.DEFAULT, ParticleType.GEOJSON, Value.get(point), Value.get(point));
+ }
+
+ /**
+ * Create geospatial "containing point" filter for query on collection index.
+ *
+ * @param name bin name
+ * @param type index collection type
+ * @param point GeoJSON point
+ * @param ctx optional context for elements within a CDT
+ * @return filter instance
+ */
+ public static Filter geoContains(String name, IndexCollectionType type, String point, CTX... ctx) {
+ return new Filter(FilterType.GEO_CONTAINS, name, type, ParticleType.GEOJSON, Value.get(point), Value.get(point), ctx);
+ }
+
+ /**
+ * Create geospatial "containing point" filter for query on collection index with expression.
+ *
+ * @param exp the Expression to use for the filter
+ * @param type index collection type
+ * @param point GeoJSON point
+ * @return filter instance
+ */
+ public static Filter geoContains(Expression exp, IndexCollectionType type, String point) {
+ return new Filter(FilterType.GEO_CONTAINS, null, exp.getBytes(), type, ParticleType.GEOJSON, Value.get(point), Value.get(point));
+ }
+
+ /**
+ * Create geospatial "containing point" filter for query on collection index with index name.
+ *
+ * @param indexName index name
+ * @param type index collection type
+ * @param point GeoJSON point
+ * @return filter instance
+ */
+ public static Filter geoContainsByIndex(String indexName, IndexCollectionType type, String point) {
+ return new Filter(FilterType.GEO_CONTAINS_BY_INDEX, indexName, null, type, ParticleType.GEOJSON, Value.get(point), Value.get(point));
+ }
+
+ private final String name;
+ private final String indexName;
+ private final IndexCollectionType colType;
+ private final byte[] packedCtx;
+ private final int valType;
+ private final Value begin;
+ private final Value end;
+ private final byte[] packedExp;
+
+ private Filter(FilterType type, String name, IndexCollectionType colType, int valType, Value begin, Value end,
+ CTX[] ctx) {
+ this(type, name, null, colType, valType, begin, end, (ctx != null && ctx.length > 0) ? Pack.pack(ctx) : null, null);
+ }
+ private Filter(FilterType type, String indexName, byte[] exp, IndexCollectionType colType, int valType, Value begin,
+ Value end) {
+ this(type, null, indexName, colType, valType, begin, end, null, exp);
+ }
+
+ Filter(FilterType filterType, String name, String indexName, IndexCollectionType colType, int valType, Value begin, Value end,
+ byte[] packedCtx, byte[] packedExp) {
+ this.filterType = filterType;
+ this.name = name;
+ this.indexName = indexName;
+ this.colType = colType;
+ this.valType = valType;
+ this.begin = begin;
+ this.end = end;
+ this.packedCtx = packedCtx;
+ this.packedExp = packedExp;
+ }
+
+ /**
+ * Write filter to send command buffer.
+ * For internal use only.
+ */
+ public int write(byte[] buf, int offset) {
+ // Write name.
+ int len = Buffer.stringToUtf8(name, buf, offset + 1);
+ buf[offset] = (byte)len;
+ offset += len + 1;
+
+ // Write particle type.
+ buf[offset++] = (byte)valType;
+
+ // Write filter begin.
+ len = begin.write(buf, offset + 4);
+ Buffer.intToBytes(len, buf, offset);
+ offset += len + 4;
+
+ // Write filter end.
+ len = end.write(buf, offset + 4);
+ Buffer.intToBytes(len, buf, offset);
+ offset += len + 4;
+
+ return offset;
+ }
+
+ /**
+ * Retrieve index collection type.
+ * For internal use only.
+ */
+ public IndexCollectionType getCollectionType() {
+ return colType;
+ }
+
+ /**
+ * Filter name.
+ * For internal use only.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Index name.
+ * For internal use only.
+ */
+ public String getIndexName() {
+ return indexName;
+ }
+
+ /**
+ * Index collection type.
+ * For internal use only.
+ */
+ public IndexCollectionType getColType() {
+ return colType;
+ }
+
+ /**
+ * Filter begin value.
+ * For internal use only.
+ */
+ public Value getBegin() {
+ return begin;
+ }
+
+ /**
+ * Filter begin value.
+ * For internal use only.
+ */
+ public Value getEnd() {
+ return end;
+ }
+
+ /**
+ * Filter Value type.
+ * For internal use only.
+ */
+ public int getValType() {
+ return valType;
+ }
+
+ /**
+ * Retrieve packed Context.
+ * For internal use only.
+ */
+ public byte[] getPackedCtx() {
+ return packedCtx;
+ }
+
+ /**
+ * Retrieve packed Expression.
+ * For internal use only.
+ */
+ public byte[] getPackedExp() {
+ return packedExp;
+ }
+
+ /**
+ * Check for Filter equality.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Filter other = (Filter) obj;
+ if (begin == null) {
+ if (other.begin != null)
+ return false;
+ } else if (!begin.equals(other.begin))
+ return false;
+ if (colType != other.colType)
+ return false;
+ if (end == null) {
+ if (other.end != null)
+ return false;
+ } else if (!end.equals(other.end))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (!Arrays.equals(packedCtx, other.packedCtx))
+ return false;
+ if (valType != other.valType)
+ return false;
+ return true;
+ }
+
+ /**
+ * Generate Filter hashCode.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((begin == null) ? 0 : begin.hashCode());
+ result = prime * result + ((colType == null) ? 0 : colType.hashCode());
+ result = prime * result + ((end == null) ? 0 : end.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + Arrays.hashCode(packedCtx);
+ result = prime * result + valType;
+ return result;
+ }
+}
diff --git a/src/main/java/com/aerospike/dsl/client/query/IndexCollectionType.java b/src/main/java/com/aerospike/dsl/client/query/IndexCollectionType.java
new file mode 100644
index 0000000..54b01b6
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/query/IndexCollectionType.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2021 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.query;
+
+/**
+ * Secondary index collection type.
+ */
+public enum IndexCollectionType {
+ /**
+ * Normal scalar index.
+ */
+ DEFAULT,
+
+ /**
+ * Index list elements.
+ */
+ LIST,
+
+ /**
+ * Index map keys.
+ */
+ MAPKEYS,
+
+ /**
+ * Index map values.
+ */
+ MAPVALUES;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/query/IndexType.java b/src/main/java/com/aerospike/dsl/client/query/IndexType.java
new file mode 100644
index 0000000..c9c2c35
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/query/IndexType.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.query;
+
+/**
+ * Underlying data type of secondary index.
+ */
+public enum IndexType {
+ /**
+ * Number index.
+ */
+ NUMERIC,
+
+ /**
+ * String index.
+ */
+ STRING,
+
+ /**
+ * byte[] index. Requires server version 7.0+.
+ */
+ BLOB,
+
+ /**
+ * 2-dimensional spherical geospatial index.
+ */
+ GEO2DSPHERE;
+}
diff --git a/src/main/java/com/aerospike/dsl/client/query/RegexFlag.java b/src/main/java/com/aerospike/dsl/client/query/RegexFlag.java
new file mode 100644
index 0000000..15d4031
--- /dev/null
+++ b/src/main/java/com/aerospike/dsl/client/query/RegexFlag.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012-2023 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
+ *
+ * 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 com.aerospike.dsl.client.query;
+
+/**
+ * Regex bit flags. Use BITWISE OR to combine flags. Example:
+ *
+ *