diff --git a/.github/workflows/pr-open.yaml b/.github/workflows/pr-open.yaml
index 99692a8d..8fd75af8 100644
--- a/.github/workflows/pr-open.yaml
+++ b/.github/workflows/pr-open.yaml
@@ -143,6 +143,30 @@ jobs:
env:
MAVEN_OPTS: ${{ vars.MAVEN_OPTS }}
+ - name: Build & run examples
+ run: |
+ mvn \
+ --batch-mode \
+ --no-transfer-progress \
+ --update-snapshots \
+ --fail-at-end \
+ --errors \
+ -pl examples \
+ -am \
+ package \
+ -DskipTests
+
+ # Run from the examples module so that examples which load YAML via
+ # relative paths (src/main/resources/*.yml) resolve their config files.
+ cd examples
+ java -jar target/*-jar-with-dependencies.jar all \
+ -h 127.0.0.1 \
+ -p 3000 \
+ -n test \
+ --report target/example-reports/TEST-examples.xml
+ env:
+ MAVEN_OPTS: ${{ vars.MAVEN_OPTS }}
+
- name: Upload surefire/failsafe reports (always)
if: always()
uses: actions/upload-artifact@v4
@@ -151,5 +175,6 @@ jobs:
path: |
**/target/surefire-reports/**
**/target/failsafe-reports/**
+ examples/target/example-reports/**
if-no-files-found: ignore
retention-days: ${{ vars.ARTIFACT_RETENTION_DAYS }}
diff --git a/client/src/main/java/com/aerospike/client/sdk/policy/BehaviorFileMonitor.java b/client/src/main/java/com/aerospike/client/sdk/policy/BehaviorFileMonitor.java
index bf9fb0ee..3c2a96af 100644
--- a/client/src/main/java/com/aerospike/client/sdk/policy/BehaviorFileMonitor.java
+++ b/client/src/main/java/com/aerospike/client/sdk/policy/BehaviorFileMonitor.java
@@ -46,12 +46,11 @@ class BehaviorFileMonitor implements Closeable {
private static final Logger log = LoggerFactory.getLogger(Loggers.BEHAVIOR);
private final BehaviorRegistry registry = BehaviorRegistry.getInstance();
- // Use a scheduled executor with 2 threads: one for monitoring, one for reload tasks
- private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2, r -> {
- Thread t = new Thread(r, "BehaviorFileMonitor");
- t.setDaemon(true);
- return t;
- });
+ // Scheduled executor with 2 threads: one for monitoring, one for reload tasks.
+ // Created lazily and recreated after shutdown so that monitoring can be
+ // started, stopped, and started again within the same process (this is a
+ // singleton, so a single shut-down executor must not permanently disable it).
+ private ScheduledExecutorService executor;
private Path yamlFilePath;
private WatchService watchService;
@@ -113,13 +112,31 @@ public void startMonitoring(String yamlFilePath, long reloadDelayMs) throws IOEx
// Start monitoring thread
isMonitoring = true;
- executor.submit(this::monitorFile);
+ ensureExecutor().submit(this::monitorFile);
if (log.isDebugEnabled()) {
log.debug("Started monitoring YAML file: %s checking every %,dms".formatted(yamlFilePath, reloadDelayMs));
}
}
+ /**
+ * Return the executor, (re)creating it if it has never been started or was
+ * previously shut down. Because this class is a process-wide singleton, a
+ * prior {@link #shutdown()} (for example via try-with-resources on the
+ * {@code Closeable} returned by {@code Behavior.startMonitoringWithResource})
+ * must not permanently prevent monitoring from being started again.
+ */
+ private synchronized ScheduledExecutorService ensureExecutor() {
+ if (executor == null || executor.isShutdown()) {
+ executor = Executors.newScheduledThreadPool(2, r -> {
+ Thread t = new Thread(r, "BehaviorFileMonitor");
+ t.setDaemon(true);
+ return t;
+ });
+ }
+ return executor;
+ }
+
/**
* Stop monitoring the file
*/
@@ -240,7 +257,7 @@ private void handleFileChange() {
lastModified = currentModified;
// Schedule reload with delay to avoid multiple reloads
- executor.schedule(this::loadBehaviors, reloadDelayMs, TimeUnit.MILLISECONDS);
+ ensureExecutor().schedule(this::loadBehaviors, reloadDelayMs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
if (log.isErrorEnabled()) {
@@ -285,13 +302,25 @@ private void loadBehaviors() {
*/
public void shutdown() {
stopMonitoring();
- executor.shutdown();
+
+ ScheduledExecutorService exec;
+ synchronized (this) {
+ exec = executor;
+ // Clear the reference so a subsequent startMonitoring() recreates it.
+ executor = null;
+ }
+
+ if (exec == null) {
+ return;
+ }
+
+ exec.shutdown();
try {
- if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
- executor.shutdownNow();
+ if (!exec.awaitTermination(5, TimeUnit.SECONDS)) {
+ exec.shutdownNow();
}
} catch (InterruptedException e) {
- executor.shutdownNow();
+ exec.shutdownNow();
Thread.currentThread().interrupt();
}
}
diff --git a/client/src/test/java/com/aerospike/client/sdk/ErrorDetailVerbosityTest.java b/client/src/test/java/com/aerospike/client/sdk/ErrorDetailVerbosityTest.java
index 654df9c7..34b26cae 100644
--- a/client/src/test/java/com/aerospike/client/sdk/ErrorDetailVerbosityTest.java
+++ b/client/src/test/java/com/aerospike/client/sdk/ErrorDetailVerbosityTest.java
@@ -47,12 +47,8 @@ public class ErrorDetailVerbosityTest extends ClusterTest {
@BeforeAll
public static void setup() {
- // Extended error-detail (subcode/message) plus the verbosity-3 expression
- // build trace (SERVER-1137). The SERVER-1137 feature branch is cut from the
- // 8.1.1 line and reports its base version as 8.1.1.0-start-*, so gate at
- // 8.1.1 rather than the 8.1.3 release that first shipped the base tier.
- Assumptions.assumeTrue(args.serverVersion.isGreaterOrEqual(8, 1, 1, 0),
- "Extended error-detail requires server version 8.1.1 or later");
+ Assumptions.assumeTrue(args.serverVersion.isGreaterOrEqual(8, 1, 3, 0),
+ "Extended error-detail requires server version 8.1.3 or later");
intKey = args.set.id("edv-int-key");
strKey = args.set.id("edv-str-key");
diff --git a/client/src/test/java/com/aerospike/client/sdk/SuiteCore.java b/client/src/test/java/com/aerospike/client/sdk/SuiteCore.java
index e6afe30e..56d6d6a8 100644
--- a/client/src/test/java/com/aerospike/client/sdk/SuiteCore.java
+++ b/client/src/test/java/com/aerospike/client/sdk/SuiteCore.java
@@ -20,6 +20,7 @@
import org.junit.platform.suite.api.Suite;
import com.aerospike.client.sdk.policy.AsyncRecordStreamTest;
+import com.aerospike.client.sdk.policy.BehaviorFileMonitorRestartTest;
import com.aerospike.client.sdk.policy.BehaviorTest;
import com.aerospike.client.sdk.policy.BehaviorYamlTest;
import com.aerospike.client.sdk.policy.QueryProducerCancellationTest;
@@ -28,6 +29,7 @@
@Suite
@SelectClasses({
AsyncRecordStreamTest.class,
+ BehaviorFileMonitorRestartTest.class,
BehaviorTest.class,
BehaviorYamlTest.class,
QueryProducerCancellationTest.class,
diff --git a/client/src/test/java/com/aerospike/client/sdk/policy/BehaviorFileMonitorRestartTest.java b/client/src/test/java/com/aerospike/client/sdk/policy/BehaviorFileMonitorRestartTest.java
new file mode 100644
index 00000000..256b9f9d
--- /dev/null
+++ b/client/src/test/java/com/aerospike/client/sdk/policy/BehaviorFileMonitorRestartTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012-2026 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.client.sdk.policy;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Regression tests for {@link BehaviorFileMonitor} lifecycle.
+ *
+ *
The monitor is a process-wide singleton backed by a scheduled executor. A
+ * prior bug shut that executor down on {@code close()}/{@code shutdown()} and
+ * never recreated it, so the second {@code startMonitoring} in a JVM failed with
+ * {@code RejectedExecutionException}. Running examples in separate JVMs hid this;
+ * running them in one process (or any real app that stops and restarts
+ * monitoring) surfaced it. These tests lock in that monitoring is restartable.
+ */
+@DisplayName("BehaviorFileMonitor Restart Tests")
+public class BehaviorFileMonitorRestartTest {
+
+ private static final String YAML = """
+ behaviors:
+ simple:
+ allOperations:
+ abandonCallAfter: 5s
+ maximumNumberOfCallAttempts: 3
+ """;
+
+ private Path writeConfig(Path dir) throws IOException {
+ Path file = dir.resolve("behavior-monitor-test.yml");
+ Files.writeString(file, YAML);
+ return file;
+ }
+
+ @AfterEach
+ void tearDown() {
+ // Reset shared singleton state so tests do not leak into one another.
+ Behavior.shutdownMonitor();
+ }
+
+ @Test
+ @DisplayName("Should restart monitoring after a try-with-resources close in the same process")
+ void shouldRestartAfterClose(@TempDir Path tempDir) throws IOException {
+ Path config = writeConfig(tempDir);
+
+ try (Closeable monitor = Behavior.startMonitoringWithResource(config.toString())) {
+ assertNotNull(monitor);
+ assertTrue(Behavior.isMonitoring(), "monitoring should be active during first session");
+ }
+ assertFalse(Behavior.isMonitoring(), "monitoring should stop after close");
+
+ // Before the fix, this second start reused the terminated singleton
+ // executor and threw RejectedExecutionException.
+ assertDoesNotThrow(() -> {
+ try (Closeable monitor = Behavior.startMonitoringWithResource(config.toString())) {
+ assertNotNull(monitor);
+ assertTrue(Behavior.isMonitoring(), "monitoring should be active during second session");
+ }
+ });
+ }
+
+ @Test
+ @DisplayName("Should restart monitoring after an explicit shutdown")
+ void shouldRestartAfterExplicitShutdown(@TempDir Path tempDir) throws IOException {
+ Path config = writeConfig(tempDir);
+
+ Behavior.startMonitoring(config.toString());
+ assertTrue(Behavior.isMonitoring());
+ Behavior.shutdownMonitor();
+ assertFalse(Behavior.isMonitoring());
+
+ assertDoesNotThrow(() -> Behavior.startMonitoring(config.toString()));
+ assertTrue(Behavior.isMonitoring());
+ }
+
+ @Test
+ @DisplayName("Should support several sequential monitoring sessions")
+ void shouldSupportSeveralSequentialSessions(@TempDir Path tempDir) throws IOException {
+ Path config = writeConfig(tempDir);
+
+ assertDoesNotThrow(() -> {
+ for (int i = 0; i < 3; i++) {
+ try (Closeable monitor = Behavior.startMonitoringWithResource(config.toString())) {
+ assertNotNull(monitor);
+ assertTrue(Behavior.isMonitoring(), "monitoring should be active in session " + i);
+ }
+ }
+ });
+ }
+}
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..15ccb39b
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,69 @@
+# Aerospike Java SDK Examples
+
+The examples module contains runnable examples that double as documentation source. Example classes should focus on the SDK usage being demonstrated; the runner owns connection setup, configuration, verification, cleanup, and CI reporting.
+
+## Run Examples
+
+Build the examples JAR:
+
+```bash
+mvn -pl examples -am package -DskipTests
+```
+
+Run one example:
+
+```bash
+./examples/run_examples BatchExample -h localhost -p 3000 -n test
+```
+
+Run all registered examples:
+
+```bash
+./examples/run_examples all -h localhost -p 3000 -n test
+```
+
+Write a JUnit XML report:
+
+```bash
+./examples/run_examples all \
+ -h localhost \
+ -p 3000 \
+ -n test \
+ --report examples/target/example-reports/TEST-examples.xml
+```
+
+Useful runner options:
+
+- `--fail-fast`: stop after the first failed example.
+- `--include-tags smoke,records`: run examples with at least one matching tag.
+- `--exclude-tags config`: skip examples with matching tags.
+
+## Add An Example
+
+1. Create a class in `src/main/java/com/aerospike/examples` that extends `Example`.
+2. Implement `runExample()` with no constructor or runner arguments; use `cluster()`, `dataSet()`, `namespace()`, and `console` from `Example` when needed.
+3. Use `dataSet()` or `dataSet("set-name")` when the example writes records, unless the example is specifically demonstrating a fixed namespace or set.
+4. Register the example in `ExampleRegistry` with tags and an `ExampleFixture`.
+5. Add verification in a fixture under `com.aerospike.examples.fixtures`.
+
+Every example registered for CI should have an explicit fixture decision:
+
+- Use a fixture with `setup`, `verify`, and `cleanup` when the example changes server state.
+- Use `ExampleFixture.NONE` only when there is no meaningful post-run state to verify.
+- Throw `ExampleSkipException` for server capability or configuration gates that should be reported as skipped rather than failed.
+
+## Verification Model
+
+The runner executes each registered example in this order:
+
+1. Fixture setup
+2. Example body
+3. Fixture verification
+4. Fixture cleanup
+5. Result reporting
+
+Verification should check state after the example runs. Prefer reusable helpers in `ExampleAssertions` for common checks such as truncating a dataset, counting records, checking that a record exists, and comparing bin values.
+
+## CI
+
+PR CI builds the examples module, runs `all` against the Aerospike server provisioned for integration tests, and writes `examples/target/example-reports/TEST-examples.xml`. The report is uploaded with the other test artifacts so example failures are visible as test failures.
diff --git a/examples/run_examples b/examples/run_examples
index cda9aef4..f605d340 100755
--- a/examples/run_examples
+++ b/examples/run_examples
@@ -29,5 +29,10 @@ if [ -z "$JAR_FILE" ]; then
exit 1
fi
+# Run from the examples directory so that examples which load YAML via relative
+# paths (src/main/resources/*.yml) resolve their config files regardless of the
+# caller's working directory.
+cd "$SCRIPT_DIR" || exit 1
+
# Run Main with all arguments
java -cp "$JAR_FILE" com.aerospike.examples.Main "$@"
diff --git a/examples/src/main/java/com/aerospike/examples/AelTestSpecRunner.java b/examples/src/main/java/com/aerospike/examples/AelTestSpecRunner.java
index 8d1c77c7..f44f226e 100644
--- a/examples/src/main/java/com/aerospike/examples/AelTestSpecRunner.java
+++ b/examples/src/main/java/com/aerospike/examples/AelTestSpecRunner.java
@@ -21,7 +21,6 @@
import java.util.Map;
import java.util.TreeMap;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordStream;
@@ -37,50 +36,65 @@
* path functions, transactions, rank-based access, return type variations,
* and edge cases.
*/
-public class AelTestSpecRunner {
+public class AelTestSpecRunner extends Example {
private static final String SEP = "=".repeat(70);
- private static int totalTests = 0;
- private static int passedTests = 0;
- private static int failedTests = 0;
- private static int errorTests = 0;
+ private int totalTests = 0;
+ private int passedTests = 0;
+ private int failedTests = 0;
+ private int errorTests = 0;
public static void main(String[] args) throws Exception {
Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments).connect()) {
- Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet set = DataSet.of("test", "ael_test_spec");
-
- session.truncate(set);
- Thread.sleep(200);
- setupTestData(session, set);
-
- testScalarBinAccess(session, set);
- testTypeCasting(session, set);
- testTypeDerivation(session, set);
- testMapAccess(session, set);
- testListAccess(session, set);
- testNestedCDT(session, set);
- testArithmetic(session, set);
- testBitwise(session, set);
- testComparison(session, set);
- testLogical(session, set);
- testControlStructures(session, set);
- testMetadata(session, set);
- testPathFunctions(session, set);
- testTransactionScenario(session, set);
- testRankBased(session, set);
- testReturnTypes(session, set);
- testEdgeCases(session, set);
-
- printSummary();
+ int exitCode = Main.runExamples(
+ new Console(),
+ arguments,
+ new String[] {"AelTestSpecRunner"});
+
+ if (exitCode != 0) {
+ throw new IllegalStateException("AelTestSpecRunner failed");
}
}
+ @Override
+ public void runExample() throws Exception {
+ Session session = cluster().createSession(Behavior.DEFAULT);
+ DataSet set = dataSet("ael_test_spec");
+
+ // The runner's fixture truncates ael_test_spec before this example runs.
+ setupTestData(session, set);
+
+ testScalarBinAccess(session, set);
+ testTypeCasting(session, set);
+ testTypeDerivation(session, set);
+ testMapAccess(session, set);
+ testListAccess(session, set);
+ testNestedCDT(session, set);
+ testArithmetic(session, set);
+ testBitwise(session, set);
+ testComparison(session, set);
+ testLogical(session, set);
+ testControlStructures(session, set);
+ testMetadata(session, set);
+ testPathFunctions(session, set);
+ testTransactionScenario(session, set);
+ testRankBased(session, set);
+ testReturnTypes(session, set);
+ testEdgeCases(session, set);
+
+ printSummary();
+
+ // This is a diagnostic spec-coverage tool, not a pass/fail conformance
+ // gate. The failures and errors above are largely documented AEL
+ // limitations (see the "Known issue" notes and OperationDifferences), so
+ // the example does not fail CI on them. Inspect the summary to track how
+ // much of the spec the implementation currently supports.
+ }
+
// =====================================================================
// Test Data Setup
// =====================================================================
- static void setupTestData(Session session, DataSet set) {
+ void setupTestData(Session session, DataSet set) {
// Record 1: scalar bins
session.upsert(set.id(1))
.bin("intBin").setTo(42)
@@ -221,7 +235,7 @@ static void setupTestData(Session session, DataSet set) {
// =====================================================================
// 1. Scalar Bin Access
// =====================================================================
- static void testScalarBinAccess(Session session, DataSet set) {
+ void testScalarBinAccess(Session session, DataSet set) {
section("1. SCALAR BIN ACCESS");
readCheck("S01", session, set, 1, "$.intBin", 42L);
@@ -239,7 +253,7 @@ static void testScalarBinAccess(Session session, DataSet set) {
// =====================================================================
// 2. Type Inference and Casting
// =====================================================================
- static void testTypeCasting(Session session, DataSet set) {
+ void testTypeCasting(Session session, DataSet set) {
section("2. TYPE INFERENCE AND CASTING");
readCheck("T01", session, set, 1, "$.intBin.asFloat()", 42.0);
@@ -260,7 +274,7 @@ static void testTypeCasting(Session session, DataSet set) {
// =====================================================================
// 2b. Type Derivation (no explicit get(type:) annotations)
// =====================================================================
- static void testTypeDerivation(Session session, DataSet set) {
+ void testTypeDerivation(Session session, DataSet set) {
section("2b. TYPE DERIVATION");
System.out.println(" Record 11: a=10(INT), b=10(INT), c=true(BOOL), d=11(INT),");
System.out.println(" e=3.14(FLOAT), f=\"hello\"(STRING), g=false(BOOL)");
@@ -384,7 +398,7 @@ static void testTypeDerivation(Session session, DataSet set) {
// =====================================================================
// 3. Map Access
// =====================================================================
- static void testMapAccess(Session session, DataSet set) {
+ void testMapAccess(Session session, DataSet set) {
section("3. MAP ACCESS");
readCheck("M01", session, set, 2, "$.m.alpha.get(type: INT)", 10L);
@@ -430,7 +444,7 @@ static void testMapAccess(Session session, DataSet set) {
// =====================================================================
// 4. List Access
// =====================================================================
- static void testListAccess(Session session, DataSet set) {
+ void testListAccess(Session session, DataSet set) {
section("4. LIST ACCESS");
readCheck("L01", session, set, 2, "$.l.[0].get(type: INT)", 50L);
@@ -457,7 +471,7 @@ static void testListAccess(Session session, DataSet set) {
// =====================================================================
// 5. Nested CDT Navigation
// =====================================================================
- static void testNestedCDT(Session session, DataSet set) {
+ void testNestedCDT(Session session, DataSet set) {
section("5. NESTED CDT NAVIGATION");
readCheck("N01", session, set, 3, "$.profile.address.city.get(type: STRING)", "Austin");
@@ -480,7 +494,7 @@ static void testNestedCDT(Session session, DataSet set) {
// =====================================================================
// 6. Arithmetic
// =====================================================================
- static void testArithmetic(Session session, DataSet set) {
+ void testArithmetic(Session session, DataSet set) {
section("6. ARITHMETIC");
readCheck("A01", session, set, 7, "$.price + $.qty", 105L);
@@ -500,7 +514,7 @@ static void testArithmetic(Session session, DataSet set) {
// =====================================================================
// 7. Bitwise Operations
// =====================================================================
- static void testBitwise(Session session, DataSet set) {
+ void testBitwise(Session session, DataSet set) {
section("7. BITWISE OPERATIONS");
readCheck("B01", session, set, 1, "$.intBin & 15", 10L);
@@ -522,7 +536,7 @@ static void testBitwise(Session session, DataSet set) {
// =====================================================================
// 8. Comparison Operators
// =====================================================================
- static void testComparison(Session session, DataSet set) {
+ void testComparison(Session session, DataSet set) {
section("8. COMPARISON OPERATORS");
filterCheck("C01", session, set, 1, "$.intBin == 42", true);
@@ -545,7 +559,7 @@ static void testComparison(Session session, DataSet set) {
// =====================================================================
// 9. Logical Operators
// =====================================================================
- static void testLogical(Session session, DataSet set) {
+ void testLogical(Session session, DataSet set) {
section("9. LOGICAL OPERATORS");
filterCheck("LG01", session, set, 1, "$.intBin > 40 and $.strBin == 'hello'", true);
@@ -567,7 +581,7 @@ static void testLogical(Session session, DataSet set) {
// =====================================================================
// 10. Control Structures
// =====================================================================
- static void testControlStructures(Session session, DataSet set) {
+ void testControlStructures(Session session, DataSet set) {
section("10. CONTROL STRUCTURES");
// 10.1 Variable binding (with...do — current implementation)
@@ -622,7 +636,7 @@ static void testControlStructures(Session session, DataSet set) {
// =====================================================================
// 11. Metadata
// =====================================================================
- static void testMetadata(Session session, DataSet set) {
+ void testMetadata(Session session, DataSet set) {
section("11. METADATA");
readPrint("MD01", session, set, 1, "$.ttl()", "TTL in seconds");
@@ -640,7 +654,7 @@ static void testMetadata(Session session, DataSet set) {
// =====================================================================
// 12. Path Functions
// =====================================================================
- static void testPathFunctions(Session session, DataSet set) {
+ void testPathFunctions(Session session, DataSet set) {
section("12. PATH FUNCTIONS");
readCheck("PF01", session, set, 2, "$.m.alpha.get(type: INT)", 10L);
@@ -670,7 +684,7 @@ static void testPathFunctions(Session session, DataSet set) {
// =====================================================================
// 13. Transaction Scenario
// =====================================================================
- static void testTransactionScenario(Session session, DataSet set) {
+ void testTransactionScenario(Session session, DataSet set) {
section("13. TRANSACTION SCENARIO");
readCheck("TX01", session, set, 8, "$.txns.{}.count()", 12L);
@@ -723,7 +737,7 @@ static void testTransactionScenario(Session session, DataSet set) {
// =====================================================================
// 14. Rank-Based Access (Record 9)
// =====================================================================
- static void testRankBased(Session session, DataSet set) {
+ void testRankBased(Session session, DataSet set) {
section("14. RANK-BASED ACCESS");
System.out.println(" scores rank order: english(78) < math(85) < history(88) < science(92) < art(95)");
System.out.println();
@@ -751,7 +765,7 @@ static void testRankBased(Session session, DataSet set) {
// =====================================================================
// 15. Return Type Variations
// =====================================================================
- static void testReturnTypes(Session session, DataSet set) {
+ void testReturnTypes(Session session, DataSet set) {
section("15. RETURN TYPE VARIATIONS");
System.out.println(" Using $.m.{alpha,beta,gamma} on Record 2");
System.out.println();
@@ -781,7 +795,7 @@ static void testReturnTypes(Session session, DataSet set) {
// =====================================================================
// 16. Edge Cases
// =====================================================================
- static void testEdgeCases(Session session, DataSet set) {
+ void testEdgeCases(Session session, DataSet set) {
section("16. EDGE CASES");
readCheck("E01", session, set, 2, "$.l.[0].get(type: INT)", 50L);
@@ -811,7 +825,7 @@ static void testEdgeCases(Session session, DataSet set) {
// =====================================================================
// Helper Methods
// =====================================================================
- static void section(String title) {
+ void section(String title) {
System.out.println();
System.out.println(SEP);
System.out.println(title);
@@ -822,7 +836,7 @@ static void section(String title) {
/**
* Execute a read expression and compare to an expected value.
*/
- static void readCheck(String id, Session session, DataSet set, int pk,
+ void readCheck(String id, Session session, DataSet set, int pk,
String ael, Object expected) {
totalTests++;
System.out.printf(" [%s] %s%n", id, ael);
@@ -859,7 +873,7 @@ static void readCheck(String id, Session session, DataSet set, int pk,
/**
* Execute a read expression, print the result without checking.
*/
- static void readPrint(String id, Session session, DataSet set, int pk,
+ void readPrint(String id, Session session, DataSet set, int pk,
String ael, String description) {
totalTests++;
System.out.printf(" [%s] %s%n", id, ael);
@@ -882,7 +896,7 @@ static void readPrint(String id, Session session, DataSet set, int pk,
/**
* Execute a read expression where we expect an error.
*/
- static void readExpectError(String id, Session session, DataSet set, int pk,
+ void readExpectError(String id, Session session, DataSet set, int pk,
String ael, String description) {
totalTests++;
System.out.printf(" [%s] %s%n", id, ael);
@@ -906,7 +920,7 @@ static void readExpectError(String id, Session session, DataSet set, int pk,
/**
* Execute a filter expression and check whether the record is returned.
*/
- static void filterCheck(String id, Session session, DataSet set, int pk,
+ void filterCheck(String id, Session session, DataSet set, int pk,
String ael, boolean expectFound) {
totalTests++;
System.out.printf(" [%s] %s%n", id, ael);
@@ -936,7 +950,7 @@ static void filterCheck(String id, Session session, DataSet set, int pk,
/**
* Execute a filter expression and print the result without checking.
*/
- static void filterPrint(String id, Session session, DataSet set, int pk,
+ void filterPrint(String id, Session session, DataSet set, int pk,
String ael, String description) {
totalTests++;
System.out.printf(" [%s] %s%n", id, ael);
@@ -960,7 +974,7 @@ static String className(Object obj) {
return obj == null ? "null" : obj.getClass().getSimpleName();
}
- static void printSummary() {
+ void printSummary() {
System.out.println();
System.out.println(SEP);
System.out.printf("SUMMARY: %d total | %d passed | %d failed | %d errors%n",
diff --git a/examples/src/main/java/com/aerospike/examples/Args.java b/examples/src/main/java/com/aerospike/examples/Args.java
index a6a45ec4..e5dbeba1 100644
--- a/examples/src/main/java/com/aerospike/examples/Args.java
+++ b/examples/src/main/java/com/aerospike/examples/Args.java
@@ -17,6 +17,8 @@
package com.aerospike.examples;
import java.util.Arrays;
+import java.util.Set;
+import java.util.TreeSet;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
@@ -40,6 +42,10 @@ public class Args {
public final String clientKeyFile;
public final AuthMode authMode;
public final boolean useServicesAlternate;
+ public final String reportPath;
+ public final boolean failFast;
+ public final Set includeTags;
+ public final Set excludeTags;
public Args(CommandLine cl) {
this.host = cl.getOptionValue("h", "localhost");
@@ -90,6 +96,10 @@ public Args(CommandLine cl) {
}
this.useServicesAlternate = cl.hasOption("a");
+ this.reportPath = cl.getOptionValue("report");
+ this.failFast = cl.hasOption("fail-fast");
+ this.includeTags = parseTags(cl.getOptionValue("include-tags"));
+ this.excludeTags = parseTags(cl.getOptionValue("exclude-tags"));
}
/**
@@ -113,6 +123,28 @@ public static void addCommonOptions(Options options) {
options.addOption("caFile", "caFile", true, "TLS CA certificate file path");
options.addOption("clientCertFile", "clientCertFile", true, "TLS client certificate file path");
options.addOption("clientKeyFile", "clientKeyFile", true, "TLS client key file path");
+ options.addOption(null, "report", true, "Write JUnit XML example report to the given path");
+ options.addOption(null, "fail-fast", false, "Stop after the first failed example");
+ options.addOption(null, "include-tags", true, "Comma-separated example tags to include");
+ options.addOption(null, "exclude-tags", true, "Comma-separated example tags to exclude");
+ }
+
+ private static Set parseTags(String value) {
+ Set tags = new TreeSet<>();
+
+ if (value == null || value.isBlank()) {
+ return Set.of();
+ }
+
+ for (String tag : value.split(",")) {
+ String trimmed = tag.trim();
+
+ if (!trimmed.isEmpty()) {
+ tags.add(trimmed);
+ }
+ }
+
+ return Set.copyOf(tags);
}
@Override
@@ -124,6 +156,8 @@ public String toString() {
", namespace='" + namespace + '\'' +
", set='" + set + '\'' +
", useServicesAlternate=" + useServicesAlternate +
+ ", includeTags=" + includeTags +
+ ", excludeTags=" + excludeTags +
'}';
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/BatchExample.java b/examples/src/main/java/com/aerospike/examples/BatchExample.java
index 569104fd..d7644c9f 100644
--- a/examples/src/main/java/com/aerospike/examples/BatchExample.java
+++ b/examples/src/main/java/com/aerospike/examples/BatchExample.java
@@ -16,26 +16,19 @@
*/
package com.aerospike.examples;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
public class BatchExample extends Example {
- public BatchExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
- Session session = cluster.createSession(Behavior.DEFAULT);
+ public void runExample() throws Exception {
+ Session session = cluster().createSession(Behavior.DEFAULT);
System.out.println("*************");
System.out.println("* Batch tests");
System.out.println("*************");
- DataSet set = DataSet.of("test", "set");
-
- session.truncate(set);
+ DataSet set = dataSet();
System.out.println("Batch Insert:");
session.insert(set.ids(1,2,3,4,5))
diff --git a/examples/src/main/java/com/aerospike/examples/BehaviorHierarchicalExample.java b/examples/src/main/java/com/aerospike/examples/BehaviorHierarchicalExample.java
index 12925da2..b047cbfe 100644
--- a/examples/src/main/java/com/aerospike/examples/BehaviorHierarchicalExample.java
+++ b/examples/src/main/java/com/aerospike/examples/BehaviorHierarchicalExample.java
@@ -21,7 +21,6 @@
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.policy.Behavior.OpKind;
import com.aerospike.client.sdk.policy.Behavior.OpShape;
@@ -53,16 +52,12 @@ public class BehaviorHierarchicalExample extends Example {
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
private static final String DEFAULT_CONFIG_PATH = "src/main/resources/behavior-hierarchical-example.yml";
- public BehaviorHierarchicalExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
+ public void runExample() throws Exception {
demonstrateDynamicReloading(DEFAULT_CONFIG_PATH);
}
- private void demonstrateDynamicReloading(String yamlFilePath) {
+ private void demonstrateDynamicReloading(String yamlFilePath) throws IOException {
int refreshDelayMs = 5000;
int reloadDelayMs = 2000;
console.write("=== Behavior Hierarchical Example with Dynamic Reloading ===\n");
@@ -81,21 +76,19 @@ private void demonstrateDynamicReloading(String yamlFilePath) {
console.write("\n" + "=".repeat(70));
console.write("Monitoring for changes... Modify the YAML file to see dynamic reloading.");
- console.write("Settings will refresh every 5 seconds. Press Ctrl+C to stop.");
+ console.write("Settings refresh once in the example runner. Re-run locally to observe file changes.");
console.write("=".repeat(70));
- // Keep running and show current settings periodically
- while (true) {
- Thread.sleep(refreshDelayMs);
- console.write("\n" + getTimestamp() + " === Current Configuration ===");
- displayCurrentSettings();
- }
+ Thread.sleep(refreshDelayMs);
+ console.write("\n" + getTimestamp() + " === Current Configuration ===");
+ displayCurrentSettings();
} catch (IOException e) {
console.error("Error starting monitoring: " + e.getMessage());
- e.printStackTrace();
+ throw e;
} catch (InterruptedException e) {
console.write("\nMonitoring interrupted");
+ Thread.currentThread().interrupt();
}
console.write("Monitoring stopped");
}
diff --git a/examples/src/main/java/com/aerospike/examples/BehaviorYamlExample.java b/examples/src/main/java/com/aerospike/examples/BehaviorYamlExample.java
index ea448dab..62901731 100644
--- a/examples/src/main/java/com/aerospike/examples/BehaviorYamlExample.java
+++ b/examples/src/main/java/com/aerospike/examples/BehaviorYamlExample.java
@@ -20,7 +20,7 @@
import java.io.IOException;
import java.util.Set;
-import com.aerospike.client.sdk.Cluster;
+
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.policy.ResolvedSettings;
@@ -38,12 +38,8 @@
*/
public class BehaviorYamlExample extends Example {
- public BehaviorYamlExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
+ public void runExample() throws Exception {
String yamlFilePath = "src/main/resources/behavior-example.yml";
try (Closeable monitor = Behavior.startMonitoringWithResource(yamlFilePath)) {
@@ -130,9 +126,7 @@ public void runExample(Cluster cluster, Args args) throws Exception {
} catch (IOException e) {
console.error("Error loading behavior from YAML: " + e.getMessage());
- e.printStackTrace();
- } catch (InterruptedException e) {
- console.write("Interrupted");
+ throw e;
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/CdtPathExpressionExample.java b/examples/src/main/java/com/aerospike/examples/CdtPathExpressionExample.java
index f2673e66..e61f477a 100644
--- a/examples/src/main/java/com/aerospike/examples/CdtPathExpressionExample.java
+++ b/examples/src/main/java/com/aerospike/examples/CdtPathExpressionExample.java
@@ -33,26 +33,28 @@
import com.aerospike.client.sdk.exp.LoopVarPart;
import com.aerospike.client.sdk.exp.MapExp;
import com.aerospike.client.sdk.policy.Behavior;
+import com.aerospike.client.sdk.util.Version;
/**
* Demonstrates fluent CDT path helpers: {@code onEachChild}, {@code collectValues},
* {@code modifyBy}, {@code removeMatches}, and {@code collectValuesAsExpressionRead}.
*
* These features require Aerospike Server 8.1.1 or later ({@code selectByPath} /
- * {@code modifyByPath}). If the cluster is older, operations may fail with a server error.
+ * {@code modifyByPath}). If the cluster is older, this example is skipped.
*/
public class CdtPathExpressionExample extends Example {
- public CdtPathExpressionExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
- Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet set = DataSet.of(args.namespace, "cdt-path-demo");
+ public void runExample() throws Exception {
+ Cluster cluster = cluster();
+ Version version = cluster.getRandomNode().getVersion();
+ if (!version.isGreaterOrEqual(8, 1, 1, 0)) {
+ throw new ExampleSkipException(
+ "server is " + version + "; CDT path expressions require 8.1.1+");
+ }
- session.truncate(set);
+ Session session = cluster.createSession(Behavior.DEFAULT);
+ DataSet set = dataSet("cdt-path-demo");
boolean allOk = true;
@@ -156,7 +158,10 @@ public void runExample(Cluster cluster, Args args) throws Exception {
"The Lord of the Rings");
allOk &= reportCheck(console, 5, stringListMatchesUnordered(rec.bins.get("catalog"), allTitles));
- console.info("Overall: " + (allOk ? "SUCCESS" : "*** FAILURE ***"));
+ if (!allOk) {
+ throw new AssertionError("One or more CDT path expression checks failed");
+ }
+ console.info("Overall: SUCCESS");
}
private static boolean reportCheck(Console console, int step, boolean ok) {
diff --git a/examples/src/main/java/com/aerospike/examples/CommonExample.java b/examples/src/main/java/com/aerospike/examples/CommonExample.java
index e2a72f69..7360f760 100644
--- a/examples/src/main/java/com/aerospike/examples/CommonExample.java
+++ b/examples/src/main/java/com/aerospike/examples/CommonExample.java
@@ -19,7 +19,6 @@
import java.util.Optional;
import com.aerospike.client.sdk.AerospikeException;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordResult;
@@ -41,10 +40,6 @@
* Examples for common commands.
*/
public class CommonExample extends Example {
- public CommonExample(Console console) {
- super(console);
- }
-
public void printRecordStream(String header, RecordStream rs) {
if (header != null) {
System.out.println("=".repeat(40));
@@ -64,13 +59,9 @@ public void printRecordStream(String header, RecordStream rs) {
}
}
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
- Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet set = DataSet.of("test", "set");
-
- System.out.println("Truncate records");
-
- session.truncate(set);
+ public void runExample() throws Exception {
+ Session session = cluster().createSession(Behavior.DEFAULT);
+ DataSet set = dataSet();
System.out.println("Write 1 record");
@@ -284,7 +275,7 @@ public void runExample(Cluster cluster, Args args) throws Exception {
System.out.println("Create index");
- session.createIndex(set, "ageidx", "age", IndexType.INTEGER, IndexCollectionType.DEFAULT);
+ session.createIndex(set, "ageidx", "age", IndexType.INTEGER, IndexCollectionType.DEFAULT).waitTillComplete();
System.out.println("Foreground secondary index query");
@@ -372,7 +363,7 @@ public void runExample(Cluster cluster, Args args) throws Exception {
session.query(set).withHint(hint -> hint.queryDuration(QueryDuration.LONG)).recordsPerSecond(20).execute();
- session.backgroundTask().update(set).bin("age").add(1).recordsPerSecond(35).execute();
+ session.backgroundTask().update(set).bin("age").add(1).recordsPerSecond(35).execute().waitTillComplete();
// Exp operations - read and write.
System.out.println("Read and write operation example");
diff --git a/examples/src/main/java/com/aerospike/examples/CompleteYamlConfigExample.java b/examples/src/main/java/com/aerospike/examples/CompleteYamlConfigExample.java
index 7e995567..5f5b8da8 100644
--- a/examples/src/main/java/com/aerospike/examples/CompleteYamlConfigExample.java
+++ b/examples/src/main/java/com/aerospike/examples/CompleteYamlConfigExample.java
@@ -45,12 +45,8 @@ public class CompleteYamlConfigExample extends Example {
private static final String CONFIG_FILE = "src/main/resources/complete-config-example.yml";
- public CompleteYamlConfigExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
+ public void runExample() throws Exception {
console.write("=== Complete YAML Configuration Example ===\n");
try (Closeable monitor = Behavior.startMonitoringWithResource(CONFIG_FILE)) {
@@ -60,8 +56,8 @@ public void runExample(Cluster cluster, Args args) throws Exception {
displayAllBehaviors();
demonstrateBehaviorInheritance();
- if (args.host != null) {
- performClusterOperations(args);
+ if (host() != null) {
+ performClusterOperations();
} else {
console.write("\n=== Skipping Cluster Operations ===");
console.write("No host specified. Add -h to perform actual operations.\n");
@@ -69,9 +65,7 @@ public void runExample(Cluster cluster, Args args) throws Exception {
} catch (IOException e) {
console.error("Error loading configuration: " + e.getMessage());
- e.printStackTrace();
- } catch (InterruptedException e) {
- console.write("Interrupted");
+ throw e;
}
}
@@ -247,21 +241,21 @@ private void demonstrateBehaviorInheritance() {
console.write("");
}
- private void performClusterOperations(Args args) {
+ private void performClusterOperations() {
console.write("=== CLUSTER OPERATIONS ===\n");
- ClusterDefinition clusterDef = new ClusterDefinition(args.host, args.port)
+ ClusterDefinition clusterDef = new ClusterDefinition(host(), port())
.appId("complete-yaml-example")
.failIfNotConnected(true);
- if (args.useServicesAlternate) {
+ if (useServicesAlternate()) {
clusterDef.usingServicesAlternate();
}
try (Cluster cluster = clusterDef.connect()) {
- console.write("Connected to cluster at " + args.host + ":" + args.port + "\n");
+ console.write("Connected to cluster at " + host() + ":" + port() + "\n");
- DataSet dataSet = DataSet.of(args.namespace, "complete-yaml-demo");
+ DataSet dataSet = dataSet("complete-yaml-demo");
testWithBehavior(cluster, "high-performance", dataSet);
testWithBehavior(cluster, "high-reliability", dataSet);
@@ -272,7 +266,7 @@ private void performClusterOperations(Args args) {
} catch (Throwable t) {
console.error("Cluster operation failed: " + Util.getErrorMessage(t));
- t.printStackTrace();
+ throw new RuntimeException(t);
}
}
@@ -310,6 +304,7 @@ private void testWithBehavior(Cluster cluster, String behaviorName, DataSet data
} catch (Exception e) {
console.error(" Error: " + e.getMessage());
+ throw new RuntimeException(e);
}
console.write("");
diff --git a/examples/src/main/java/com/aerospike/examples/Example.java b/examples/src/main/java/com/aerospike/examples/Example.java
index eb58df0e..9d2ec706 100644
--- a/examples/src/main/java/com/aerospike/examples/Example.java
+++ b/examples/src/main/java/com/aerospike/examples/Example.java
@@ -17,7 +17,6 @@
package com.aerospike.examples;
import java.io.File;
-import java.lang.reflect.Constructor;
import java.util.List;
import org.apache.commons.cli.CommandLine;
@@ -28,24 +27,26 @@
import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
+import com.aerospike.client.sdk.DataSet;
/**
* Abstract base class for all examples.
*
- * Concrete examples should extend this class and implement the {@link #runExample(Args)} method.
- * The base class handles example lifecycle (begin/end logging) and provides a console for output.
+ *
Concrete examples should extend this class and implement the {@link #runExample()} method.
+ * The base class provides access to runner-managed configuration. Lifecycle logging and the
+ * setup/verify/cleanup fixture flow are owned by {@link ExampleRunner}.
*/
public abstract class Example {
- private static final String PACKAGE_NAME = "com.aerospike.examples.";
-
protected Console console;
+ private ExampleContext context;
- public Example(Console console) {
- this.console = console;
+ void initialize(ExampleContext context) {
+ this.context = context;
+ this.console = context.console();
}
/**
- * Run one or more examples.
+ * Run one or more examples through the fixture and reporting-aware runner.
*
* @param console the console for output
* @param args configuration parameters
@@ -53,25 +54,18 @@ public Example(Console console) {
* @throws Exception if an example fails
*/
public static void runExamples(Console console, Args args, List examples) throws Exception {
- ClusterDefinition def = clusterDefinition(args);
-
- Cluster cluster = def.connect();
-
- try {
- for (String exampleName : examples) {
- runExample(exampleName, cluster, args, console);
- }
- }
- finally {
- cluster.close();
- }
+ ExampleRunResult result = new ExampleRunner(console, args).run(examples);
+
+ if (result.exitCode() != 0) {
+ throw new IllegalStateException("One or more examples failed");
+ }
}
private static String resolvePath(String dir, String path) {
File file = new File(path);
if (file.isAbsolute()) {
- return path;
+ return path;
}
file = new File(dir, path);
@@ -97,7 +91,7 @@ public static Args parseStandaloneArgs(String[] argv) throws ParseException {
/**
* Build a {@link ClusterDefinition} with the same baseline settings as {@link #runExamples}
- * (log level, connection pool sizing, optional TLS, optional services alternate from {@code -a}).
+ * (connection pool sizing, optional TLS, optional services alternate from {@code -a}).
* Callers may chain further options (credentials, racks, …) before {@link ClusterDefinition#connect()}.
*
* @param args parsed example arguments
@@ -141,48 +135,49 @@ public static ClusterDefinition clusterDefinition(Args args) {
}
/**
- * Run a single example by name using reflection.
+ * Initialize this example with the runner-managed context and run its body.
*
- * @param exampleName the simple name of the example class
- * @param cluster cluster
- * @param args configuration parameters
- * @param console the console for output
- * @throws Exception if the example fails or cannot be found
+ * @param context runner-managed example context
+ * @throws Exception if the example fails
*/
- public static void runExample(
- String exampleName, Cluster cluster, Args args, Console console
- ) throws Exception {
- String fullName = PACKAGE_NAME + exampleName;
- Class> cls = Class.forName(fullName);
-
- if (Example.class.isAssignableFrom(cls)) {
- Constructor> ctor = cls.getDeclaredConstructor(Console.class);
- Example example = (Example) ctor.newInstance(console);
- example.run(cluster, args);
- } else {
- console.error("Invalid example: " + exampleName);
- }
+ public void run(ExampleContext context) throws Exception {
+ initialize(context);
+ runExample();
}
- /**
- * Run this example with lifecycle logging.
- *
- * @param cluster cluster
- * @param args configuration parameters
- * @throws Exception if the example fails
- */
- public void run(Cluster cluster, Args args) throws Exception {
- console.info(this.getClass().getSimpleName() + " Begin");
- runExample(cluster, args);
- console.info(this.getClass().getSimpleName() + " End");
+ protected Cluster cluster() {
+ return context.cluster();
+ }
+
+ protected String namespace() {
+ return context.args().namespace;
+ }
+
+ protected String host() {
+ return context.args().host;
+ }
+
+ protected int port() {
+ return context.args().port;
+ }
+
+ protected boolean useServicesAlternate() {
+ return context.args().useServicesAlternate;
+ }
+
+ protected DataSet dataSet() {
+ return context.dataSet();
+ }
+
+ protected DataSet dataSet(String set) {
+ return context.dataSet(set);
}
/**
* Run the example logic. Subclasses must implement this method.
*
- * @param params configuration parameters
* @throws Exception if the example fails
*/
- public abstract void runExample(Cluster cluster, Args args) throws Exception;
+ public abstract void runExample() throws Exception;
}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleContext.java b/examples/src/main/java/com/aerospike/examples/ExampleContext.java
new file mode 100644
index 00000000..023d417e
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleContext.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+import com.aerospike.client.sdk.Cluster;
+import com.aerospike.client.sdk.DataSet;
+import com.aerospike.client.sdk.Session;
+import com.aerospike.client.sdk.policy.Behavior;
+
+/**
+ * Shared state for an example run.
+ */
+public class ExampleContext {
+ private final Cluster cluster;
+ private final Args args;
+ private final Console console;
+
+ public ExampleContext(Cluster cluster, Args args, Console console) {
+ this.cluster = cluster;
+ this.args = args;
+ this.console = console;
+ }
+
+ public Cluster cluster() {
+ return cluster;
+ }
+
+ public Args args() {
+ return args;
+ }
+
+ public Console console() {
+ return console;
+ }
+
+ public Session session() {
+ return cluster.createSession(Behavior.DEFAULT);
+ }
+
+ public DataSet dataSet() {
+ return DataSet.of(args.namespace, args.set);
+ }
+
+ public DataSet dataSet(String set) {
+ return DataSet.of(args.namespace, set);
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleDefinition.java b/examples/src/main/java/com/aerospike/examples/ExampleDefinition.java
new file mode 100644
index 00000000..f8c06727
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleDefinition.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+import java.util.Set;
+
+/**
+ * Registry metadata for one runnable example.
+ */
+public record ExampleDefinition(
+ String name,
+ Class extends Example> exampleClass,
+ ExampleFixture fixture,
+ Set tags
+) {
+ public ExampleDefinition {
+ fixture = fixture == null ? ExampleFixture.NONE : fixture;
+ tags = tags == null ? Set.of() : Set.copyOf(tags);
+ }
+
+ public boolean hasTag(String tag) {
+ return tags.contains(tag);
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleFixture.java b/examples/src/main/java/com/aerospike/examples/ExampleFixture.java
new file mode 100644
index 00000000..95ba506e
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleFixture.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+/**
+ * Hooks that keep setup, verification, and cleanup outside doc-facing examples.
+ */
+public interface ExampleFixture {
+ ExampleFixture NONE = new ExampleFixture() {
+ };
+
+ default void setup(ExampleContext context) throws Exception {
+ }
+
+ default void verify(ExampleContext context) throws Exception {
+ }
+
+ default void cleanup(ExampleContext context) throws Exception {
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleRegistry.java b/examples/src/main/java/com/aerospike/examples/ExampleRegistry.java
new file mode 100644
index 00000000..52144af8
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleRegistry.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import com.aerospike.examples.ecommerce.EcommerceExample;
+import com.aerospike.examples.fixtures.ExampleFixtures;
+
+public final class ExampleRegistry {
+ private static final Map EXAMPLES = createExamples();
+
+ private ExampleRegistry() {
+ }
+
+ public static Collection all() {
+ return EXAMPLES.values();
+ }
+
+ public static Optional find(String name) {
+ return Optional.ofNullable(EXAMPLES.get(name));
+ }
+
+ public static String[] names() {
+ return EXAMPLES.keySet().toArray(String[]::new);
+ }
+
+ private static Map createExamples() {
+ Map examples = new LinkedHashMap<>();
+
+ register(examples, "CommonExample", CommonExample.class, ExampleFixtures.commonExample(), "smoke", "records");
+ register(examples, "BatchExample", BatchExample.class, ExampleFixtures.batchExample(), "smoke", "records");
+ register(examples, "BehaviorHierarchicalExample", BehaviorHierarchicalExample.class, ExampleFixture.NONE, "config");
+ register(examples, "BehaviorYamlExample", BehaviorYamlExample.class, ExampleFixture.NONE, "config");
+ register(
+ examples,
+ "CompleteYamlConfigExample",
+ CompleteYamlConfigExample.class,
+ ExampleFixtures.completeYamlConfigExample(),
+ "config",
+ "records");
+ register(examples, "YamlConfigExample", YamlConfigExample.class, ExampleFixture.NONE, "config");
+ register(
+ examples,
+ "YamlConfigConnectionExample",
+ YamlConfigConnectionExample.class,
+ ExampleFixtures.yamlConfigConnectionExample(),
+ "smoke",
+ "config",
+ "records");
+ register(examples, "StudentScoresExample", StudentScoresExample.class, ExampleFixtures.studentScoresExample(), "smoke", "records");
+ register(
+ examples,
+ "MapRemoveByKeyRangeTest",
+ MapRemoveByKeyRangeTest.class,
+ ExampleFixtures.mapRemoveByKeyRangeTest(),
+ "smoke",
+ "records",
+ "ael");
+ register(
+ examples,
+ "TransactionProcessingExample",
+ TransactionProcessingExample.class,
+ ExampleFixtures.transactionProcessingExample(),
+ "smoke",
+ "records");
+ register(examples, "AelTestSpecRunner", AelTestSpecRunner.class, ExampleFixtures.aelTestSpecRunner(), "ael", "records");
+ register(
+ examples,
+ "OperationDifferences",
+ OperationDifferences.class,
+ ExampleFixtures.operationDifferences(),
+ "diagnostic",
+ "ael",
+ "records");
+ register(
+ examples,
+ "CdtPathExpressionExample",
+ CdtPathExpressionExample.class,
+ ExampleFixtures.cdtPathExpressionExample(),
+ "extended",
+ "records",
+ "server-specific");
+ register(
+ examples,
+ "StringOperationsExample",
+ StringOperationsExample.class,
+ ExampleFixtures.stringOperationsExample(),
+ "extended",
+ "records",
+ "server-specific");
+ register(
+ examples,
+ "TypedMappingExamples",
+ TypedMappingExamples.class,
+ ExampleFixtures.typedMappingExamples(),
+ "extended",
+ "mapping",
+ "records");
+ register(examples, "QueryExamples", QueryExamples.class, ExampleFixtures.queryExamples(), "extended", "records");
+ register(examples, "EcommerceExample", EcommerceExample.class, ExampleFixtures.ecommerceExample(), "extended", "records");
+ register(examples, "RosterExample", RosterExample.class, ExampleFixture.NONE, "server-specific");
+
+ return Collections.unmodifiableMap(examples);
+ }
+
+ private static void register(
+ Map examples,
+ String name,
+ Class extends Example> exampleClass,
+ ExampleFixture fixture,
+ String... tags
+ ) {
+ examples.put(name, new ExampleDefinition(name, exampleClass, fixture, Set.copyOf(List.of(tags))));
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleResult.java b/examples/src/main/java/com/aerospike/examples/ExampleResult.java
new file mode 100644
index 00000000..9c054e21
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+public record ExampleResult(
+ String name,
+ ExampleStatus status,
+ long durationMillis,
+ String message,
+ Throwable error
+) {
+ public static ExampleResult passed(String name, long durationMillis) {
+ return new ExampleResult(name, ExampleStatus.PASSED, durationMillis, null, null);
+ }
+
+ public static ExampleResult skipped(String name, long durationMillis, String message) {
+ return new ExampleResult(name, ExampleStatus.SKIPPED, durationMillis, message, null);
+ }
+
+ public static ExampleResult failed(String name, long durationMillis, Throwable error) {
+ return new ExampleResult(name, ExampleStatus.FAILED, durationMillis, error.getMessage(), error);
+ }
+
+ public boolean failed() {
+ return status == ExampleStatus.FAILED;
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleRunResult.java b/examples/src/main/java/com/aerospike/examples/ExampleRunResult.java
new file mode 100644
index 00000000..df0e84ef
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleRunResult.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+import java.util.List;
+
+public record ExampleRunResult(List results) {
+ public ExampleRunResult {
+ results = List.copyOf(results);
+ }
+
+ public boolean hasFailures() {
+ return results.stream().anyMatch(ExampleResult::failed);
+ }
+
+ public int exitCode() {
+ return hasFailures() ? 1 : 0;
+ }
+
+ public long passedCount() {
+ return count(ExampleStatus.PASSED);
+ }
+
+ public long failedCount() {
+ return count(ExampleStatus.FAILED);
+ }
+
+ public long skippedCount() {
+ return count(ExampleStatus.SKIPPED);
+ }
+
+ private long count(ExampleStatus status) {
+ return results.stream().filter(result -> result.status() == status).count();
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleRunner.java b/examples/src/main/java/com/aerospike/examples/ExampleRunner.java
new file mode 100644
index 00000000..7c8248db
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleRunner.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.aerospike.client.sdk.Cluster;
+import com.aerospike.client.sdk.ClusterDefinition;
+
+public class ExampleRunner {
+ private final Console console;
+ private final Args args;
+
+ public ExampleRunner(Console console, Args args) {
+ this.console = console;
+ this.args = args;
+ }
+
+ public ExampleRunResult run(List exampleNames) throws Exception {
+ List definitions = resolveExamples(exampleNames);
+ List results = new ArrayList<>();
+
+ try (Cluster cluster = createCluster()) {
+ ExampleContext context = new ExampleContext(cluster, args, console);
+
+ for (ExampleDefinition definition : definitions) {
+ ExampleResult result = runOne(definition, context);
+ results.add(result);
+
+ if (args.failFast && result.failed()) {
+ break;
+ }
+ }
+ }
+
+ ExampleRunResult runResult = new ExampleRunResult(results);
+ logSummary(runResult);
+ writeReport(runResult);
+ return runResult;
+ }
+
+ private List resolveExamples(List exampleNames) {
+ List definitions = new ArrayList<>();
+
+ for (String name : exampleNames) {
+ ExampleDefinition definition = ExampleRegistry.find(name)
+ .orElseThrow(() -> new IllegalArgumentException("Unknown example: " + name));
+
+ if (matchesTags(definition)) {
+ definitions.add(definition);
+ }
+ else {
+ console.info("Skipping %s because it does not match the tag filters", definition.name());
+ }
+ }
+
+ return definitions;
+ }
+
+ private boolean matchesTags(ExampleDefinition definition) {
+ if (!args.includeTags.isEmpty() && args.includeTags.stream().noneMatch(definition::hasTag)) {
+ return false;
+ }
+
+ return args.excludeTags.isEmpty() || args.excludeTags.stream().noneMatch(definition::hasTag);
+ }
+
+ private ExampleResult runOne(ExampleDefinition definition, ExampleContext context) {
+ long start = System.nanoTime();
+ ExampleResult result;
+ console.info(definition.name() + " Begin");
+
+ try {
+ definition.fixture().setup(context);
+ instantiate(definition).run(context);
+ definition.fixture().verify(context);
+ console.info(definition.name() + " Passed");
+ result = ExampleResult.passed(definition.name(), elapsedMillis(start));
+ }
+ catch (ExampleSkipException e) {
+ console.warn("%s Skipped: %s", definition.name(), e.getMessage());
+ result = ExampleResult.skipped(definition.name(), elapsedMillis(start), e.getMessage());
+ }
+ catch (Throwable t) {
+ console.error("%s Failed: %s", definition.name(), t.getMessage());
+ result = ExampleResult.failed(definition.name(), elapsedMillis(start), t);
+ }
+ finally {
+ console.info(definition.name() + " End");
+ }
+
+ try {
+ definition.fixture().cleanup(context);
+ }
+ catch (Throwable t) {
+ console.error("%s Cleanup failed: %s", definition.name(), t.getMessage());
+
+ // Only a passing example is downgraded by a cleanup failure. A skip or an
+ // existing failure keeps its original, more meaningful result.
+ if (result.status() == ExampleStatus.PASSED) {
+ result = ExampleResult.failed(definition.name(), elapsedMillis(start), t);
+ }
+ }
+
+ return result;
+ }
+
+ private Example instantiate(ExampleDefinition definition) throws Exception {
+ return definition.exampleClass().getDeclaredConstructor().newInstance();
+ }
+
+ private Cluster createCluster() {
+ ClusterDefinition def = new ClusterDefinition(args.host, args.port)
+ .clusterName(args.clusterName)
+ .withSystemSettings(builder -> builder
+ .circuitBreaker(ops -> ops.maximumErrorsInErrorWindow(200))
+ .connections(conn -> conn
+ .minimumConnectionsPerNode(200)
+ .maximumConnectionsPerNode(200)
+ )
+ );
+
+ if (args.tlsName != null) {
+ String certHome = System.getenv("CERT_HOME");
+
+ if (certHome == null) {
+ certHome = "";
+ }
+
+ String caFile = resolvePath(certHome, args.caFile);
+ String clientCertFile = resolvePath(certHome, args.clientCertFile);
+ String clientKeyFile = resolvePath(certHome, args.clientKeyFile);
+
+ def.withTlsConfig(tls -> tls
+ .tlsName(args.tlsName)
+ .caFile(caFile)
+ .clientCertFile(clientCertFile)
+ .clientKeyFile(clientKeyFile)
+ );
+ }
+
+ return def.connect();
+ }
+
+ private static String resolvePath(String dir, String path) {
+ File file = new File(path);
+
+ if (file.isAbsolute()) {
+ return path;
+ }
+
+ file = new File(dir, path);
+ return file.getAbsolutePath();
+ }
+
+ private void logSummary(ExampleRunResult runResult) {
+ console.info(
+ "Examples complete: %d passed, %d failed, %d skipped",
+ runResult.passedCount(),
+ runResult.failedCount(),
+ runResult.skippedCount());
+ }
+
+ private void writeReport(ExampleRunResult runResult) throws Exception {
+ if (args.reportPath == null || args.reportPath.isBlank()) {
+ return;
+ }
+
+ Path reportPath = Path.of(args.reportPath);
+ JUnitXmlReportWriter.write(reportPath, runResult);
+ console.info("Wrote example report to " + reportPath);
+ }
+
+ private static long elapsedMillis(long startNanos) {
+ return (System.nanoTime() - startNanos) / 1_000_000L;
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleSkipException.java b/examples/src/main/java/com/aerospike/examples/ExampleSkipException.java
new file mode 100644
index 00000000..262943ee
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleSkipException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+/**
+ * Signals that an example is not applicable to the current server/configuration.
+ */
+public class ExampleSkipException extends Exception {
+ public ExampleSkipException(String message) {
+ super(message);
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/ExampleStatus.java b/examples/src/main/java/com/aerospike/examples/ExampleStatus.java
new file mode 100644
index 00000000..fa82772d
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/ExampleStatus.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+public enum ExampleStatus {
+ PASSED,
+ FAILED,
+ SKIPPED
+}
diff --git a/examples/src/main/java/com/aerospike/examples/JUnitXmlReportWriter.java b/examples/src/main/java/com/aerospike/examples/JUnitXmlReportWriter.java
new file mode 100644
index 00000000..686da237
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/JUnitXmlReportWriter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012-2026 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.examples;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+public final class JUnitXmlReportWriter {
+ private JUnitXmlReportWriter() {
+ }
+
+ public static void write(Path reportPath, ExampleRunResult runResult) throws IOException {
+ Path parent = reportPath.getParent();
+
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+
+ List results = runResult.results();
+ long elapsedMillis = results.stream().mapToLong(ExampleResult::durationMillis).sum();
+
+ StringBuilder xml = new StringBuilder();
+ xml.append("\n");
+ xml.append("\n");
+
+ for (ExampleResult result : results) {
+ xml.append(" ");
+
+ if (result.status() == ExampleStatus.FAILED) {
+ xml.append("\n ");
+ xml.append(escape(stackTrace(result.error())));
+ xml.append("\n ");
+ }
+ else if (result.status() == ExampleStatus.SKIPPED) {
+ xml.append("\n \n ");
+ }
+
+ xml.append("\n");
+ }
+
+ xml.append("\n");
+ Files.writeString(reportPath, xml.toString(), StandardCharsets.UTF_8);
+ }
+
+ private static String seconds(long millis) {
+ return String.format("%.3f", millis / 1000.0);
+ }
+
+ private static String stackTrace(Throwable error) {
+ if (error == null) {
+ return "";
+ }
+
+ StringWriter sw = new StringWriter();
+ error.printStackTrace(new PrintWriter(sw));
+ return sw.toString();
+ }
+
+ private static String nullToEmpty(String value) {
+ return value == null ? "" : value;
+ }
+
+ private static String escape(String value) {
+ return value
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """)
+ .replace("'", "'");
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/Main.java b/examples/src/main/java/com/aerospike/examples/Main.java
index 20be2f26..63d67ca4 100644
--- a/examples/src/main/java/com/aerospike/examples/Main.java
+++ b/examples/src/main/java/com/aerospike/examples/Main.java
@@ -19,6 +19,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
@@ -37,37 +38,20 @@
* ./run_examples -u
* }
*
- * Available Examples
- *
- * - {@code BehaviorHierarchicalExample} - Demonstrates hierarchical YAML configuration with dynamic reloading
- * - {@code BehaviorYamlExample} - Demonstrates loading behaviors from YAML files
- * - {@code YamlConfigConnectionExample} - Demonstrates connecting to a cluster with YAML configuration
- * - {@code CdtPathExpressionExample} - Fluent CDT path iteration ({@code onEachChild}), collect, modify, and remove (server 8.1.1+)
- * - {@code StringOperationsExample} - String bin reads/modify via {@code BinBuilder}, {@code appendOperations}, and {@code StringExp} (server 8.1.3+)
- *
+ * The set of available examples is defined by {@link ExampleRegistry}; run with
+ * {@code -u} to print the current list.
*/
public class Main {
-
- private static final String[] EXAMPLE_NAMES = new String[] {
- "CommonExample",
- "BatchExample",
- "CdtPathExpressionExample",
- "StringOperationsExample",
- "BehaviorHierarchicalExample",
- "BehaviorYamlExample",
- "CompleteYamlConfigExample",
- "YamlConfigExample",
- "YamlConfigConnectionExample"
- };
-
public static String[] getAllExampleNames() {
- return EXAMPLE_NAMES;
+ return ExampleRegistry.names();
}
/**
* Main entry point.
*/
public static void main(String[] args) {
+ int exitCode = 1;
+
try {
Options options = new Options();
Args.addCommonOptions(options);
@@ -77,6 +61,7 @@ public static void main(String[] args) {
if (args.length == 0 || cl.hasOption("u")) {
logUsage(options);
+ exitCode = 0;
return;
}
@@ -85,23 +70,21 @@ public static void main(String[] args) {
if (exampleNames.length == 0) {
logUsage(options);
+ exitCode = 0;
return;
}
- // Check for "all"
- for (String exampleName : exampleNames) {
- if (exampleName.equalsIgnoreCase("all")) {
- exampleNames = EXAMPLE_NAMES;
- break;
- }
- }
-
Console console = new Console();
- runExamples(console, arguments, exampleNames);
+ exitCode = runExamples(console, arguments, exampleNames);
} catch (Exception ex) {
ex.printStackTrace();
}
+ finally {
+ if (exitCode != 0) {
+ System.exit(exitCode);
+ }
+ }
}
/**
@@ -116,7 +99,7 @@ private static void logUsage(Options options) {
System.out.println(sw.toString());
System.out.println("examples:");
- for (String name : EXAMPLE_NAMES) {
+ for (String name : ExampleRegistry.names()) {
System.out.println(" " + name);
}
System.out.println();
@@ -126,11 +109,19 @@ private static void logUsage(Options options) {
/**
* Run one or more examples.
*/
- public static void runExamples(Console console, Args args, String[] examples) throws Exception {
- List exampleList = new ArrayList<>();
+ public static int runExamples(Console console, Args args, String[] examples) throws Exception {
+ List exampleList = expandExamples(examples);
+ ExampleRunResult result = new ExampleRunner(console, args).run(exampleList);
+ return result.exitCode();
+ }
+
+ private static List expandExamples(String[] examples) {
for (String example : examples) {
- exampleList.add(example);
+ if (example.equalsIgnoreCase("all")) {
+ return Arrays.asList(ExampleRegistry.names());
+ }
}
- Example.runExamples(console, args, exampleList);
+
+ return new ArrayList<>(Arrays.asList(examples));
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/MapRemoveByKeyRangeTest.java b/examples/src/main/java/com/aerospike/examples/MapRemoveByKeyRangeTest.java
index ce02cfd7..749cba5b 100644
--- a/examples/src/main/java/com/aerospike/examples/MapRemoveByKeyRangeTest.java
+++ b/examples/src/main/java/com/aerospike/examples/MapRemoveByKeyRangeTest.java
@@ -18,7 +18,6 @@
import java.util.TreeMap;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordStream;
@@ -41,168 +40,158 @@
* This test creates a known map and calls removeByKeyRange as a read expression
* to see what is actually returned.
*/
-public class MapRemoveByKeyRangeTest {
-
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments).connect()) {
- Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet set = DataSet.of("test", "map_remove_test");
-
- session.truncate(set);
-
- // Seed: key-ordered map {a=1, b=2, c=3, d=4, e=5}
- TreeMap sourceMap = new TreeMap<>();
- sourceMap.put("a", 1L);
- sourceMap.put("b", 2L);
- sourceMap.put("c", 3L);
- sourceMap.put("d", 4L);
- sourceMap.put("e", 5L);
-
- session.upsert(set.id(1))
- .bin("m").setTo(sourceMap)
+public class MapRemoveByKeyRangeTest extends Example {
+
+ @Override
+ public void runExample() throws Exception {
+ Session session = cluster().createSession(Behavior.DEFAULT);
+ DataSet set = dataSet("map_remove_test");
+
+ // Seed: key-ordered map {a=1, b=2, c=3, d=4, e=5}
+ TreeMap sourceMap = new TreeMap<>();
+ sourceMap.put("a", 1L);
+ sourceMap.put("b", 2L);
+ sourceMap.put("c", 3L);
+ sourceMap.put("d", 4L);
+ sourceMap.put("e", 5L);
+
+ session.upsert(set.id(1))
+ .bin("m").setTo(sourceMap)
+ .execute();
+
+ System.out.println("Source map: " + sourceMap);
+ System.out.println();
+
+ // removeByKeyRange("b", "e") removes keys b, c, d (b inclusive, e exclusive)
+ // That is 3 items removed, leaving {a=1, e=5}
+
+ // --- Test 1: returnType = NONE ---
+ System.out.println("=== Test 1: MapExp.removeByKeyRange(NONE, \"b\", \"e\") ===");
+ System.out.println("Javadoc says NONE is valid. Class doc says modify expressions return the bin's value.");
+ System.out.println("Expected: the modified map {a=1, e=5}");
+ try {
+ Exp removeNone = MapExp.removeByKeyRange(
+ MapReturnType.NONE,
+ Exp.val("b"), Exp.val("e"),
+ Exp.mapBin("m"));
+
+ RecordStream rs = session.query(set.id(1))
+ .bin("result").selectFrom(removeNone)
.execute();
-
- System.out.println("Source map: " + sourceMap);
- System.out.println();
-
- // removeByKeyRange("b", "e") removes keys b, c, d (b inclusive, e exclusive)
- // That is 3 items removed, leaving {a=1, e=5}
-
- // --- Test 1: returnType = NONE ---
- System.out.println("=== Test 1: MapExp.removeByKeyRange(NONE, \"b\", \"e\") ===");
- System.out.println("Javadoc says NONE is valid. Class doc says modify expressions return the bin's value.");
- System.out.println("Expected: the modified map {a=1, e=5}");
- try {
- Exp removeNone = MapExp.removeByKeyRange(
- MapReturnType.NONE,
- Exp.val("b"), Exp.val("e"),
- Exp.mapBin("m"));
-
- RecordStream rs = session.query(set.id(1))
- .bin("result").selectFrom(removeNone)
- .execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- Object result = rec.bins.get("result");
- System.out.println("Actual: " + result);
- System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
- } catch (Exception e) {
- System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- System.out.println();
-
- // --- Test 2: returnType = INVERTED ---
- System.out.println("=== Test 2: MapExp.removeByKeyRange(INVERTED, \"b\", \"e\") ===");
- System.out.println("Javadoc says INVERTED is valid. Inverted means remove everything OUTSIDE the range.");
- System.out.println("Expected: remove {a=1, e=5} (outside range b..e), leaving {b=2, c=3, d=4}");
- try {
- Exp removeInverted = MapExp.removeByKeyRange(
- MapReturnType.INVERTED,
- Exp.val("b"), Exp.val("e"),
- Exp.mapBin("m"));
-
- RecordStream rs = session.query(set.id(1))
- .bin("result").selectFrom(removeInverted)
- .execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- Object result = rec.bins.get("result");
- System.out.println("Actual: " + result);
- System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
- } catch (Exception e) {
- System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- System.out.println();
-
- // --- Test 3: returnType = COUNT (Javadoc says this is NOT valid) ---
- System.out.println("=== Test 3: MapExp.removeByKeyRange(COUNT, \"b\", \"e\") ===");
- System.out.println("Javadoc says only NONE and INVERTED are valid.");
- System.out.println("If the doc is wrong, this might return the count of removed items (3).");
- try {
- Exp removeCount = MapExp.removeByKeyRange(
- MapReturnType.COUNT,
- Exp.val("b"), Exp.val("e"),
- Exp.mapBin("m"));
-
- RecordStream rs = session.query(set.id(1))
- .bin("result").selectFrom(removeCount)
- .execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- Object result = rec.bins.get("result");
- System.out.println("Actual: " + result);
- System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
- } catch (Exception e) {
- System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- System.out.println();
-
- // --- Test 4: returnType = KEY (Javadoc says this is NOT valid) ---
- System.out.println("=== Test 4: MapExp.removeByKeyRange(KEY, \"b\", \"e\") ===");
- System.out.println("If the doc is wrong, this might return the removed keys [b, c, d].");
- try {
- Exp removeKey = MapExp.removeByKeyRange(
- MapReturnType.KEY,
- Exp.val("b"), Exp.val("e"),
- Exp.mapBin("m"));
-
- RecordStream rs = session.query(set.id(1))
- .bin("result").selectFrom(removeKey)
- .execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- Object result = rec.bins.get("result");
- System.out.println("Actual: " + result);
- System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
- } catch (Exception e) {
- System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- System.out.println();
-
- // --- Test 5: returnType = VALUE (Javadoc says this is NOT valid) ---
- System.out.println("=== Test 5: MapExp.removeByKeyRange(VALUE, \"b\", \"e\") ===");
- System.out.println("If the doc is wrong, this might return the removed values [2, 3, 4].");
- try {
- Exp removeValue = MapExp.removeByKeyRange(
- MapReturnType.VALUE,
- Exp.val("b"), Exp.val("e"),
- Exp.mapBin("m"));
-
- RecordStream rs = session.query(set.id(1))
- .bin("result").selectFrom(removeValue)
- .execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- Object result = rec.bins.get("result");
- System.out.println("Actual: " + result);
- System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
- } catch (Exception e) {
- System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- System.out.println();
-
- // --- Test 6: returnType = KEY_VALUE (Javadoc says this is NOT valid) ---
- System.out.println("=== Test 6: MapExp.removeByKeyRange(KEY_VALUE, \"b\", \"e\") ===");
- System.out.println("If the doc is wrong, this might return the removed entries {b=2, c=3, d=4}.");
- try {
- Exp removeKV = MapExp.removeByKeyRange(
- MapReturnType.KEY_VALUE,
- Exp.val("b"), Exp.val("e"),
- Exp.mapBin("m"));
-
- RecordStream rs = session.query(set.id(1))
- .bin("result").selectFrom(removeKV)
- .execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- Object result = rec.bins.get("result");
- System.out.println("Actual: " + result);
- System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
- } catch (Exception e) {
- System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
- System.out.println();
-
- // Verify original record is unchanged (read expressions don't mutate)
- System.out.println("=== Verify original map is unchanged ===");
- RecordStream rs = session.query(set.id(1)).execute();
- Record rec = rs.getFirst().orElseThrow().recordOrThrow();
- System.out.println("Original map after all tests: " + rec.bins.get("m"));
+ Object result = rs.getFirst().orElseThrow().recordOrThrow().bins.get("result");
+ System.out.println("Actual: " + result);
+ System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ System.out.println();
+
+ // --- Test 2: returnType = INVERTED ---
+ System.out.println("=== Test 2: MapExp.removeByKeyRange(INVERTED, \"b\", \"e\") ===");
+ System.out.println("Javadoc says INVERTED is valid. Inverted means remove everything OUTSIDE the range.");
+ System.out.println("Expected: remove {a=1, e=5} (outside range b..e), leaving {b=2, c=3, d=4}");
+ try {
+ Exp removeInverted = MapExp.removeByKeyRange(
+ MapReturnType.INVERTED,
+ Exp.val("b"), Exp.val("e"),
+ Exp.mapBin("m"));
+
+ RecordStream rs = session.query(set.id(1))
+ .bin("result").selectFrom(removeInverted)
+ .execute();
+ Object result = rs.getFirst().orElseThrow().recordOrThrow().bins.get("result");
+ System.out.println("Actual: " + result);
+ System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ System.out.println();
+
+ // --- Test 3: returnType = COUNT (Javadoc says this is NOT valid) ---
+ System.out.println("=== Test 3: MapExp.removeByKeyRange(COUNT, \"b\", \"e\") ===");
+ System.out.println("Javadoc says only NONE and INVERTED are valid.");
+ System.out.println("If the doc is wrong, this might return the count of removed items (3).");
+ try {
+ Exp removeCount = MapExp.removeByKeyRange(
+ MapReturnType.COUNT,
+ Exp.val("b"), Exp.val("e"),
+ Exp.mapBin("m"));
+
+ RecordStream rs = session.query(set.id(1))
+ .bin("result").selectFrom(removeCount)
+ .execute();
+ Object result = rs.getFirst().orElseThrow().recordOrThrow().bins.get("result");
+ System.out.println("Actual: " + result);
+ System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ System.out.println();
+
+ // --- Test 4: returnType = KEY (Javadoc says this is NOT valid) ---
+ System.out.println("=== Test 4: MapExp.removeByKeyRange(KEY, \"b\", \"e\") ===");
+ System.out.println("If the doc is wrong, this might return the removed keys [b, c, d].");
+ try {
+ Exp removeKey = MapExp.removeByKeyRange(
+ MapReturnType.KEY,
+ Exp.val("b"), Exp.val("e"),
+ Exp.mapBin("m"));
+
+ RecordStream rs = session.query(set.id(1))
+ .bin("result").selectFrom(removeKey)
+ .execute();
+ Object result = rs.getFirst().orElseThrow().recordOrThrow().bins.get("result");
+ System.out.println("Actual: " + result);
+ System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ System.out.println();
+
+ // --- Test 5: returnType = VALUE (Javadoc says this is NOT valid) ---
+ System.out.println("=== Test 5: MapExp.removeByKeyRange(VALUE, \"b\", \"e\") ===");
+ System.out.println("If the doc is wrong, this might return the removed values [2, 3, 4].");
+ try {
+ Exp removeValue = MapExp.removeByKeyRange(
+ MapReturnType.VALUE,
+ Exp.val("b"), Exp.val("e"),
+ Exp.mapBin("m"));
+
+ RecordStream rs = session.query(set.id(1))
+ .bin("result").selectFrom(removeValue)
+ .execute();
+ Object result = rs.getFirst().orElseThrow().recordOrThrow().bins.get("result");
+ System.out.println("Actual: " + result);
+ System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
+ System.out.println();
+
+ // --- Test 6: returnType = KEY_VALUE (Javadoc says this is NOT valid) ---
+ System.out.println("=== Test 6: MapExp.removeByKeyRange(KEY_VALUE, \"b\", \"e\") ===");
+ System.out.println("If the doc is wrong, this might return the removed entries {b=2, c=3, d=4}.");
+ try {
+ Exp removeKV = MapExp.removeByKeyRange(
+ MapReturnType.KEY_VALUE,
+ Exp.val("b"), Exp.val("e"),
+ Exp.mapBin("m"));
+
+ RecordStream rs = session.query(set.id(1))
+ .bin("result").selectFrom(removeKV)
+ .execute();
+ Object result = rs.getFirst().orElseThrow().recordOrThrow().bins.get("result");
+ System.out.println("Actual: " + result);
+ System.out.println("Type: " + (result == null ? "null" : result.getClass().getName()));
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ System.out.println();
+
+ // Verify original record is unchanged (read expressions don't mutate)
+ System.out.println("=== Verify original map is unchanged ===");
+ RecordStream rs = session.query(set.id(1)).execute();
+ Record rec = rs.getFirst().orElseThrow().recordOrThrow();
+ System.out.println("Original map after all tests: " + rec.bins.get("m"));
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/OperationDifferences.java b/examples/src/main/java/com/aerospike/examples/OperationDifferences.java
index 6d4af954..5214ae16 100644
--- a/examples/src/main/java/com/aerospike/examples/OperationDifferences.java
+++ b/examples/src/main/java/com/aerospike/examples/OperationDifferences.java
@@ -20,7 +20,6 @@
import java.util.List;
import java.util.Map;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordStream;
@@ -39,39 +38,37 @@
* 2e: Mutation path functions (sort, remove, etc.) silently ignored
*
*/
-public class OperationDifferences {
+public class OperationDifferences extends Example {
private static final String SEPARATOR = "=".repeat(70);
private static final String PASS = "PASS";
private static final String FAIL = "** FAIL **";
- private static int totalTests = 0;
- private static int failedTests = 0;
-
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments).connect()) {
- Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet set = DataSet.of("test", "ael_diff_test");
-
- session.truncate(set);
- setupTestData(session, set);
-
-// test2a_LetThenVsWithDo(session, set);
- test2b_NameIdentifierTooPermissive(session, set);
- test2c_RightShiftReversed(session, set);
- test2d_ExistsSilentlyIgnored(session, set);
- test2e_MutationOperationsIgnored(session, set);
- testCasting_AsIntAsFloat(session, set);
-
- System.out.println(SEPARATOR);
- System.out.printf("SUMMARY: %d/%d tests show spec/implementation differences%n",
- failedTests, totalTests);
- System.out.println(SEPARATOR);
- }
+ private int totalTests = 0;
+ private int failedTests = 0;
+
+ @Override
+ public void runExample() throws Exception {
+ Session session = cluster().createSession(Behavior.DEFAULT);
+ DataSet set = dataSet("ael_diff_test");
+
+ // The runner's fixture truncates ael_diff_test before this example runs.
+ setupTestData(session, set);
+
+// test2a_LetThenVsWithDo(session, set);
+ test2b_NameIdentifierTooPermissive(session, set);
+ test2c_RightShiftReversed(session, set);
+ test2d_ExistsSilentlyIgnored(session, set);
+ test2e_MutationOperationsIgnored(session, set);
+ testCasting_AsIntAsFloat(session, set);
+
+ System.out.println(SEPARATOR);
+ System.out.printf("SUMMARY: %d/%d tests show spec/implementation differences%n",
+ failedTests, totalTests);
+ System.out.println(SEPARATOR);
}
- static void setupTestData(Session session, DataSet set) {
+ void setupTestData(Session session, DataSet set) {
// Record 1: integer bin for shift operator tests
session.upsert(set.id(1))
.bin("intBin").setTo(-8)
@@ -123,7 +120,8 @@ static void setupTestData(Session session, DataSet set) {
// =========================================================================
// 2a: Spec says `let...then`, implementation uses `let...then`
// =========================================================================
- static void test2a_LetThenVsWithDo(Session session, DataSet set) {
+ @SuppressWarnings("unused")
+ void test2a_LetThenVsWithDo(Session session, DataSet set) {
System.out.println(SEPARATOR);
System.out.println("TEST 2a: Spec keyword 'let...then' vs implementation 'let...then'");
System.out.println(SEPARATOR);
@@ -176,7 +174,7 @@ static void test2a_LetThenVsWithDo(Session session, DataSet set) {
// 2b: NAME_IDENTIFIER allows digit-starting tokens, so $.m.1 uses
// string key "1" instead of integer key 1
// =========================================================================
- static void test2b_NameIdentifierTooPermissive(Session session, DataSet set) {
+ void test2b_NameIdentifierTooPermissive(Session session, DataSet set) {
System.out.println(SEPARATOR);
System.out.println("TEST 2b: NAME_IDENTIFIER treats bare integers as string map keys");
System.out.println(SEPARATOR);
@@ -232,7 +230,7 @@ static void test2b_NameIdentifierTooPermissive(Session session, DataSet set) {
// 2c: >> operator performs logical right shift (zero-fill) instead of
// arithmetic right shift (sign-preserving) as per spec and Java convention
// =========================================================================
- static void test2c_RightShiftReversed(Session session, DataSet set) {
+ void test2c_RightShiftReversed(Session session, DataSet set) {
System.out.println(SEPARATOR);
System.out.println("TEST 2c: >> operator semantics reversed");
System.out.println(SEPARATOR);
@@ -328,7 +326,7 @@ static void test2c_RightShiftReversed(Session session, DataSet set) {
// implement visitPathFunctionExists, so it silently falls through
// and the exists() call is dropped from the expression.
// =========================================================================
- static void test2d_ExistsSilentlyIgnored(Session session, DataSet set) {
+ void test2d_ExistsSilentlyIgnored(Session session, DataSet set) {
System.out.println(SEPARATOR);
System.out.println("TEST 2d: exists() path function silently ignored");
System.out.println(SEPARATOR);
@@ -419,7 +417,7 @@ static void test2d_ExistsSilentlyIgnored(Session session, DataSet set) {
// increment) are defined in the grammar but have no visitor
// implementation -- they are silently ignored or cause errors.
// =========================================================================
- static void test2e_MutationOperationsIgnored(Session session, DataSet set) {
+ void test2e_MutationOperationsIgnored(Session session, DataSet set) {
System.out.println(SEPARATOR);
System.out.println("TEST 2e: Mutation path functions silently ignored");
System.out.println(SEPARATOR);
@@ -540,7 +538,7 @@ static void test2e_MutationOperationsIgnored(Session session, DataSet set) {
// arithmetic. Both operands must be the same type; the spec provides
// asInt() and asFloat() path functions for explicit conversion.
// =========================================================================
- static void testCasting_AsIntAsFloat(Session session, DataSet set) {
+ void testCasting_AsIntAsFloat(Session session, DataSet set) {
System.out.println(SEPARATOR);
System.out.println("TEST: asInt() / asFloat() type casting for arithmetic");
System.out.println(SEPARATOR);
@@ -680,7 +678,7 @@ static void testCasting_AsIntAsFloat(Session session, DataSet set) {
System.out.println();
}
- private static void check(String testId, boolean passed, String description) {
+ private void check(String testId, boolean passed, String description) {
totalTests++;
String status;
if (passed) {
diff --git a/examples/src/main/java/com/aerospike/examples/QueryExamples.java b/examples/src/main/java/com/aerospike/examples/QueryExamples.java
index f61913a5..837e12ac 100644
--- a/examples/src/main/java/com/aerospike/examples/QueryExamples.java
+++ b/examples/src/main/java/com/aerospike/examples/QueryExamples.java
@@ -28,13 +28,11 @@
import com.aerospike.client.sdk.AerospikeException;
import com.aerospike.client.sdk.AerospikeException.BinOpInvalidException;
import com.aerospike.client.sdk.Cluster;
-import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.DefaultRecordMappingFactory;
import com.aerospike.client.sdk.ErrorStrategy;
import com.aerospike.client.sdk.Key;
import com.aerospike.client.sdk.Record;
-import com.aerospike.client.sdk.RecordMapper;
import com.aerospike.client.sdk.RecordResult;
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.ResultCode;
@@ -56,180 +54,23 @@
import com.aerospike.client.sdk.query.SortDir;
import com.aerospike.client.sdk.query.SortProperties;
import com.aerospike.client.sdk.task.ExecuteTask;
-import com.aerospike.client.sdk.util.MapUtil;
+import com.aerospike.examples.query.Address;
+import com.aerospike.examples.query.AddressMapper;
+import com.aerospike.examples.query.Customer;
+import com.aerospike.examples.query.CustomerMapper;
/**
- * Large runnable example suite for queries, batch, object mapping, and policies.
+ * Broad tour of the query, batch, CDT, expression and object-mapping surface of the SDK.
*
- * Run with the same host/port flags as other examples ({@link Args} via {@link Example#parseStandaloneArgs}):
- * default seed {@code localhost:3000}; {@code -h} / {@code -p} override host and port. Optional {@code -a}
- * enables services alternate (see {@link ClusterDefinition#usingServicesAlternate()}).
+ * The example intentionally exercises a wide range of operations end-to-end. To keep the
+ * narrative readable, each logical area is factored into its own {@code demonstrate...} method
+ * that shares the same {@link Session} and {@link TypedDataSet}. The data model
+ * ({@link Customer}, {@link Address} and their mappers) lives in the
+ * {@code com.aerospike.examples.query} package.
*/
-public class QueryExamples {
- public static class Address {
- private final long id;
- private final String line1;
- private final String city;
- private final String state;
- private final String country;
- private final String zipCode;
-
- public Address(long id, String line1, String city, String state, String country, String zipCode) {
- super();
- this.id = id;
- this.line1 = line1;
- this.city = city;
- this.state = state;
- this.country = country;
- this.zipCode = zipCode;
- }
-
- public long getId() {
- return id;
- }
- public String getLine1() {
- return line1;
- }
- public String getCity() {
- return city;
- }
-
- public String getState() {
- return state;
- }
- public String getCountry() {
- return country;
- }
- public String getZipCode() {
- return zipCode;
- }
- @Override
- public String toString() {
- return "Address [line1=" + line1 + ", city=" + city + ", state=" + state + ", country=" + country + ", zipCode="
- + zipCode + "]";
- }
- }
-
- public static class Customer {
- private long id;
- private String name;
- private int age;
- private Date dob;
- private Address address;
-
- public Customer() {
- super();
- }
- public Customer(long id, String name, int age, Date dob) {
- this(id, name, age, dob, null);
- }
- public Customer(long id, String name, int age, Date dob, Address address) {
- super();
- this.id = id;
- this.name = name;
- this.age = age;
- this.dob = dob;
- this.address = address;
- }
-
- public long getId() {
- return id;
- }
- public void setId(long id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public Date getDob() {
- return dob;
- }
- public void setDob(Date dob) {
- this.dob = dob;
- }
-
- public Address getAddress() {
- return address;
- }
- public void setAddress(Address address) {
- this.address = address;
- }
-
- @Override
- public String toString() {
- return "Customer [id=" + id + ", name=" + name + ", age=" + age + ", dob=" + dob + ", address=" + address + "]";
- }
- }
-
- public static class AddressMapper implements RecordMapper
{
-
- @Override
- public Address fromMap(Map map, Key recordKey, int generation) {
- Address result = new Address(
- MapUtil.asLong(map, "id"),
- MapUtil.asString(map, "line1"),
- MapUtil.asString(map, "city"),
- MapUtil.asString(map, "state"),
- MapUtil.asString(map, "country"),
- MapUtil.asString(map, "zip"));
- return result;
- }
-
- @Override
- public Map toMap(Address addr) {
- return MapUtil.buildMap()
- .add("line1", addr.getLine1())
- .add("city", addr.getCity())
- .add("state", addr.getState())
- .add("country", addr.getCountry())
- .add("zip", addr.getZipCode())
- .done();
- }
-
- @Override
- public Object id(Address element) {
- return element.getId();
- }
+@SuppressWarnings("unused")
+public class QueryExamples extends Example {
- }
-
- public static class CustomerMapper implements RecordMapper {
- @Override
- public Customer fromMap(Map map, Key recordKey, int generation) {
- Customer result = new Customer();
- result.setId(recordKey.userKey.toLong());
- result.setAge(MapUtil.asInt(map, "age"));
- result.setDob(MapUtil.asDateFromLong(map, "dob"));
- result.setName(MapUtil.asString(map, "name"));
- result.setAddress(MapUtil.asObjectFromMap(map, "address", new AddressMapper()));
- return result;
- }
-
- @Override
- public Map toMap(Customer customer) {
- return MapUtil.buildMap()
- .add("id", customer.getId())
- .add("age", customer.getAge())
- .addAsLong("dob", customer.getDob())
- .add("name", customer.getName())
- .add("address", customer.getAddress(), new AddressMapper())
- .done();
- }
-
- @Override
- public Object id(Customer customer) {
- return customer.getId();
- }
- }
public static void print(RecordStream recordStream) {
int count = 0;
while (recordStream.hasNext()) {
@@ -242,101 +83,127 @@ public static void print(TypedRecordStream> typedStream) {
print(typedStream.asUntypedRecordStream());
}
- @SuppressWarnings("unused")
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments)
-// .withNativeCredentials("admin", "password123")
-// .preferringRacks(1)
- .connect()) {
-
- CustomerMapper customerMapper = new CustomerMapper();
-
- cluster.setRecordMappingFactory(new DefaultRecordMappingFactory(Map.of(
- Customer.class, customerMapper,
- Address.class, new AddressMapper()
- )
- ));
-
- Behavior newBehavior = Behavior.DEFAULT.deriveWithChanges("newBehavior", builder ->
- builder.on(Selectors.all(), ops -> ops
- .waitForSocketResponseAfterCallFails(Duration.ofSeconds(3))
- .sendKey(true)
- )
- .on(Selectors.reads().ap(), ops -> ops
- .waitForCallToComplete(Duration.ofMillis(25))
- .abandonCallAfter(Duration.ofMillis(100))
- .maximumNumberOfCallAttempts(3)
- )
- .on(Selectors.reads().query(), ops -> ops
- .waitForCallToComplete(Duration.ofSeconds(2))
- .abandonCallAfter(Duration.ofSeconds(30))
- )
- .on(Selectors.reads().batch(), ops -> ops
- .maximumNumberOfCallAttempts(7)
- .allowInlineMemoryAccess(true)
- )
- );
- Behavior childBehavior = newBehavior.deriveWithChanges("child", builder ->
- builder.on(Selectors.writes().batch(), ops -> ops
- .allowInlineSsdAccess(true)
- .maxConcurrentNodes(5)
- )
- .on(Selectors.reads().ap(), ops -> ops
- .maximumNumberOfCallAttempts(8)
- )
- );
- Behavior nonExceptionBehvaior = Behavior.DEFAULT.deriveWithChanges("nonException", builder ->
- builder.on(Selectors.all(), ops -> ops.stackTraceOnException(false)));
-
- TypedDataSet customerDataSet = TypedDataSet.of("test", "person", Customer.class);
- TypedDataSet addressDataSet = TypedDataSet.of("test", "address", Address.class);
- // Untyped view of the same ns/set: Key-based reads return RecordStream (CDT / raw-bin demos).
- DataSet cdtDemoRecords = customerDataSet.asDataSet();
-// DataSet customerDataSet = DataSet.of("test", "person");
+ @Override
+ public void runExample() throws Exception {
+ Cluster cluster = cluster();
- Session session = cluster.createSession(newBehavior);
- Session sessionWithoutExceptions = cluster.createSession(nonExceptionBehvaior);
+ CustomerMapper customerMapper = new CustomerMapper();
- Set namespaces = session.info().namespaces();
- namespaces.forEach(ns -> {
- Optional details = session.info().namespaceDetails(ns);
- details.ifPresent(System.out::println);
- });
+ cluster.setRecordMappingFactory(new DefaultRecordMappingFactory(Map.of(
+ Customer.class, customerMapper,
+ Address.class, new AddressMapper()
+ )
+ ));
+
+ Behavior newBehavior = Behavior.DEFAULT.deriveWithChanges("newBehavior", builder ->
+ builder.on(Selectors.all(), ops -> ops
+ .waitForSocketResponseAfterCallFails(Duration.ofSeconds(3))
+ .sendKey(true)
+ )
+ .on(Selectors.reads().ap(), ops -> ops
+ .waitForCallToComplete(Duration.ofMillis(25))
+ .abandonCallAfter(Duration.ofMillis(100))
+ .maximumNumberOfCallAttempts(3)
+ )
+ .on(Selectors.reads().query(), ops -> ops
+ .waitForCallToComplete(Duration.ofSeconds(2))
+ .abandonCallAfter(Duration.ofSeconds(30))
+ )
+ .on(Selectors.reads().batch(), ops -> ops
+ .maximumNumberOfCallAttempts(7)
+ .allowInlineMemoryAccess(true)
+ )
+ );
+ Behavior childBehavior = newBehavior.deriveWithChanges("child", builder ->
+ builder.on(Selectors.writes().batch(), ops -> ops
+ .allowInlineSsdAccess(true)
+ .maxConcurrentNodes(5)
+ )
+ .on(Selectors.reads().ap(), ops -> ops
+ .maximumNumberOfCallAttempts(8)
+ )
+ );
+ Behavior nonExceptionBehvaior = Behavior.DEFAULT.deriveWithChanges("nonException", builder ->
+ builder.on(Selectors.all(), ops -> ops.stackTraceOnException(false)));
+
+ TypedDataSet customerDataSet = TypedDataSet.of(namespace(), "person", Customer.class);
+ TypedDataSet addressDataSet = TypedDataSet.of(namespace(), "address", Address.class);
+ DataSet cdtDemoRecords = customerDataSet.asDataSet();
+// DataSet customerDataSet = dataSet("person");
+
+ Session session = cluster.createSession(newBehavior);
+ Session sessionWithoutExceptions = cluster.createSession(nonExceptionBehvaior);
+
+ DataSet users = dataSet("users");
+
+ printClusterInfo(session);
+
+ List customers = sampleCustomers();
+
+ demonstrateBasicWritesAndErrors(session, customerDataSet);
+ seedData(session, customerDataSet, customerMapper, customers);
+ demonstrateConditionalUpdates(session, customerDataSet, customerMapper, customers);
+ demonstrateBatchReads(session, customerDataSet);
+ demonstrateAsyncFilteredUpdates(session, customerDataSet);
+ demonstratePointAndHeaderReads(session, customerDataSet);
+ demonstrateObjectListAndChunking(session, customerDataSet, customerMapper);
+ demonstrateSortingAndPagination(session, customerDataSet, customerMapper);
+ demonstratePreparedAel(session, customerDataSet, customerMapper);
+ demonstrateTtl(session, customerDataSet);
+ demonstrateReadWriteExpressions(session, customerDataSet);
+ demonstrateQueryHints(session, customerDataSet);
+ demonstrateBackgroundQuery(session, customerDataSet);
+ demonstrateMultiOperationBatches(session, customerDataSet, users);
+ Customer readCustomer = demonstrateObjectMapping(session, customerDataSet, customerMapper);
+ demonstrateGenerationCheck(session, customerDataSet, readCustomer);
+ demonstrateComplexCdt(session, cdtDemoRecords);
+ demonstrateBitOperations(session, cdtDemoRecords);
+ demonstrateHeterogeneousBatch(session, customerDataSet, addressDataSet);
+ demonstrateAsyncMapping(session, customerDataSet);
+ }
- List sindexes = session.info().secondaryIndexes();
- System.out.println(sindexes);
- sindexes.forEach(sindex -> {
- System.out.printf("Secondary index: %s\n", sindex);
- System.out.println(" " + session.info().secondaryIndexDetails(sindex));
- });
+ private void printClusterInfo(Session session) {
+ Set namespaces = session.info().namespaces();
+ namespaces.forEach(ns -> {
+ Optional details = session.info().namespaceDetails(ns);
+ details.ifPresent(System.out::println);
+ });
+
+ List sindexes = session.info().secondaryIndexes();
+ System.out.println(sindexes);
+ sindexes.forEach(sindex -> {
+ System.out.printf("Secondary index: %s\n", sindex);
+ System.out.println(" " + session.info().secondaryIndexDetails(sindex));
+ });
+ }
- session.truncate(customerDataSet);
+ private void demonstrateBasicWritesAndErrors(Session session, TypedDataSet customerDataSet) {
+ session.truncate(customerDataSet);
- try {
- session.update(customerDataSet.id(1)).execute();
- }
- catch (AerospikeException ae) {
- System.out.printf("Exception caught as expected: %s (%s)\n", ae.getMessage(), ae.getClass());
- }
- try {
- session.update(customerDataSet.id(1)).bin("bob").setTo(5).execute();
- }
- catch (AerospikeException ae) {
- System.out.printf("Exception caught as expected on node %s: %s (%s)\n", ae.getNode(), ae.getMessage(), ae.getClass());
- }
- RecordStream rss = session.update(customerDataSet.id(1)).bin("bob").setTo(5).executeAsync(ErrorStrategy.IN_STREAM);
+ try {
+ session.update(customerDataSet.id(1)).execute();
+ }
+ catch (AerospikeException ae) {
+ System.out.printf("Exception caught as expected: %s (%s)\n", ae.getMessage(), ae.getClass());
+ }
+ try {
+ session.update(customerDataSet.id(1)).bin("bob").setTo(5).execute();
+ }
+ catch (AerospikeException ae) {
+ System.out.printf("Exception caught as expected on node %s: %s (%s)\n", ae.getNode(), ae.getMessage(), ae.getClass());
+ }
+ RecordStream rss = session.update(customerDataSet.id(1)).bin("bob").setTo(5).executeAsync(ErrorStrategy.IN_STREAM);
// System.out.println(rss.getFirst());
- session.insert(customerDataSet.id(1))
- .bin("Name").setTo("test1")
- .bin("i1").setTo(1)
- .bin("i2").setTo(2)
- .bin("f1").setTo(1.1)
- .bin("f2").setTo(2.2)
- .bin("s1").setTo("hello ")
- .bin("s2").setTo("world")
- .execute();
+ session.insert(customerDataSet.id(1))
+ .bin("Name").setTo("test1")
+ .bin("i1").setTo(1)
+ .bin("i2").setTo(2)
+ .bin("f1").setTo(1.1)
+ .bin("f2").setTo(2.2)
+ .bin("s1").setTo("hello ")
+ .bin("s2").setTo("world")
+ .execute();
// print(session.query(customerDataSet.id(1))
// .bin("calc0").selectFrom("1.0 == $.f1")
@@ -356,331 +223,352 @@ Address.class, new AddressMapper()
// .execute());
// System.exit(0);
- session.upsert(customerDataSet.id("bob")).bin("A").setTo(2).bin("B").setTo(2.2).execute();
- RecordStream rs1 = session.query(customerDataSet.id("bob"))
- .readingOnlyBins("name")
- .execute()
- .asUntypedRecordStream();
- System.out.println(rs1.getFirst());
+ session.upsert(customerDataSet.id("bob")).bin("A").setTo(2).bin("B").setTo(2.2).execute();
+ RecordStream rs1 = session.query(customerDataSet.id("bob"))
+ .readingOnlyBins("name")
+ .execute()
+ .asUntypedRecordStream();
+ System.out.println(rs1.getFirst());
+ }
- session.upsert(customerDataSet.ids(1,2,3,4,5)).bin("holdings").add(1).execute();
- session.upsert(customerDataSet)
- .bins("name", "age")
- .id(1).values("Tim", 312)
- .id(2).values("Bob", 25)
- .id(3).values("Jane", 46)
- .execute();
+ private void seedData(Session session, TypedDataSet customerDataSet, CustomerMapper customerMapper,
+ List customers) {
+ session.upsert(customerDataSet.ids(1,2,3,4,5)).bin("holdings").add(1).execute();
+ session.upsert(customerDataSet)
+ .bins("name", "age")
+ .id(1).values("Tim", 312)
+ .id(2).values("Bob", 25)
+ .id(3).values("Jane", 46)
+ .execute();
- System.out.printf("id(2) exists: %b\n", session.exists(customerDataSet.ids(2)).execute().getFirst());
- session.delete(customerDataSet.ids(2)).withoutDurableDelete().execute();
+ System.out.printf("id(2) exists: %b\n", session.exists(customerDataSet.ids(2)).execute().getFirst());
+ session.delete(customerDataSet.ids(2)).withoutDurableDelete().execute();
// System.out.printf("id(2) exists: %b\n", session.exists(customerDataSet.ids(2)).execute().getFirst());
- DataSet users = DataSet.of("test", "users");
-
- RecordStream result = session.upsert(customerDataSet.id(80))
- .bin("name").setTo("Tim")
- .bin("age").setTo(342)
- .execute();
- System.out.println(result.getFirst());
+ RecordStream result = session.upsert(customerDataSet.id(80))
+ .bin("name").setTo("Tim")
+ .bin("age").setTo(342)
+ .execute();
+ System.out.println(result.getFirst());
- session.upsert(customerDataSet.ids(81, 82))
- .bin("name").setTo("Tim")
- .bin("age").setTo(343)
- .execute();
- session.upsert(customerDataSet)
- .bins("name", "age")
- .id(83).values("Tim", 342)
- .execute();
- session.upsert(customerDataSet)
- .bins("name", "age")
- .id(84).values("Tim", 342)
- .id(85).values("Fred", 37)
- .execute();
+ session.upsert(customerDataSet.ids(81, 82))
+ .bin("name").setTo("Tim")
+ .bin("age").setTo(343)
+ .execute();
+ session.upsert(customerDataSet)
+ .bins("name", "age")
+ .id(83).values("Tim", 342)
+ .execute();
+ session.upsert(customerDataSet)
+ .bins("name", "age")
+ .id(84).values("Tim", 342)
+ .id(85).values("Fred", 37)
+ .execute();
// sessionWithoutExceptions.insert(customerDataSet.id(80))
// .bin("name").setTo("Bob")
// .execute()
// .getFirst();
- session.upsert(customerDataSet.id(100))
- .bin("name").setTo("Tim")
- .bin("age").setTo(312)
- .bin("dob").setTo(new Date().getTime())
- .bin("id2").setTo(100)
- .expireRecordAt(LocalDateTime.of(2030, 1, 1, 0, 0))
- .execute();
-
- session.delete(customerDataSet.ids(900, 901, 902, 903, 904, 905)).execute();
-
- session.insert(customerDataSet)
- .bins("name", "age", "hair", "dob")
- .id(899).values("Tim", 312, "brown", new Date().getTime());
-
- RecordStream values = session.insert(customerDataSet)
- .bins("name", "age", "hair", "dob")
- .id(900).values("Tim", 312, "brown", new Date().getTime())
- .id(901).values("Jane", 28, "blonde", new Date().getTime())
- .id(902).values("Bob", 54, "brown", new Date().getTime()).expireRecordAfter(Duration.ofDays(5))
- .id(903).values("Jordan", 45, "red", new Date().getTime())
- .id(904).values("Alex", 67, "blonde", new Date().getTime())
- .id(905).values("Sam", 24, "brown", new Date().getTime())
- .defaultExpireRecordAfter(Duration.ofDays(30))
- .execute();
- values.forEach(kr -> System.out.printf("%s -> %s\n", kr.getKey(), kr.recordOrThrow()));
-
- for (int i = 0; i < 15; i++) {
- session.upsert(customerDataSet.id(i))
- .bin("name").setTo("Tim-" + i)
- .bin("age").setTo(312+i)
- .bin("hair").setTo("brown")
- .bin("dob").setTo(new Date().getTime())
- .execute();
-
- session.upsert(customerDataSet)
- .bins("name", "age", "hair", "dob")
- .id(1000+i).values("Tim-"+i, 312+i, "brown", new Date().getTime())
- .expireRecordAfter(Duration.ofDays(30))
- .execute();
-// .values("Jane", 28, "blonde", new Date().getTime())
- }
-
- session.delete(customerDataSet.ids(1,2,3,5,7,11,13,17)).execute();
-
- session.delete(customerDataSet.id(102)).execute();
-
- session.insert(customerDataSet.id(102))
- .bin("name").setTo("Sue")
- .bin("age").setTo(27)
- .bin("id").setTo(102)
+ session.upsert(customerDataSet.id(100))
+ .bin("name").setTo("Tim")
+ .bin("age").setTo(312)
.bin("dob").setTo(new Date().getTime())
+ .bin("id2").setTo(100)
+ .expireRecordAt(LocalDateTime.of(2030, 1, 1, 0, 0))
.execute();
- session.update(customerDataSet.id(102))
- .bin("age").setTo(26)
- .execute();
-
- session.delete(customerDataSet.id(102)).execute();
-
- session.upsert(customerDataSet.id(102))
- .bin("name").setTo("Sue")
- .bin("age").setTo(27)
- .bin("id").setTo(102)
- .bin("dob").setTo(new Date().getTime())
- .bin("rooms").setTo(Map.of(
- "room1", Map.of("occupied", false, "rates", Map.of(1, 100, 2, 150, 3, -1)),
- "room2", Map.of("occupied", true, "rates", Map.of(1, 90, 2, -1, 3, -1)),
- "room3", Map.of("occupied", false, "rates", Map.of(1, 67, 2, 200, 3, 99)),
- "room4", Map.of("occupied", true, "rates", Map.of(1, 98, 2, -1, 3, -1)),
- "room5", Map.of("occupied", false, "rates", Map.of(1, 98, 2, -1, 3, -1)),
- "room6", Map.of("occupied", true, "rates", Map.of(1, 98, 2, -1, 3, -1))
- ))
- .bin("rooms2").setTo(Map.of("test", true))
+ session.delete(customerDataSet.ids(900, 901, 902, 903, 904, 905)).execute();
+
+ session.insert(customerDataSet)
+ .bins("name", "age", "hair", "dob")
+ .id(899).values("Tim", 312, "brown", new Date().getTime());
+
+ RecordStream values = session.insert(customerDataSet)
+ .bins("name", "age", "hair", "dob")
+ .id(900).values("Tim", 312, "brown", new Date().getTime())
+ .id(901).values("Jane", 28, "blonde", new Date().getTime())
+ .id(902).values("Bob", 54, "brown", new Date().getTime()).expireRecordAfter(Duration.ofDays(5))
+ .id(903).values("Jordan", 45, "red", new Date().getTime())
+ .id(904).values("Alex", 67, "blonde", new Date().getTime())
+ .id(905).values("Sam", 24, "brown", new Date().getTime())
+ .defaultExpireRecordAfter(Duration.ofDays(30))
.execute();
+ values.forEach(kr -> System.out.printf("%s -> %s\n", kr.getKey(), kr.recordOrThrow()));
-
- RecordStream results = session.upsert(customerDataSet.id(102))
- .bin("name").setTo("Bob")
- .bin("age").setTo(30)
- .bin("id").get()
+ for (int i = 0; i < 15; i++) {
+ session.upsert(customerDataSet.id(i))
+ .bin("name").setTo("Tim-" + i)
+ .bin("age").setTo(312+i)
+ .bin("hair").setTo("brown")
.bin("dob").setTo(new Date().getTime())
-// .bin("rooms").onMapKey("room1").update("Hello")
- .bin("rooms").onMapIndex(2).getValues()
- .bin("rooms").onMapKeyRange("room1", "room2").countAllOthers()
- .bin("rooms").onMapKey("room1").getValues()
- .bin("rooms").onMapKeyRange("room1", "room3").count()
- .bin("rooms").onMapKey("room1").onMapKey("rates").onMapKey(1).setTo(110)
- .bin("rooms").onMapKey("room2").mapClear()
- .bin("rooms").onMapKeyRange("room4", "room9").remove()
- .bin("rooms").onMapKey("room1").onMapKey("rates").onMapKey(1).add(5)
- .bin("rooms").onMapKeyRelativeIndexRange("bob", -1, 1).getKeysAndValues()
- .bin("rooms2").mapClear()
- .bin("rooms2").onMapKey("child", MapOrder.KEY_ORDERED).onMapKey("subChild").setTo(5)
- // TODO: How to insert an element into a list which doesn't exist?
- // TODO: Should complex operations return one item per call?
-// .bin("rooms2").onMapKey("child", MapOrder.KEY_ORDERED).onListIndex(0, ListOrder.UNORDERED, false).listAdd(5)
.execute();
- System.out.println(results.getFirst());
- System.out.println(session.query(customerDataSet.id(102)).execute().getFirst());
- session.update(customerDataSet.id(102))
- .bin("name").append("-test")
- .bin("age").add(1)
- .execute();
- System.out.println(session.query(customerDataSet.id(102)).execute().getFirst());
-
-
- session.upsert(customerDataSet.id(102))
- .bin("name").setTo("Sue")
- .bin("age").setTo(26)
- .bin("dob").setTo(new Date().getTime())
+ session.upsert(customerDataSet)
+ .bins("name", "age", "hair", "dob")
+ .id(1000+i).values("Tim-"+i, 312+i, "brown", new Date().getTime())
+ .expireRecordAfter(Duration.ofDays(30))
.execute();
+// .values("Jane", 28, "blonde", new Date().getTime())
+ }
- List customers = List.of(
- new Customer(20, "Jordan", 36, new Date()),
- new Customer(21, "Alex", 27, new Date()),
- new Customer(22, "Betty", 27, new Date()),
- new Customer(23, "Bob", 33, new Date()),
- new Customer(24, "Fred", 6, new Date()),
- new Customer(25, "Alex", 28, new Date()),
- new Customer(26, "Alex", 26, new Date()),
- new Customer(27, "Jordan", 19, new Date()),
- new Customer(28, "Gruper", 28, new Date()),
- new Customer(29, "Bree", 24, new Date()),
- new Customer(30, "Perry", 44, new Date()),
- new Customer(31, "Alex", 27, new Date()),
- new Customer(32, "Betty", 27, new Date()),
- new Customer(33, "Wilma", 18, new Date()),
- new Customer(34, "Joran", 82, new Date()),
- new Customer(35, "Alex", 27, new Date()),
- new Customer(36, "Fred", 99, new Date()),
- new Customer(37, "Sydney", 22, new Date()),
- new Customer(38, "Ita", 99, new Date()),
- new Customer(39, "Rupert", 83, new Date()),
- new Customer(40, "Dominic", 53, new Date()),
- new Customer(41, "Tim", 27, new Date()),
- new Customer(42, "Tim", 29, new Date()),
- new Customer(43, "Tim", 31, new Date()),
- new Customer(44, "Tim", 30, new Date()),
- new Customer(45, "Tim", 33, new Date()),
- new Customer(46, "Tim", 35, new Date())
- );
-
- session.insert(customerDataSet)
- .objects(customers)
- .execute();
+ session.delete(customerDataSet.ids(1,2,3,5,7,11,13,17)).execute();
+
+ session.delete(customerDataSet.id(102)).execute();
+
+ session.insert(customerDataSet.id(102))
+ .bin("name").setTo("Sue")
+ .bin("age").setTo(27)
+ .bin("id").setTo(102)
+ .bin("dob").setTo(new Date().getTime())
+ .execute();
+
+ session.update(customerDataSet.id(102))
+ .bin("age").setTo(26)
+ .execute();
+
+ session.delete(customerDataSet.id(102)).execute();
+
+ session.upsert(customerDataSet.id(102))
+ .bin("name").setTo("Sue")
+ .bin("age").setTo(27)
+ .bin("id").setTo(102)
+ .bin("dob").setTo(new Date().getTime())
+ .bin("rooms").setTo(Map.of(
+ "room1", Map.of("occupied", false, "rates", Map.of(1, 100, 2, 150, 3, -1)),
+ "room2", Map.of("occupied", true, "rates", Map.of(1, 90, 2, -1, 3, -1)),
+ "room3", Map.of("occupied", false, "rates", Map.of(1, 67, 2, 200, 3, 99)),
+ "room4", Map.of("occupied", true, "rates", Map.of(1, 98, 2, -1, 3, -1)),
+ "room5", Map.of("occupied", false, "rates", Map.of(1, 98, 2, -1, 3, -1)),
+ "room6", Map.of("occupied", true, "rates", Map.of(1, 98, 2, -1, 3, -1))
+ ))
+ .bin("rooms2").setTo(Map.of("test", true))
+ .execute();
- System.out.println("Updating all customers called Tim");
- print(session.update(customerDataSet)
- .where("$.name == 'Tim'")
- .objects(customers)
- .execute());
- System.out.printf("Customer 46 age before scan: %d\n",
- session.query(customerDataSet.id(46)).execute().getFirstRecord().getInt("age"));
+ RecordStream results = session.upsert(customerDataSet.id(102))
+ .bin("name").setTo("Bob")
+ .bin("age").setTo(30)
+ .bin("id").get()
+ .bin("dob").setTo(new Date().getTime())
+// .bin("rooms").onMapKey("room1").update("Hello")
+ .bin("rooms").onMapIndex(2).getValues()
+ .bin("rooms").onMapKeyRange("room1", "room2").countAllOthers()
+ .bin("rooms").onMapKey("room1").getValues()
+ .bin("rooms").onMapKeyRange("room1", "room3").count()
+ .bin("rooms").onMapKey("room1").onMapKey("rates").onMapKey(1).setTo(110)
+ .bin("rooms").onMapKey("room2").mapClear()
+ .bin("rooms").onMapKeyRange("room4", "room9").remove()
+ .bin("rooms").onMapKey("room1").onMapKey("rates").onMapKey(1).add(5)
+ .bin("rooms").onMapKeyRelativeIndexRange("bob", -1, 1).getKeysAndValues()
+ .bin("rooms2").mapClear()
+ .bin("rooms2").onMapKey("child", MapOrder.KEY_ORDERED).onMapKey("subChild").setTo(5)
+ // TODO: How to insert an element into a list which doesn't exist?
+ // TODO: Should complex operations return one item per call?
+// .bin("rooms2").onMapKey("child", MapOrder.KEY_ORDERED).onListIndex(0, ListOrder.UNORDERED, false).listAdd(5)
+ .execute();
+
+ System.out.println(results.getFirst());
+ System.out.println(session.query(customerDataSet.id(102)).execute().getFirst());
+ session.update(customerDataSet.id(102))
+ .bin("name").append("-test")
+ .bin("age").add(1)
+ .execute();
+ System.out.println(session.query(customerDataSet.id(102)).execute().getFirst());
+
+
+ session.upsert(customerDataSet.id(102))
+ .bin("name").setTo("Sue")
+ .bin("age").setTo(26)
+ .bin("dob").setTo(new Date().getTime())
+ .execute();
+
+ session.insert(customerDataSet)
+ .objects(customers)
+ .using(customerMapper)
+ .execute();
+ }
- ExecuteTask task = session.backgroundTask().update(customerDataSet)
- .bin("age").add(1)
- .execute();
+ /**
+ * The seed set of customers inserted as mapped objects. Referenced again later for
+ * conditional (where-filtered) updates.
+ */
+ private static List sampleCustomers() {
+ return List.of(
+ new Customer(20, "Jordan", 36, new Date()),
+ new Customer(21, "Alex", 27, new Date()),
+ new Customer(22, "Betty", 27, new Date()),
+ new Customer(23, "Bob", 33, new Date()),
+ new Customer(24, "Fred", 6, new Date()),
+ new Customer(25, "Alex", 28, new Date()),
+ new Customer(26, "Alex", 26, new Date()),
+ new Customer(27, "Jordan", 19, new Date()),
+ new Customer(28, "Gruper", 28, new Date()),
+ new Customer(29, "Bree", 24, new Date()),
+ new Customer(30, "Perry", 44, new Date()),
+ new Customer(31, "Alex", 27, new Date()),
+ new Customer(32, "Betty", 27, new Date()),
+ new Customer(33, "Wilma", 18, new Date()),
+ new Customer(34, "Joran", 82, new Date()),
+ new Customer(35, "Alex", 27, new Date()),
+ new Customer(36, "Fred", 99, new Date()),
+ new Customer(37, "Sydney", 22, new Date()),
+ new Customer(38, "Ita", 99, new Date()),
+ new Customer(39, "Rupert", 83, new Date()),
+ new Customer(40, "Dominic", 53, new Date()),
+ new Customer(41, "Tim", 27, new Date()),
+ new Customer(42, "Tim", 29, new Date()),
+ new Customer(43, "Tim", 31, new Date()),
+ new Customer(44, "Tim", 30, new Date()),
+ new Customer(45, "Tim", 33, new Date()),
+ new Customer(46, "Tim", 35, new Date())
+ );
+ }
- System.out.printf("task id = %d\n", task.getTaskId());
- task.waitTillComplete();
- System.out.printf("Customer 46 age after scan: %d\n",
- session.query(customerDataSet.id(46)).execute().getFirstRecord().getInt("age"));
+ private void demonstrateConditionalUpdates(Session session, TypedDataSet customerDataSet,
+ CustomerMapper customerMapper, List customers) {
+ System.out.println("Updating all customers called Tim");
+ print(session.update(customerDataSet)
+ .where("$.name == 'Tim'")
+ .objects(customers)
+ .using(customerMapper)
+ .execute());
+
+ System.out.printf("Customer 46 age before scan: %d\n",
+ session.query(customerDataSet.id(46)).execute().getFirstRecord().getInt("age"));
+
+ ExecuteTask task = session.backgroundTask().update(customerDataSet)
+ .bin("age").add(1)
+ .execute();
+
+ System.out.printf("task id = %d\n", task.getTaskId());
+ task.waitTillComplete();
+ System.out.printf("Customer 46 age after scan: %d\n",
+ session.query(customerDataSet.id(46)).execute().getFirstRecord().getInt("age"));
+ }
- // Batch partition filter test
- List keys = customerDataSet.asKeys(IntStream.rangeClosed(20, 48).toArray());
- System.out.println("Read 25 records, but only those in partitions 0->2047");
- print(session.query(keys)
- .onPartitionRange(0, 2048)
- .execute());
+ private void demonstrateBatchReads(Session session, TypedDataSet customerDataSet) {
+ // Batch partition filter test
+ List keys = customerDataSet.asKeys(IntStream.rangeClosed(20, 48).toArray());
+ System.out.println("Read 25 records, but only those in partitions 0->2047");
+ print(session.query(keys)
+ .onPartitionRange(0, 2048)
+ .execute());
- System.out.println("Full batch read:");
- print(session.query(keys).execute());
+ System.out.println("Full batch read:");
+ print(session.query(keys).execute());
- System.out.println("\nBatchRead where name = 'Tim':");
- print(session.query(keys).where("$.name == 'Tim'").execute());
+ System.out.println("\nBatchRead where name = 'Tim':");
+ print(session.query(keys).where("$.name == 'Tim'").execute());
- System.out.println("\nBatchRead where name = 'Tim':");
- print(session.query(keys).includeMissingKeys().where("$.name == 'Tim'").execute());
+ System.out.println("\nBatchRead where name = 'Tim':");
+ print(session.query(keys).includeMissingKeys().where("$.name == 'Tim'").execute());
- System.out.println("\nBatchRead where name = 'Tim':");
- print(session.query(keys).where("$.name == 'Tim'").includeMissingKeys().failOnFilteredOut().execute());
+ System.out.println("\nBatchRead where name = 'Tim':");
+ print(session.query(keys).where("$.name == 'Tim'").includeMissingKeys().failOnFilteredOut().execute());
// System.out.println("Read the set, limit 6, test than includeMissingKeys() gives a compile error");
// print(session.query(customerDataSet).includeMissingKeys().execute());
// print(session.query(customerDataSet).failOnFilteredOut().execute());
- System.out.println("Read the set, limit 6");
- print(session.query(customerDataSet).limit(6).execute());
+ System.out.println("Read the set, limit 6");
+ print(session.query(customerDataSet).limit(6).execute());
+ }
- List keyList2 = customerDataSet.asKeys(20,21,22,23,24,25,26,27);
- RecordStream thisStream = session.update(keyList2)
- .bin("age").add(1)
- .execute();
+ private void demonstrateAsyncFilteredUpdates(Session session, TypedDataSet customerDataSet) {
+ List keyList2 = customerDataSet.asKeys(20,21,22,23,24,25,26,27);
+ RecordStream thisStream = session.update(keyList2)
+ .bin("age").add(1)
+ .execute();
- System.out.println("Showing results before guaranteeing execution has finished.");
- print(session.query(keyList2).execute());
- System.out.println("Showing async results");
- print(thisStream);
- System.out.println("Showing results now execution has finished.");
- print(session.query(keyList2).execute());
+ System.out.println("Showing results before guaranteeing execution has finished.");
+ print(session.query(keyList2).execute());
+ System.out.println("Showing async results");
+ print(thisStream);
+ System.out.println("Showing results now execution has finished.");
+ print(session.query(keyList2).execute());
- System.out.printf("Update people in list whose age is < 35 (%s)\n", keyList2);
- print(session.update(keyList2)
- .bin("age").add(1)
- .where("$.age < 35")
- .execute());
- print(session.query(keyList2).execute());
+ System.out.printf("Update people in list whose age is < 35 (%s)\n", keyList2);
+ print(session.update(keyList2)
+ .bin("age").add(1)
+ .where("$.age < 35")
+ .execute());
+ print(session.query(keyList2).execute());
- session.update(keyList2)
- .bin("age").add(1)
- .where("$.age < 35")
- .failOnFilteredOut()
- .execute();
- print(session.query(keyList2).execute());
+ session.update(keyList2)
+ .bin("age").add(1)
+ .where("$.age < 35")
+ .failOnFilteredOut()
+ .execute();
+ print(session.query(keyList2).execute());
+ }
- // Query contract:
- // - If a list of ids is provided and there is not "sort" clause, the records in the stream are returned in the order which the ids are specified
- // - If we're processing the records with notifiers, we cannot also get them back in a stream
- // Should there be a KeyRecord style class with an error code on it and inDoubt? (Similar to Batch Record, but this violates SOLID by being used both for
- // input and output)
+ private void demonstratePointAndHeaderReads(Session session, TypedDataSet customerDataSet) {
+ // Query contract:
+ // - If a list of ids is provided and there is not "sort" clause, the records in the stream are returned in the order which the ids are specified
+ // - If we're processing the records with notifiers, we cannot also get them back in a stream
+ // Should there be a KeyRecord style class with an error code on it and inDoubt? (Similar to Batch Record, but this violates SOLID by being used both for
+ // input and output)
- // Need a class to turn a resultcode into an exception similar to SQLExceptionTranslator in Spring Boot. These are customizable in `sql-error-codes.xml`
- // file, eg:
- //
- // PostgreSQL
- // 23505
- // 23000,23502,23503
- //
+ // Need a class to turn a resultcode into an exception similar to SQLExceptionTranslator in Spring Boot. These are customizable in `sql-error-codes.xml`
+ // file, eg:
+ //
+ // PostgreSQL
+ // 23505
+ // 23000,23502,23503
+ //
- // Threads are really cheap in JDK 21+ so all calls could notify async via a new thread and an ArrayBlockingQueue for example?
+ // Threads are really cheap in JDK 21+ so all calls could notify async via a new thread and an ArrayBlockingQueue for example?
// session.query(customerDataSet)
// .onRecordArrival(keyRecord -> System.out.println(keyRecord))
// .onRecordError()
// .onDone(() -> System.out.println("Done"))
// .execute();
- System.out.println("\nRead point records - in the same order as the keys, limit to 3");
- print(session.queryTypedKeys(customerDataSet.ids(1,3,5,7)).limit(3).execute());
-
- System.out.println("\nSingle point record");
- print(session.queryTypedKeys(customerDataSet.ids(6)).execute());
+ System.out.println("\nRead point records - in the same order as the keys, limit to 3");
+ print(session.queryTypedKeys(customerDataSet.ids(1,3,5,7)).limit(3).execute());
- System.out.println("Read the set, output as stream, limit of 5");
- session.query(customerDataSet).limit(5).execute()
- .stream()
- .map(keyRec -> "Name: " + keyRec.recordOrThrow().getString("name"))
- .forEach(str -> System.out.println(str));
+ System.out.println("\nSingle point record");
+ print(session.queryTypedKeys(customerDataSet.ids(6)).execute());
- System.out.println("Read header, point read");
- print(session.query(customerDataSet.id(6)).withNoBins().execute());
- System.out.println("Read header, batch read");
- print(session.queryTypedKeys(customerDataSet.ids(6,7,8)).withNoBins().execute());
- System.out.println("Read header, set read");
- print(session.query(customerDataSet).withNoBins().execute());
-
- System.out.println("Read with select bins, point read");
- print(session.queryTypedKeys(customerDataSet.ids(6)).readingOnlyBins("name", "age").execute());
- System.out.println("Read with select bins, batch read");
- print(session.queryTypedKeys(customerDataSet.ids(6,7,8)).readingOnlyBins("name", "age").execute());
- System.out.println("Read with select bins, set read");
- print(session.query(customerDataSet).readingOnlyBins("name", "age").execute());
+ System.out.println("Read the set, output as stream, limit of 5");
+ session.query(customerDataSet).limit(5).execute()
+ .stream()
+ .map(keyRec -> "Name: " + keyRec.recordOrThrow().getString("name"))
+ .forEach(str -> System.out.println(str));
+
+ System.out.println("Read header, point read");
+ print(session.query(customerDataSet.id(6)).withNoBins().execute());
+ System.out.println("Read header, batch read");
+ print(session.queryTypedKeys(customerDataSet.ids(6,7,8)).withNoBins().execute());
+ System.out.println("Read header, set read");
+ print(session.query(customerDataSet).withNoBins().execute());
+
+ System.out.println("Read with select bins, point read");
+ print(session.queryTypedKeys(customerDataSet.ids(6)).readingOnlyBins("name", "age").execute());
+ System.out.println("Read with select bins, batch read");
+ print(session.queryTypedKeys(customerDataSet.ids(6,7,8)).readingOnlyBins("name", "age").execute());
+ System.out.println("Read with select bins, set read");
+ print(session.query(customerDataSet).readingOnlyBins("name", "age").execute());
// session.update(customerDataSet.ids(1,2,3,4))
// .bin
- // Throw an exception
- try {
- print(session.queryTypedKeys(customerDataSet.ids(6,7,8)).readingOnlyBins("name", "age").withNoBins().execute());
- }
- catch (Exception e) {
- e.printStackTrace();
- }
+ // Throw an exception
+ try {
+ print(session.queryTypedKeys(customerDataSet.ids(6,7,8)).readingOnlyBins("name", "age").withNoBins().execute());
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
- // TODO: Put transaction control into policies
+ private void demonstrateObjectListAndChunking(Session session, TypedDataSet customerDataSet,
+ CustomerMapper customerMapper) {
+ // TODO: Put transaction control into policies
// session.doInTransaction(txnSession -> {
// Optional recResult = txnSession.query(customerDataSet.id(1)).execute().getFirst();
// if (true) {
@@ -690,629 +578,657 @@ Address.class, new AddressMapper()
// txnSession.insert(customerDataSet.id(3)).notInAnyTransaction().execute();
// });
- customers = session.queryTypedKeys(customerDataSet.ids(20, 21)).execute().toObjectList(customerMapper);
- System.out.println(customers);
+ List customers = session.queryTypedKeys(customerDataSet.ids(20, 21)).execute().toObjectList(customerMapper);
+ System.out.println(customers);
- // Records-per-second check
- RecordStream queryResults = session.query(customerDataSet).recordsPerSecond(1).execute().asUntypedRecordStream();
- queryResults.forEach(rr -> System.out.println(rr.recordOrThrow()));
- // session.query(customerDataSet.id(1)).recordsPerSecond(100).execute();
+ // Records-per-second check
+ RecordStream queryResults = session.query(customerDataSet).recordsPerSecond(1).execute().asUntypedRecordStream();
+ queryResults.forEach(rr -> System.out.println(rr.recordOrThrow()));
+ // session.query(customerDataSet.id(1)).recordsPerSecond(100).execute();
- // Server-side chunking example - fetch records in chunks of 20
- customers = session.query(customerDataSet).chunkSize(20).execute().toObjectList(customerMapper);
- System.out.println(customers);
+ // Server-side chunking example - fetch records in chunks of 20
+ customers = session.query(customerDataSet).chunkSize(20).execute().toObjectList(customerMapper);
+ System.out.println(customers);
- // Server-side chunking example - process records in chunks of 10
- RecordStream rs = session.query(customerDataSet).chunkSize(10).execute().asUntypedRecordStream();
- int chunk = 0;
- while (rs.hasMoreChunks()) {
- System.out.println("Chunk: " + (++chunk));
- rs.forEach(rec -> System.out.println(rec));
- }
+ // Server-side chunking example - process records in chunks of 10
+ RecordStream rs = session.query(customerDataSet).chunkSize(10).execute().asUntypedRecordStream();
+ int chunk = 0;
+ while (rs.hasMoreChunks()) {
+ System.out.println("Chunk: " + (++chunk));
+ rs.forEach(rec -> System.out.println(rec));
+ }
- int total = session.query(customerDataSet)
- .execute()
- .stream()
- .mapToInt(kr -> kr.recordOrThrow().getInt("quantity"))
- .sum();
- System.out.println("\n\nSorting customers by Name with a where clause using NavigatableRecordStream");
- customers = session.query(customerDataSet)
- .where("$.name == 'Tim' and $.age > 30")
- .limit(1000)
- .execute()
- .asNavigatableStream()
- .sortBy("name", SortDir.SORT_ASC, true)
- .toObjectList(customerMapper);
-
- for (Customer customer : customers) {
- System.out.println(customer);
- }
+ int total = session.query(customerDataSet)
+ .execute()
+ .stream()
+ .mapToInt(kr -> kr.recordOrThrow().getInt("quantity"))
+ .sum();
+ }
- System.out.println("End sorting customers by Name with a where clause\n");
-
- customers = session.query(customerDataSet)
- .where(Ael.stringBin("name").eq("Tim").and(Ael.longBin("age").gt(30)))
- .limit(1000)
- .execute()
- .asNavigatableStream()
- .sortBy("name", SortDir.SORT_ASC, true)
- .toObjectList(customerMapper);
- for (Customer customer : customers) {
- System.out.println(customer);
- }
+ private void demonstrateSortingAndPagination(Session session, TypedDataSet customerDataSet,
+ CustomerMapper customerMapper) {
+ System.out.println("\n\nSorting customers by Name with a where clause using NavigatableRecordStream");
+ List customers = session.query(customerDataSet)
+ .where("$.name == 'Tim' and $.age > 30")
+ .limit(1000)
+ .execute()
+ .asNavigatableStream()
+ .sortBy("name", SortDir.SORT_ASC, true)
+ .toObjectList(customerMapper);
- // PreparedAel: reuse a template with ?0, ?1 placeholders (zero-based).
- // Bound values are substituted client-side as AEL literals; types are inferred
- // from the literals (e.g. 'Tim' -> STRING, 30 -> INT).
- PreparedAel nameAndAgeFilter = PreparedAel.prepare("$.name == ?0 and $.age > ?1");
- System.out.println("\nPreparedAel filter (Tim, age > 30):");
- customers = session.query(customerDataSet)
- .where(nameAndAgeFilter, "Tim", 30)
- .execute()
- .toObjectList(customerMapper);
- customers.forEach(System.out::println);
-
- System.out.println("PreparedAel filter (Jane, age > 21) — same template, different params:");
- customers = session.query(customerDataSet)
- .where(nameAndAgeFilter, "Jane", 21)
- .execute()
- .toObjectList(customerMapper);
- customers.forEach(System.out::println);
- System.out.println("End PreparedAel examples\n");
+ for (Customer customer : customers) {
+ System.out.println(customer);
+ }
- System.out.println("---- End sort ---");
+ System.out.println("End sorting customers by Name with a where clause\n");
+ customers = session.query(customerDataSet)
+ .where(Ael.stringBin("name").eq("Tim").and(Ael.longBin("age").gt(30)))
+ .limit(1000)
+ .execute()
+ .asNavigatableStream()
+ .sortBy("name", SortDir.SORT_ASC, true)
+ .toObjectList(customerMapper);
+ for (Customer customer : customers) {
+ System.out.println(customer);
+ }
- System.out.println("\n\nSorting customers by Age (desc) then name (asc), using NavigatableRecordStream for client-side pagination");
- try (TypedNavigatableRecordStream navStream = session.query(customerDataSet)
- .limit(13)
- .execute()
- .asNavigatableStream()
- .sortBy(List.of(
- SortProperties.descending("age"),
- SortProperties.ascendingIgnoreCase("name")
- ))
- .pageSize(5)) {
-
- int page = 0;
- while (navStream.hasMorePages()) {
- System.out.println("---- Page " + (++page) + " -----");
- customers = navStream.toObjectList(customerMapper);
- customers.forEach(cust -> System.out.println(cust));
- }
- System.out.println("---- End sort ---");
-
- // Jump to page 2
- System.out.println("--- Setting page to 2 ---");
- navStream.setPageTo(2);
- navStream.forEach(rec -> System.out.println(rec));
- System.out.println("--- done with page 2 ---");
-
- // Now re-sort by name only
- System.out.println("Re-sorting records by name");
- navStream.sortBy(SortProperties.ascending("name"));
- int pageNum = 0;
- while (navStream.hasMorePages()) {
- System.out.println("---- Page " + (++pageNum) + " -----");
- List custList = navStream.toObjectList(customerMapper);
- custList.forEach(cust -> System.out.println(cust));
- }
- System.out.println("---- End sort ---");
- }
+ System.out.println("---- End sort ---");
- // -------------
- // UDF Calls
- // -------------
-// Object udfResult = session.executeUdf(customerDataSet.id(1)).function("pkg", "myFunc").execute().getFirstUdfResultObject();
-// System.out.println("UDF Result = " + udfResult);
- // ---------------------------
- // TTL Test
- // ---------------------------
- System.out.println("--- Test TTL ---");
- session.delete(customerDataSet.id(1)).execute();
+ System.out.println("\n\nSorting customers by Age (desc) then name (asc), using NavigatableRecordStream for client-side pagination");
+ try (TypedNavigatableRecordStream navStream = session.query(customerDataSet)
+ .limit(13)
+ .execute()
+ .asNavigatableStream()
+ .sortBy(List.of(
+ SortProperties.descending("age"),
+ SortProperties.ascendingIgnoreCase("name")
+ ))
+ .pageSize(5)) {
- session.upsert(customerDataSet)
- .bins("binA")
- .id(1).values(5)
- .expireRecordAfterSeconds(5)
- .execute();
- System.out.println("Initial read, should be there");
- System.out.println(session.query(customerDataSet.id(1)).execute().getFirst());
- try {
- Thread.sleep(Duration.ofSeconds(6));
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ int page = 0;
+ while (navStream.hasMorePages()) {
+ System.out.println("---- Page " + (++page) + " -----");
+ customers = navStream.toObjectList(customerMapper);
+ customers.forEach(cust -> System.out.println(cust));
}
- System.out.println("Read after TTL exires, should not be there");
- System.out.println(session.query(customerDataSet.id(1)).execute().getFirst());
-
- // ---------------------------
- // Read and write expressions
- // ---------------------------
- System.out.println("--- Expression testing ---");
- session.replace(customerDataSet.id(223))
- .bin("age").setTo(500)
- .bin("value").setTo(123)
- .execute();
-
- System.out.printf("Base record: %s\n",
- session.query(customerDataSet.id(223)).execute().getFirst());
-
- print(session.query(customerDataSet.id(223))
- .bin("age").get()
- .execute());
- System.out.println("Using a read expression");
+ System.out.println("---- End sort ---");
- print(session.queryTypedKeys(customerDataSet.ids(223))
- .bin("bob").selectFrom("$.age + $.value", arg -> arg.ignoreEvalFailure())
- .execute());
+ // Jump to page 2
+ System.out.println("--- Setting page to 2 ---");
+ navStream.setPageTo(2);
+ navStream.forEach(rec -> System.out.println(rec));
+ System.out.println("--- done with page 2 ---");
+
+ // Now re-sort by name only
+ System.out.println("Re-sorting records by name");
+ navStream.sortBy(SortProperties.ascending("name"));
+ int pageNum = 0;
+ while (navStream.hasMorePages()) {
+ System.out.println("---- Page " + (++pageNum) + " -----");
+ List custList = navStream.toObjectList(customerMapper);
+ custList.forEach(cust -> System.out.println(cust));
+ }
+ System.out.println("---- End sort ---");
+ }
+ }
- System.out.println("Using a write expression");
- session.upsert(customerDataSet.id(1))
- .bin("age").setTo(50)
- .bin("value").setTo(10)
- .bin("c2").upsertFrom("$.age + 2 * $.value", arg -> arg.ignoreOpFailure().deleteIfNull())
- .execute();
- session.update(customerDataSet.id(223))
- .bin("bob").upsertFrom("$.age + 2 * $.value")
- .execute();
- System.out.printf("Modified record: %s\n",
- session.query(customerDataSet.id(223)).execute().getFirst());
+ private void demonstratePreparedAel(Session session, TypedDataSet customerDataSet,
+ CustomerMapper customerMapper) {
+ PreparedAel nameAndAgeFilter = PreparedAel.prepare("$.name == ?0 and $.age > ?1");
+
+ System.out.println("\nPreparedAel filter (Tim, age > 30):");
+ session.query(customerDataSet)
+ .where(nameAndAgeFilter, "Tim", 30)
+ .execute()
+ .toObjectList(customerMapper)
+ .forEach(System.out::println);
+
+ System.out.println("PreparedAel filter (Jane, age > 21):");
+ session.query(customerDataSet)
+ .where(nameAndAgeFilter, "Jane", 21)
+ .execute()
+ .toObjectList(customerMapper)
+ .forEach(System.out::println);
+ }
+ private void demonstrateTtl(Session session, TypedDataSet customerDataSet) {
+ // -------------
+ // UDF Calls
+ // -------------
+// Object udfResult = session.executeUdf(customerDataSet.id(1)).function("pkg", "myFunc").execute().getFirstUdfResultObject();
+// System.out.println("UDF Result = " + udfResult);
- session.query(customerDataSet)
- .readingOnlyBins("name", "age")
- .execute();
+ // ---------------------------
+ // TTL Test
+ // ---------------------------
+ System.out.println("--- Test TTL ---");
+ session.delete(customerDataSet.id(1)).execute();
+
+ session.upsert(customerDataSet)
+ .bins("binA")
+ .id(1).values(5)
+ .expireRecordAfterSeconds(5)
+ .execute();
+ System.out.println("Initial read, should be there");
+ System.out.println(session.query(customerDataSet.id(1)).execute().getFirst());
+ try {
+ Thread.sleep(Duration.ofSeconds(6));
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ System.out.println("Read after TTL exires, should not be there");
+ System.out.println(session.query(customerDataSet.id(1)).execute().getFirst());
+ }
- // ---------------------------
- // Query hints
- // ---------------------------
- System.out.println("\n--- Query hints ---");
+ private void demonstrateReadWriteExpressions(Session session, TypedDataSet customerDataSet) {
+ // ---------------------------
+ // Read and write expressions
+ // ---------------------------
+ System.out.println("--- Expression testing ---");
+ session.replace(customerDataSet.id(223))
+ .bin("age").setTo(500)
+ .bin("value").setTo(123)
+ .execute();
+
+ System.out.printf("Base record: %s\n",
+ session.query(customerDataSet.id(223)).execute().getFirst());
+
+ print(session.query(customerDataSet.id(223))
+ .bin("age").get()
+ .execute());
+ System.out.println("Using a read expression");
+
+ TypedRecordStream rs = session.queryTypedKeys(customerDataSet.ids(223))
+ .bin("bob").selectFrom("$.age + $.value", arg -> arg.ignoreEvalFailure())
+ .execute();
+ print(rs);
+
+ System.out.println("Using a write expression");
+ session.upsert(customerDataSet.id(1))
+ .bin("age").setTo(50)
+ .bin("value").setTo(10)
+ .bin("c2").upsertFrom("$.age + 2 * $.value", arg -> arg.ignoreOpFailure().deleteIfNull())
+ .execute();
+ session.update(customerDataSet.id(223))
+ .bin("bob").upsertFrom("$.age + 2 * $.value")
+ .execute();
+ System.out.printf("Modified record: %s\n",
+ session.query(customerDataSet.id(223)).execute().getFirst());
+
+
+ session.query(customerDataSet)
+ .readingOnlyBins("name", "age")
+ .execute();
+ }
- // Hint with index name: tell the server to use a specific secondary index
- session.query(customerDataSet)
- .where("$.age > 30")
- .withHint(hint -> hint.forIndex("age_idx"))
- .execute();
+ private void demonstrateQueryHints(Session session, TypedDataSet customerDataSet) {
+ // ---------------------------
+ // Query hints
+ // ---------------------------
+ System.out.println("\n--- Query hints ---");
+
+ // Hint with index name: tell the server to use a specific secondary index
+ session.query(customerDataSet)
+ .where("$.age > 30")
+ .withHint(hint -> hint.forIndex("age_idx"))
+ .execute();
+
+ // Hint with bin name: prefer the secondary index on a given bin
+ session.query(customerDataSet)
+ .where("$.age > 30")
+ .withHint(hint -> hint.forBin("age"))
+ .execute();
+
+ // Hint with query duration only: override expected duration
+ session.query(customerDataSet)
+ .where("$.age > 30")
+ .withHint(hint -> hint.queryDuration(QueryDuration.SHORT))
+ .execute();
+
+ // Hint combining index name and query duration
+ session.query(customerDataSet)
+ .where("$.age > 30")
+ .withHint(hint -> hint.forIndex("age_idx").queryDuration(QueryDuration.SHORT))
+ .execute();
+
+ // Hint combining query duration first, then bin name
+ session.query(customerDataSet)
+ .where("$.age > 30")
+ .withHint(hint -> hint.queryDuration(QueryDuration.SHORT).forBin("age"))
+ .execute();
+ }
- // Hint with bin name: prefer the secondary index on a given bin
- session.query(customerDataSet)
- .where("$.age > 30")
- .withHint(hint -> hint.forBin("age"))
- .execute();
+ private void demonstrateBackgroundQuery(Session session, TypedDataSet customerDataSet) {
+ // ---------------------------
+ // Background query operations
+ // ---------------------------
+ session.backgroundTask().update(customerDataSet)
+ .bin("age").add(1)
+ .bin("bob").upsertFrom("$.age + $.value")
+ .where("$.state == 'nsw'")
+ .execute()
+ .waitTillComplete();
+ }
- // Hint with query duration only: override expected duration
- session.query(customerDataSet)
- .where("$.age > 30")
- .withHint(hint -> hint.queryDuration(QueryDuration.SHORT))
+ private void demonstrateMultiOperationBatches(Session session, TypedDataSet customerDataSet, DataSet users) {
+ // ------------------------
+ // Multi operation batches
+ // ------------------------
+ RecordStream rsStream = session
+ .update(customerDataSet.ids(1000, 1001))
+ .bin("age").add(1)
+ .bin("dob").setTo(new Date().getTime())
+// .where("$.age > 100")
+ .expireRecordAfter(Duration.ofMinutes(5))
+ .exists(customerDataSet.ids(1000,1001))
+ .queryTypedKeys(customerDataSet.ids(10,12))
+ .delete(customerDataSet.id(1003))
+ .notInAnyTransaction()
+// .defaultWhere("$.value > 200")
+ .defaultExpireRecordAfter(Duration.ofMinutes(20))
+ .execute();
+ System.out.println("Multi operations:");
+ print(rsStream);
+
+ rsStream = session.queryTypedKeys(customerDataSet.ids(1,2,3))
+ .bin("name").get()
+ .bin("map").onMapKeyRange(5, 10).getKeysAndValues()
+ .update(customerDataSet.ids(1))
+ .bin("age").add(1)
.execute();
- // Hint combining index name and query duration
- session.query(customerDataSet)
- .where("$.age > 30")
- .withHint(hint -> hint.forIndex("age_idx").queryDuration(QueryDuration.SHORT))
+ rsStream = session
+ .update(customerDataSet.ids(1,2,3))
+ .bin("age").add(1)
+ .bin("updated").setTo(true)
+ .where("$.age < 21")
+ .delete(customerDataSet.ids(11,12,13,14,15))
+ .update(customerDataSet.ids(5,6,7))
+ .bin("luckyWinner").setTo("true")
+ .defaultWhere("$.updated == false")
.execute();
- // Hint combining query duration first, then bin name
- session.query(customerDataSet)
- .where("$.age > 30")
- .withHint(hint -> hint.queryDuration(QueryDuration.SHORT).forBin("age"))
+ rsStream = session.queryTypedKeys(customerDataSet.ids(1,2,3))
+ .limit(2)
+ .update(customerDataSet.ids(4,5,6))
+ .bin("name").setTo("bob")
+ .expireRecordAfterSeconds(500)
+ .query(customerDataSet.id(7))
.execute();
- // ---------------------------
- // Background query operations
- // ---------------------------
- session.backgroundTask().update(customerDataSet)
- .bin("age").add(1)
- .bin("bob").upsertFrom("$.age + $.value")
- .where("$.state == 'nsw'")
- .execute();
+ //AEL: Clean, readable syntax
+ RecordStream stream = session.query(users)
+ .where("$.status == 'active' and $.age >= 21")
+ .execute();
+ stream.forEach(res -> {
+ if (res.isOk()) {
+ Record row = res.recordOrThrow();
+ // Process row
+ }
+ });
+ stream.close();
+ }
- // ------------------------
- // Multi operation batches
- // ------------------------
- RecordStream rsStream = session
- .update(customerDataSet.ids(1000, 1001))
- .bin("age").add(1)
- .bin("dob").setTo(new Date().getTime())
-// .where("$.age > 100")
- .expireRecordAfter(Duration.ofMinutes(5))
- .exists(customerDataSet.ids(1000,1001))
- .queryTypedKeys(customerDataSet.ids(10,12))
- .delete(customerDataSet.id(1003))
- .notInAnyTransaction()
-// .defaultWhere("$.value > 200")
- .defaultExpireRecordAfter(Duration.ofMinutes(20))
+ private Customer demonstrateObjectMapping(Session session, TypedDataSet customerDataSet,
+ CustomerMapper customerMapper) {
+ // --------------------
+ // Object mapping
+ // --------------------
+ // Insert then read back a customer with an address
+ System.out.println("\n--- Object mapping test ----");
+ Customer sampleCust = new Customer(999, "sample", 456, new Date(), new Address("123 Main St", "Denver", "CO", "USA", "80112"));
+ System.out.println("Reference customer: " + sampleCust);
+
+ session.delete(customerDataSet.id(999)).execute();
+ session.insert(customerDataSet).object(sampleCust).execute();
+ Customer readCustomer = session.query(customerDataSet.id(999)).execute().toObjectList(customerMapper).get(0);
+ System.out.println("Customer read back: " + readCustomer);
+ System.out.println("--- End object mapping test ----");
+
+ try {
+ session.query(customerDataSet).where("$.name == 'Tim'")
+ .bin("fred").get()
.execute();
- System.out.println("Multi operations:");
- print(rsStream);
-
- rsStream = session.queryTypedKeys(customerDataSet.ids(1,2,3))
- .bin("name").get()
- .bin("map").onMapKeyRange(5, 10).getKeysAndValues()
- .update(customerDataSet.ids(1))
- .bin("age").add(1)
- .execute();
-
- rsStream = session
- .update(customerDataSet.ids(1,2,3))
- .bin("age").add(1)
- .bin("updated").setTo(true)
- .where("$.age < 21")
- .delete(customerDataSet.ids(11,12,13,14,15))
- .update(customerDataSet.ids(5,6,7))
- .bin("luckyWinner").setTo("true")
- .defaultWhere("$.updated == false")
- .execute();
+ System.err.println("Expected query with bin operations to throw an exception for now");
+ }
+ catch (BinOpInvalidException boie) {
+ // Do nothing, expected.
+ }
+ // -----------------------
+ // Object mapping (nested)
+ // -----------------------
+ Address address1 = new Address("123 Main St", "Denver", "CO", "USA", "80000");
+ Customer customer = new Customer(1, "Bob", 37, new Date(), null);
+ session.replace(customerDataSet).object(customer).using(customerMapper).execute();
+ session.upsert(customerDataSet.id(customer.getId())).bin("addrs").onMapKey("home").upsert(address1, new AddressMapper());
+
+ return readCustomer;
+ }
- rsStream = session.queryTypedKeys(customerDataSet.ids(1,2,3))
- .limit(2)
- .update(customerDataSet.ids(4,5,6))
- .bin("name").setTo("bob")
- .expireRecordAfterSeconds(500)
- .query(customerDataSet.id(7))
+ private void demonstrateGenerationCheck(Session session, TypedDataSet customerDataSet, Customer readCustomer) {
+ // ----------------
+ // Generation check
+ // ----------------
+ System.out.println("\n--- Generation check test ----");
+
+ RecordStream data = session.query(customerDataSet.id(999)).execute().asUntypedRecordStream();
+ data.getFirst().ifPresent(keyRecord -> {
+ int generation = keyRecord.recordOrThrow().generation;
+ System.out.println(" Read record with generation of " + generation);
+ session.update(customerDataSet.id(999))
+ .bin("gen").setTo(generation)
+ .ensureGenerationIs(generation)
.execute();
-
- //AEL: Clean, readable syntax
- RecordStream stream = session.query(users)
- .where("$.status == 'active' and $.age >= 21")
- .execute();
- stream.forEach(res -> {
- if (res.isOk()) {
- Record row = res.recordOrThrow();
- // Process row
- }
- });
- stream.close();
-
- // --------------------
- // Object mapping
- // --------------------
- // Insert then read back a customer with an address
- System.out.println("\n--- Object mapping test ----");
- Customer sampleCust = new Customer(999, "sample", 456, new Date(), new Address(3, "123 Main St", "Denver", "CO", "USA", "80112"));
- System.out.println("Reference customer: " + sampleCust);
-
- session.delete(customerDataSet.id(999)).execute();
- session.insert(customerDataSet).object(sampleCust).execute();
- Customer readCustomer = session.query(customerDataSet.id(999)).execute().toObjectList().get(0);
- System.out.println("Customer read back: " + readCustomer);
- System.out.println("--- End object mapping test ----");
+ System.out.println(" First update was successful");
try {
- session.query(customerDataSet).where("$.name == 'Tim'")
- .bin("fred").get()
+ // Second update should fail with a generation exception
+ session.update(customerDataSet)
+ .object(readCustomer)
+ .ensureGenerationIs(generation)
.execute();
- System.err.println("Expected query with bin operations to throw an exception for now");
+ System.out.println(" Second update was successful -- this is an error");
+
}
- catch (BinOpInvalidException boie) {
- // Do nothing, expected.
+ catch (AerospikeException ae) {
+ System.out.println(" Second update failed as expected");
+ System.out.println(ae.getResultCode() == ResultCode.GENERATION_ERROR);
}
- // -----------------------
- // Object mapping (nested)
- // -----------------------
- Address address1 = new Address(5, "123 Main St", "Denver", "CO", "USA", "80000");
- Customer customer = new Customer(1, "Bob", 37, new Date(), null);
- session.replace(customerDataSet).object(customer).execute();
- session.upsert(customerDataSet.id(customer.getId())).bin("addrs").onMapKey("home").upsert(address1, new AddressMapper());
-
- // ----------------
- // Generation check
- // ----------------
- System.out.println("\n--- Generation check test ----");
-
- RecordStream data = session.query(customerDataSet.id(999)).execute().asUntypedRecordStream();
- data.getFirst().ifPresent(keyRecord -> {
- int generation = keyRecord.recordOrThrow().generation;
- System.out.println(" Read record with generation of " + generation);
- session.update(customerDataSet.id(999))
- .bin("gen").setTo(generation)
- .ensureGenerationIs(generation)
- .execute();
- System.out.println(" First update was successful");
-
- try {
- // Second update should fail with a generation exception
- session.update(customerDataSet)
- .object(readCustomer)
- .ensureGenerationIs(generation)
- .execute();
- System.out.println(" Second update was successful -- this is an error");
-
- }
- catch (AerospikeException ae) {
- System.out.println(" Second update failed as expected");
- System.out.println(ae.getResultCode() == ResultCode.GENERATION_ERROR);
- }
- });
-
- // ----------------------------
- // Complex CDT operations (Key 500 via untyped DataSet → RecordStream)
- // ----------------------------
- System.out.println("\n--- Complex CDT operations ---");
- session.delete(cdtDemoRecords.id(500)).execute();
- session.upsert(cdtDemoRecords.id(500))
- .bin("name").setTo("CDT-Test")
- .bin("scores").setTo(List.of(95, 82, 73, 88, 91))
- .bin("tags").setTo(List.of("java", "python", "rust"))
- .bin("inventory").setTo(Map.of(
- "apples", 10,
- "bananas", 5,
- "cherries", 20
- ))
- .bin("nested").setTo(Map.of(
- "team1", Map.of("members", List.of("Alice", "Bob", "Charlie")),
- "team2", Map.of("members", List.of("Dave", "Eve"))
- ))
- .execute();
-
- // --- Read-only operations via query path (top-level) ---
-
- RecordStream cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("scores").listSize()
- .execute();
- System.out.println("List size of 'scores': " + cdtResults.getFirst());
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("inventory").mapSize()
- .execute();
- System.out.println("Map size of 'inventory': " + cdtResults.getFirst());
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("scores").listGet(0)
- .execute();
- System.out.println("First score: " + cdtResults.getFirst());
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("scores").listGetRange(1, 3)
- .execute();
- System.out.println("Scores [1..3]: " + cdtResults.getFirst());
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("scores").listGetRange(3)
- .execute();
- System.out.println("Scores from index 3 onward: " + cdtResults.getFirst());
-
- // --- Read-only operations via query path with CDT navigation ---
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team1").onMapKey("members").listSize()
- .execute();
- System.out.println("Team1 member count: " + cdtResults.getFirst());
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team1").onMapKey("members").listGet(1)
- .execute();
- System.out.println("Team1 second member: " + cdtResults.getFirst());
-
- cdtResults = session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team2").onMapKey("members").listGetRange(0, 2)
- .execute();
- System.out.println("Team2 members [0..1]: " + cdtResults.getFirst());
-
- // --- Write operations: list mutations ---
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listAppendItems(List.of(77, 65, 99))
- .execute();
- System.out.println("After listAppendItems: " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("tags").listInsert(1, "go")
- .execute();
- System.out.println("After listInsert(1, 'go'): " +
- session.query(cdtDemoRecords.id(500)).bin("tags").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("tags").listSet(0, "kotlin")
- .execute();
- System.out.println("After listSet(0, 'kotlin'): " +
- session.query(cdtDemoRecords.id(500)).bin("tags").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listInsertItems(2, List.of(100, 200))
- .execute();
- System.out.println("After listInsertItems(2, [100, 200]): " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listIncrement(0, 5)
- .execute();
- System.out.println("After listIncrement(0, 5): " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listSort()
- .execute();
- System.out.println("After listSort: " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listRemove(0)
- .execute();
- System.out.println("After listRemove(0): " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listRemoveRange(4, 2)
- .execute();
- System.out.println("After listRemoveRange(4, 2): " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- cdtResults = session.update(cdtDemoRecords.id(500))
- .bin("scores").listPop(0)
- .execute();
- System.out.println("Popped element: " + cdtResults.getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("scores").listTrim(0, 3)
- .execute();
- System.out.println("After listTrim(0, 3): " +
- session.query(cdtDemoRecords.id(500)).bin("scores").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("tags").listClear()
- .execute();
- System.out.println("After listClear on tags: " +
- session.query(cdtDemoRecords.id(500)).bin("tags").get().execute().getFirst());
-
- // --- Write operations: map mutations ---
-
- session.update(cdtDemoRecords.id(500))
- .bin("inventory").mapUpsertItems(Map.of("dates", 15, "elderberries", 8))
- .execute();
- System.out.println("After mapUpsertItems: " +
- session.query(cdtDemoRecords.id(500)).bin("inventory").get().execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("inventory").mapSetPolicy(MapOrder.KEY_ORDERED)
- .execute();
- System.out.println("After mapSetPolicy(KEY_ORDERED): " +
- session.query(cdtDemoRecords.id(500)).bin("inventory").get().execute().getFirst());
-
- // --- Write operations: CDT navigation with new methods ---
-
- session.update(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team1").onMapKey("members").listAppend("Diana")
- .execute();
- System.out.println("After nested listAppend('Diana') to team1: " +
- session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team1").onMapKey("members").listSize()
- .execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team2").onMapKey("members").listInsert(0, "Zara")
- .execute();
- System.out.println("After nested listInsert to team2: " +
- session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team2").onMapKey("members").listGetRange(0)
- .execute().getFirst());
-
- session.update(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team1").onMapKey("members").listSort()
- .execute();
- System.out.println("After sorting team1 members: " +
- session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team1").onMapKey("members").listGetRange(0)
- .execute().getFirst());
-
- // Create a new ordered list sub-element via CDT navigation
- session.update(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team3", MapOrder.KEY_ORDERED)
- .onMapKey("members").listCreate(ListOrder.ORDERED)
- .execute();
- session.update(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team3").onMapKey("members").listAddItems(List.of("Ivy", "Frank", "Grace"))
- .execute();
- System.out.println("Team3 members (ordered): " +
- session.query(cdtDemoRecords.id(500))
- .bin("nested").onMapKey("team3").onMapKey("members").listGetRange(0)
- .execute().getFirst());
-
- // --- Combined multi-bin CDT operations in one call ---
+ });
+ }
- cdtResults = session.update(cdtDemoRecords.id(500))
- .bin("scores").listAppendItems(List.of(50, 60, 70))
- .bin("inventory").mapUpsertItems(Map.of("figs", 12))
- .bin("nested").onMapKey("team2").onMapKey("members").listAppend("Quinn")
+ private void demonstrateComplexCdt(Session session, DataSet customerDataSet) {
+ // ----------------------------
+ // Complex CDT operations
+ // ----------------------------
+ System.out.println("\n--- Complex CDT operations ---");
+
+ session.delete(customerDataSet.id(500)).execute();
+ session.upsert(customerDataSet.id(500))
+ .bin("name").setTo("CDT-Test")
+ .bin("scores").setTo(List.of(95, 82, 73, 88, 91))
+ .bin("tags").setTo(List.of("java", "python", "rust"))
+ .bin("inventory").setTo(Map.of(
+ "apples", 10,
+ "bananas", 5,
+ "cherries", 20
+ ))
+ .bin("nested").setTo(Map.of(
+ "team1", Map.of("members", List.of("Alice", "Bob", "Charlie")),
+ "team2", Map.of("members", List.of("Dave", "Eve"))
+ ))
+ .execute();
+
+ // --- Read-only operations via query path (top-level) ---
+
+ RecordStream cdtResults = session.query(customerDataSet.id(500))
+ .bin("scores").listSize()
+ .execute();
+ System.out.println("List size of 'scores': " + cdtResults.getFirst());
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("inventory").mapSize()
+ .execute();
+ System.out.println("Map size of 'inventory': " + cdtResults.getFirst());
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("scores").listGet(0)
+ .execute();
+ System.out.println("First score: " + cdtResults.getFirst());
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("scores").listGetRange(1, 3)
+ .execute();
+ System.out.println("Scores [1..3]: " + cdtResults.getFirst());
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("scores").listGetRange(3)
+ .execute();
+ System.out.println("Scores from index 3 onward: " + cdtResults.getFirst());
+
+ // --- Read-only operations via query path with CDT navigation ---
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("nested").onMapKey("team1").onMapKey("members").listSize()
+ .execute();
+ System.out.println("Team1 member count: " + cdtResults.getFirst());
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("nested").onMapKey("team1").onMapKey("members").listGet(1)
+ .execute();
+ System.out.println("Team1 second member: " + cdtResults.getFirst());
+
+ cdtResults = session.query(customerDataSet.id(500))
+ .bin("nested").onMapKey("team2").onMapKey("members").listGetRange(0, 2)
+ .execute();
+ System.out.println("Team2 members [0..1]: " + cdtResults.getFirst());
+
+ // --- Write operations: list mutations ---
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listAppendItems(List.of(77, 65, 99))
+ .execute();
+ System.out.println("After listAppendItems: " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("tags").listInsert(1, "go")
+ .execute();
+ System.out.println("After listInsert(1, 'go'): " +
+ session.query(customerDataSet.id(500)).bin("tags").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("tags").listSet(0, "kotlin")
+ .execute();
+ System.out.println("After listSet(0, 'kotlin'): " +
+ session.query(customerDataSet.id(500)).bin("tags").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listInsertItems(2, List.of(100, 200))
+ .execute();
+ System.out.println("After listInsertItems(2, [100, 200]): " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listIncrement(0, 5)
+ .execute();
+ System.out.println("After listIncrement(0, 5): " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listSort()
+ .execute();
+ System.out.println("After listSort: " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listRemove(0)
+ .execute();
+ System.out.println("After listRemove(0): " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listRemoveRange(4, 2)
+ .execute();
+ System.out.println("After listRemoveRange(4, 2): " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ cdtResults = session.update(customerDataSet.id(500))
+ .bin("scores").listPop(0)
+ .execute();
+ System.out.println("Popped element: " + cdtResults.getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("scores").listTrim(0, 3)
+ .execute();
+ System.out.println("After listTrim(0, 3): " +
+ session.query(customerDataSet.id(500)).bin("scores").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("tags").listClear()
+ .execute();
+ System.out.println("After listClear on tags: " +
+ session.query(customerDataSet.id(500)).bin("tags").get().execute().getFirst());
+
+ // --- Write operations: map mutations ---
+
+ session.update(customerDataSet.id(500))
+ .bin("inventory").mapUpsertItems(Map.of("dates", 15, "elderberries", 8))
+ .execute();
+ System.out.println("After mapUpsertItems: " +
+ session.query(customerDataSet.id(500)).bin("inventory").get().execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("inventory").mapSetPolicy(MapOrder.KEY_ORDERED)
+ .execute();
+ System.out.println("After mapSetPolicy(KEY_ORDERED): " +
+ session.query(customerDataSet.id(500)).bin("inventory").get().execute().getFirst());
+
+ // --- Write operations: CDT navigation with new methods ---
+
+ session.update(customerDataSet.id(500))
+ .bin("nested").onMapKey("team1").onMapKey("members").listAppend("Diana")
+ .execute();
+ System.out.println("After nested listAppend('Diana') to team1: " +
+ session.query(customerDataSet.id(500))
.bin("nested").onMapKey("team1").onMapKey("members").listSize()
- .execute();
- System.out.println("Combined CDT result: " + cdtResults.getFirst());
-
- System.out.println("Final state: " +
- session.query(cdtDemoRecords.id(500)).execute().getFirst());
- System.out.println("--- End Complex CDT operations ---");
-
- // ---------------------------
- // Bit (BLOB) operations
- // ---------------------------
- System.out.println("\n--- Bit (BLOB) operations ---");
-
- Key bitKey = cdtDemoRecords.id(501);
- session.delete(bitKey).execute();
-
- session.upsert(bitKey)
- .bin("flags").setTo(new byte[] {0x01, 0x42})
- .execute();
-
- session.update(bitKey)
- .bin("flags").bitResize(4)
- .bin("flags").bitSet(8, 8, new byte[] {(byte) 0xFF})
- .bin("flags").bitOr(0, 16, new byte[] {(byte) 0x0F, (byte) 0xF0})
- .execute();
-
- RecordStream bitRead = session.query(bitKey)
- .bin("flags").bitGet(0, 8)
- .bin("flags").bitCount(0, 32)
- .execute();
- System.out.println("First byte + set-bit count: " + bitRead.getFirst());
-
- RecordStream intRead = session.query(bitKey)
- .bin("flags").bitGetInt(0, 16, false)
- .execute();
- System.out.println("UInt16 at bit 0: " + intRead.getFirst());
-
- session.query(bitKey)
- .bin("flags").bitLscan(0, 32, true)
- .bin("flags").bitRscan(0, 32, true)
- .execute()
- .forEach(rr -> System.out.println("Scan result: " + rr));
-
- session.update(bitKey)
- .bin("flags").bitSetInt(16, 16, 100)
- .bin("flags").bitAdd(16, 16, 1, false, BitOverflowAction.WRAP)
- .execute();
-
- System.out.println("After bitSetInt/bitAdd: " +
- session.query(bitKey).bin("flags").bitGetInt(16, 16, false).execute().getFirst());
-
- session.update(bitKey)
- .bin("flags").bitLshift(0, 8, 1)
- .bin("flags").bitNot(8, 8)
- .execute();
-
- session.update(bitKey)
- .bin("flags").bitInsert(1, new byte[] {0x11, 0x22})
- .bin("flags").bitRemove(3, 1)
- .execute();
+ .execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("nested").onMapKey("team2").onMapKey("members").listInsert(0, "Zara")
+ .execute();
+ System.out.println("After nested listInsert to team2: " +
+ session.query(customerDataSet.id(500))
+ .bin("nested").onMapKey("team2").onMapKey("members").listGetRange(0)
+ .execute().getFirst());
+
+ session.update(customerDataSet.id(500))
+ .bin("nested").onMapKey("team1").onMapKey("members").listSort()
+ .execute();
+ System.out.println("After sorting team1 members: " +
+ session.query(customerDataSet.id(500))
+ .bin("nested").onMapKey("team1").onMapKey("members").listGetRange(0)
+ .execute().getFirst());
+
+ // Create a new ordered list sub-element via CDT navigation
+ session.update(customerDataSet.id(500))
+ .bin("nested").onMapKey("team3", MapOrder.KEY_ORDERED)
+ .onMapKey("members").listCreate(ListOrder.ORDERED)
+ .execute();
+ session.update(customerDataSet.id(500))
+ .bin("nested").onMapKey("team3").onMapKey("members").listAddItems(List.of("Ivy", "Frank", "Grace"))
+ .execute();
+ System.out.println("Team3 members (ordered): " +
+ session.query(customerDataSet.id(500))
+ .bin("nested").onMapKey("team3").onMapKey("members").listGetRange(0)
+ .execute().getFirst());
+
+ // --- Combined multi-bin CDT operations in one call ---
+
+ cdtResults = session.update(customerDataSet.id(500))
+ .bin("scores").listAppendItems(List.of(50, 60, 70))
+ .bin("inventory").mapUpsertItems(Map.of("figs", 12))
+ .bin("nested").onMapKey("team2").onMapKey("members").listAppend("Quinn")
+ .bin("nested").onMapKey("team1").onMapKey("members").listSize()
+ .execute();
+ System.out.println("Combined CDT result: " + cdtResults.getFirst());
+
+ System.out.println("Final state: " +
+ session.query(customerDataSet.id(500)).execute().getFirst());
+ System.out.println("--- End Complex CDT operations ---");
+
+ session.upsert(customerDataSet.id(1))
+ .bin("test").onMapKeyRange(5, SpecialValue.INFINITY).getKeys()
+ .execute();
+ }
- System.out.println("Final flags blob: " +
- session.query(bitKey).bin("flags").get().execute().getFirst());
- System.out.println("--- End Bit (BLOB) operations ---");
+ private void demonstrateBitOperations(Session session, DataSet records) {
+ System.out.println("\n--- Bit (BLOB) operations ---");
+ Key bitKey = records.id(501);
+ session.delete(bitKey).execute();
+ session.upsert(bitKey)
+ .bin("flags").setTo(new byte[] {0x01, 0x42})
+ .execute();
+
+ session.update(bitKey)
+ .bin("flags").bitResize(4)
+ .bin("flags").bitSet(8, 8, new byte[] {(byte) 0xFF})
+ .bin("flags").bitOr(0, 16, new byte[] {(byte) 0x0F, (byte) 0xF0})
+ .execute();
+
+ RecordStream bitRead = session.query(bitKey)
+ .bin("flags").bitGet(0, 8)
+ .bin("flags").bitCount(0, 32)
+ .execute();
+ System.out.println("First byte + set-bit count: " + bitRead.getFirst());
+
+ RecordStream intRead = session.query(bitKey)
+ .bin("flags").bitGetInt(0, 16, false)
+ .execute();
+ System.out.println("UInt16 at bit 0: " + intRead.getFirst());
+
+ session.query(bitKey)
+ .bin("flags").bitLscan(0, 32, true)
+ .bin("flags").bitRscan(0, 32, true)
+ .execute()
+ .forEach(rr -> System.out.println("Scan result: " + rr));
+
+ session.update(bitKey)
+ .bin("flags").bitSetInt(16, 16, 100)
+ .bin("flags").bitAdd(16, 16, 1, false, BitOverflowAction.WRAP)
+ .execute();
+
+ System.out.println("After bitSetInt/bitAdd: " +
+ session.query(bitKey).bin("flags").bitGetInt(16, 16, false).execute().getFirst());
+
+ session.update(bitKey)
+ .bin("flags").bitLshift(0, 8, 1)
+ .bin("flags").bitNot(8, 8)
+ .execute();
+
+ session.update(bitKey)
+ .bin("flags").bitInsert(1, new byte[] {0x11, 0x22})
+ .bin("flags").bitRemove(3, 1)
+ .execute();
+ System.out.println("Final flags blob: " +
+ session.query(bitKey).bin("flags").get().execute().getFirst());
+ System.out.println("--- End Bit (BLOB) operations ---");
+ }
- session.upsert(customerDataSet.id(1))
- .bin("test").onMapKeyRange(5, SpecialValue.INFINITY).getKeys()
- .execute();
-
- // --- hetrogeneous batch call with object reads ---
- session.upsert(addressDataSet).object(new Address(1, "123 Main St", "Denver", "CO", "USA", "80000")).execute();
-
- rs = session.query(customerDataSet.ids(21,22,23))
- .query(addressDataSet.id(1))
- .execute();
- System.out.println("\n--- Hetrogeneous batch example ---\n");
- while (rs.hasNext()) {
- RecordResult rr = rs.next();
- System.out.println((Object)rr.toObject());
- }
+ private void demonstrateHeterogeneousBatch(Session session, TypedDataSet customerDataSet,
+ TypedDataSet addressDataSet) {
+ session.upsert(addressDataSet.id(1))
+ .bin("line1").setTo("123 Main St")
+ .bin("city").setTo("Denver")
+ .bin("state").setTo("CO")
+ .bin("country").setTo("USA")
+ .bin("zip").setTo("80000")
+ .execute();
+
+ RecordStream results = session.queryTypedKeys(customerDataSet.ids(21, 22, 23))
+ .query(addressDataSet.id(1))
+ .execute();
+ System.out.println("\n--- Heterogeneous batch example ---");
+ results.forEach(rr -> System.out.println((Object) rr.toObject()));
+ }
- System.out.println("\n--- Async CompletableFuture examples ---\n");
- List customersAsync = session.query(customerDataSet.ids(1, 2, 3))
- .executeAsync(ErrorStrategy.IN_STREAM)
- .asCompletableFutureMapped()
- .join();
- customersAsync.forEach(c -> System.out.println(c));
-
- Optional customerAsync = session.query(customerDataSet.id(1))
- .executeAsync(ErrorStrategy.IN_STREAM)
- .asCompletableFutureMappedSingle()
- .join();
- customerAsync.ifPresentOrElse(
- c -> System.out.println("Single customer: " + c),
- () -> System.out.println("Customer id=1 not found"));
- }
+ private void demonstrateAsyncMapping(Session session, TypedDataSet customerDataSet) {
+ System.out.println("\n--- Async CompletableFuture examples ---");
+ List customers = session.queryTypedKeys(customerDataSet.ids(1, 2, 3))
+ .executeAsync(ErrorStrategy.IN_STREAM)
+ .asCompletableFutureMapped()
+ .join();
+ customers.forEach(System.out::println);
+
+ Optional customer = session.query(customerDataSet.id(1))
+ .executeAsync(ErrorStrategy.IN_STREAM)
+ .asCompletableFutureMappedSingle()
+ .join();
+ customer.ifPresentOrElse(
+ value -> System.out.println("Single customer: " + value),
+ () -> System.out.println("Customer id=1 not found"));
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/RosterExample.java b/examples/src/main/java/com/aerospike/examples/RosterExample.java
index b1268f92..e77fa759 100644
--- a/examples/src/main/java/com/aerospike/examples/RosterExample.java
+++ b/examples/src/main/java/com/aerospike/examples/RosterExample.java
@@ -44,26 +44,28 @@
*
*
*/
-public class RosterExample extends Example{
-
- public RosterExample(Console console) {
- super(console);
- }
+public class RosterExample extends Example {
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
+ public void runExample() throws Exception {
+ Cluster cluster = cluster();
Node[] nodes = cluster.getNodes();
- String response = Info.request(nodes[0], format("roster:namespace=%s", args.namespace));
+ String response = Info.request(nodes[0], format("roster:namespace=%s", namespace()));
System.out.printf("Current roster: %s\n", response);
+ if (response == null || response.toLowerCase().contains("error") || !response.contains("observed_nodes=")) {
+ throw new ExampleSkipException(
+ "Roster commands require a strong-consistency namespace with observed_nodes; response: " + response);
+ }
+
// Parse observed nodes from response
String observedNodes = parseObservedNodes(response);
System.out.printf("Observed nodes: %s\n", observedNodes);
for (Node node : nodes) {
String setResponse = Info.request(node,
- format("roster-set:namespace=%s;nodes=%s", args.namespace, observedNodes));
+ format("roster-set:namespace=%s;nodes=%s", namespace(), observedNodes));
System.out.printf("roster-set on %s: %s\n", node.getHost(), setResponse);
}
diff --git a/examples/src/main/java/com/aerospike/examples/StringOperationsExample.java b/examples/src/main/java/com/aerospike/examples/StringOperationsExample.java
index 560e2f4a..b585ed53 100644
--- a/examples/src/main/java/com/aerospike/examples/StringOperationsExample.java
+++ b/examples/src/main/java/com/aerospike/examples/StringOperationsExample.java
@@ -39,22 +39,17 @@
*/
public class StringOperationsExample extends Example {
- public StringOperationsExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster cluster, Args args) throws Exception {
+ public void runExample() throws Exception {
+ Cluster cluster = cluster();
Version v = cluster.getRandomNode().getVersion();
if (!v.isGreaterOrEqual(Version.SERVER_VERSION_8_1_3)) {
- console.info("StringOperationsExample: skipping (server is " + v
- + "; string ops require 8.1.3+).");
- return;
+ throw new ExampleSkipException(
+ "server is " + v + "; string operations require 8.1.3+");
}
Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet set = DataSet.of(args.namespace, "string-ops-demo");
- session.truncate(set);
+ DataSet set = dataSet("string-ops-demo");
Key key = set.id("row1");
console.info("--- 1) Fluent BinBuilder: strlen, substr [1,4), substr from 3, find, upper ---");
diff --git a/examples/src/main/java/com/aerospike/examples/StudentScoresExample.java b/examples/src/main/java/com/aerospike/examples/StudentScoresExample.java
index a616439e..168762d3 100644
--- a/examples/src/main/java/com/aerospike/examples/StudentScoresExample.java
+++ b/examples/src/main/java/com/aerospike/examples/StudentScoresExample.java
@@ -4,15 +4,14 @@
import java.util.Map;
import java.util.Random;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
-public class StudentScoresExample {
+public class StudentScoresExample extends Example {
private static final String[] SUBJECTS = {"math", "english", "science", "history", "art"};
- private static final Random random = new Random(42); // fixed seed for reproducibility
- private static Map generateScores(int studentNum) {
+
+ private static Map generateScores(Random random) {
Map scores = new HashMap<>();
for (String subject : SUBJECTS) {
scores.put(subject, 55 + random.nextInt(46)); // scores between 55 and 100
@@ -20,30 +19,26 @@ private static Map generateScores(int studentNum) {
return scores;
}
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments)
- .withNativeCredentials("admin", "admin123")
- .connect()) {
-
- Session session = cluster.createSession(Behavior.DEFAULT);
- DataSet class10a = DataSet.of("test", "class10a");
- // -- Write 30 student records --
- for (int i = 1; i <= 30; i++) {
- session.upsert(class10a.id("student-" + i))
- .bin("name").setTo("Student " + i)
- .bin("scores").setTo(generateScores(i))
- .execute();
- }
-
- // -- Query: students with any score >= 90 --
- session.query(class10a)
- .where("$.scores.{=90:}.count() > 0")
- .execute()
- .forEach(r -> System.out.printf("%s: %s%n",
- r.recordOrThrow().getString("name"),
- r.recordOrThrow().getMap("scores")));
+ @Override
+ public void runExample() {
+ Session session = cluster().createSession(Behavior.DEFAULT);
+ DataSet class10a = dataSet("class10a");
+ Random random = new Random(42);
+ // -- Write 30 student records --
+ for (int i = 1; i <= 30; i++) {
+ session.upsert(class10a.id("student-" + i))
+ .bin("name").setTo("Student " + i)
+ .bin("scores").setTo(generateScores(random))
+ .execute();
}
+
+ // -- Query: students with any score >= 90 --
+ session.query(class10a)
+ .where("$.scores.{=90:}.count() > 0")
+ .execute()
+ .forEach(r -> System.out.printf("%s: %s%n",
+ r.recordOrThrow().getString("name"),
+ r.recordOrThrow().getMap("scores")));
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/TransactionProcessingExample.java b/examples/src/main/java/com/aerospike/examples/TransactionProcessingExample.java
index 0f2d69a7..ec99b75e 100644
--- a/examples/src/main/java/com/aerospike/examples/TransactionProcessingExample.java
+++ b/examples/src/main/java/com/aerospike/examples/TransactionProcessingExample.java
@@ -18,7 +18,6 @@
import java.util.Map;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DefaultRecordMappingFactory;
import com.aerospike.client.sdk.Key;
import com.aerospike.client.sdk.RecordMapper;
@@ -27,7 +26,7 @@
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.util.MapUtil;
-public class TransactionProcessingExample {
+public class TransactionProcessingExample extends Example {
public static class Transaction {
private String id;
private String desc;
@@ -182,116 +181,114 @@ public Object id(Account acct) {
}
}
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments).connect()) {
- Session session = cluster.createSession(Behavior.DEFAULT);
+ @Override
+ public void runExample() {
+ CustomerMapper customerMapper = new CustomerMapper();
+ AccountMapper accountMapper = new AccountMapper();
+ TransactionMapper txnMapper = new TransactionMapper();
- TypedDataSet customerDataSet = TypedDataSet.of("test", "customers", Customer.class);
- TypedDataSet accountDataSet = TypedDataSet.of("test", "accounts", Account.class);
- TypedDataSet txnDataSet = TypedDataSet.of("test", "txns", Transaction.class);
+ cluster().setRecordMappingFactory(DefaultRecordMappingFactory.of(
+ Customer.class, customerMapper,
+ Account.class, accountMapper,
+ Transaction.class, txnMapper));
- CustomerMapper customerMapper = new CustomerMapper();
- AccountMapper accountMapper = new AccountMapper();
- TransactionMapper txnMapper = new TransactionMapper();
+ Session session = cluster().createSession(Behavior.DEFAULT);
- cluster.setRecordMappingFactory(DefaultRecordMappingFactory.of(
- Customer.class, customerMapper,
- Account.class, accountMapper,
- Transaction.class, txnMapper));
+ TypedDataSet customerDataSet = TypedDataSet.of(namespace(), "customers", Customer.class);
+ TypedDataSet accountDataSet = TypedDataSet.of(namespace(), "accounts", Account.class);
+ TypedDataSet txnDataSet = TypedDataSet.of(namespace(), "txns", Transaction.class);
- Customer customer = new Customer();
- customer.setCustomerId("CUST-10042");
- customer.setFirstName("Jane");
- customer.setLastName("Morrison");
- customer.setEmail("jane.morrison@example.com");
- customer.setPhone("+1-555-867-5309");
- customer.setTotalSpendInCents(0);
- customer.setStatusLevel("BRONZE");
+ Customer customer = new Customer();
+ customer.setCustomerId("CUST-10042");
+ customer.setFirstName("Jane");
+ customer.setLastName("Morrison");
+ customer.setEmail("jane.morrison@example.com");
+ customer.setPhone("+1-555-867-5309");
+ customer.setTotalSpendInCents(0);
+ customer.setStatusLevel("BRONZE");
- session.insert(customerDataSet)
- .object(customer)
- .execute();
+ session.insert(customerDataSet)
+ .object(customer)
+ .execute();
- Account account = new Account();
- account.setPan("4532015112830366");
- account.setCustomerId(customer.getCustomerId());
- account.setExpiryDate("03/28");
- account.setBalanceInCents(0);
- account.setCreditLimitInCents(500000);
- account.setStatus("ACTIVE");
+ Account account = new Account();
+ account.setPan("4532015112830366");
+ account.setCustomerId(customer.getCustomerId());
+ account.setExpiryDate("03/28");
+ account.setBalanceInCents(0);
+ account.setCreditLimitInCents(500000);
+ account.setStatus("ACTIVE");
- session.insert(accountDataSet)
- .object(account)
- .execute();
+ session.insert(accountDataSet)
+ .object(account)
+ .execute();
- Transaction txn = new Transaction();
- txn.setId("TXN-00001");
- txn.setDesc("Car repairs");
- txn.setAmountInCents(45000);
- txn.setDate(System.currentTimeMillis());
+ Transaction txn = new Transaction();
+ txn.setId("TXN-00001");
+ txn.setDesc("Car repairs");
+ txn.setAmountInCents(45000);
+ txn.setDate(System.currentTimeMillis());
- // ======================================================================
- // Old style: Standard Aerospike Java Client
- // ======================================================================
- //
- // Key txnKey = new Key("test", "txns", txn.getId());
- // Key accountKey = new Key("test", "accounts", account.getPan());
- // Key customerKey = new Key("test", "customers", customer.getCustomerId());
- //
- // BatchWritePolicy insertPolicy = new BatchWritePolicy();
- // insertPolicy.recordExistsAction = RecordExistsAction.CREATE_ONLY;
- //
- // BatchWritePolicy updatePolicy = new BatchWritePolicy();
- // updatePolicy.recordExistsAction = RecordExistsAction.UPDATE_ONLY;
- //
- // Expression statusExp = Exp.build(
- // Exp.cond(
- // Exp.gt(Exp.intBin("totalSpend"), Exp.val(100000)), Exp.val("PLATINUM"),
- // Exp.gt(Exp.intBin("totalSpend"), Exp.val(10000)), Exp.val("GOLD"),
- // Exp.gt(Exp.intBin("totalSpend"), Exp.val(100)), Exp.val("SILVER"),
- // Exp.val("BRONZE")
- // )
- // );
- //
- // List batchRecords = new ArrayList<>();
- //
- // batchRecords.add(new BatchWrite(
- // insertPolicy, txnKey,
- // Operation.put(new Bin("id", txn.getId())),
- // Operation.put(new Bin("desc", txn.getDesc())),
- // Operation.put(new Bin("amountInCents", txn.getAmountInCents())),
- // Operation.put(new Bin("date", txn.getDate()))
- // ));
- //
- // batchRecords.add(new BatchWrite(
- // updatePolicy, accountKey,
- // Operation.add(new Bin("balanceCents", txn.getAmountInCents()))
- // ));
- //
- // batchRecords.add(new BatchWrite(
- // updatePolicy, customerKey,
- // Operation.add(new Bin("totalSpend", txn.getAmountInCents())),
- // ExpOperation.write("statusLevel", statusExp, ExpWriteFlags.DEFAULT)
- // ));
- //
- // client.operate(new BatchPolicy(), batchRecords);
+ // ======================================================================
+ // Old style: Standard Aerospike Java Client
+ // ======================================================================
+ //
+ // Key txnKey = new Key("test", "txns", txn.getId());
+ // Key accountKey = new Key("test", "accounts", account.getPan());
+ // Key customerKey = new Key("test", "customers", customer.getCustomerId());
+ //
+ // BatchWritePolicy insertPolicy = new BatchWritePolicy();
+ // insertPolicy.recordExistsAction = RecordExistsAction.CREATE_ONLY;
+ //
+ // BatchWritePolicy updatePolicy = new BatchWritePolicy();
+ // updatePolicy.recordExistsAction = RecordExistsAction.UPDATE_ONLY;
+ //
+ // Expression statusExp = Exp.build(
+ // Exp.cond(
+ // Exp.gt(Exp.intBin("totalSpend"), Exp.val(100000)), Exp.val("PLATINUM"),
+ // Exp.gt(Exp.intBin("totalSpend"), Exp.val(10000)), Exp.val("GOLD"),
+ // Exp.gt(Exp.intBin("totalSpend"), Exp.val(100)), Exp.val("SILVER"),
+ // Exp.val("BRONZE")
+ // )
+ // );
+ //
+ // List batchRecords = new ArrayList<>();
+ //
+ // batchRecords.add(new BatchWrite(
+ // insertPolicy, txnKey,
+ // Operation.put(new Bin("id", txn.getId())),
+ // Operation.put(new Bin("desc", txn.getDesc())),
+ // Operation.put(new Bin("amountInCents", txn.getAmountInCents())),
+ // Operation.put(new Bin("date", txn.getDate()))
+ // ));
+ //
+ // batchRecords.add(new BatchWrite(
+ // updatePolicy, accountKey,
+ // Operation.add(new Bin("balanceCents", txn.getAmountInCents()))
+ // ));
+ //
+ // batchRecords.add(new BatchWrite(
+ // updatePolicy, customerKey,
+ // Operation.add(new Bin("totalSpend", txn.getAmountInCents())),
+ // ExpOperation.write("statusLevel", statusExp, ExpWriteFlags.DEFAULT)
+ // ));
+ //
+ // client.operate(new BatchPolicy(), batchRecords);
- // ======================================================================
- // New style: API
- // ======================================================================
- session
- .insert(txnDataSet)
- .object(txn)
- .update(accountDataSet.id(account.getPan()))
- .bin("balanceCents").add(txn.getAmountInCents())
- .update(customerDataSet.id(customer.getCustomerId()))
- .bin("totalSpend").add(txn.getAmountInCents())
- .bin("statusLevel").upsertFrom("when ($.totalSpend > 100000 => 'PLATINUM', "
- + "$.totalSpend > 10000 => 'GOLD', "
- + "$.totalSpend > 100 => 'SILVER', "
- + "default => 'BRONZE')")
- .execute();
- }
+ // ======================================================================
+ // New style: API
+ // ======================================================================
+ session
+ .insert(txnDataSet)
+ .object(txn)
+ .update(accountDataSet.id(account.getPan()))
+ .bin("balanceCents").add(txn.getAmountInCents())
+ .update(customerDataSet.id(customer.getCustomerId()))
+ .bin("totalSpend").add(txn.getAmountInCents())
+ .bin("statusLevel").upsertFrom("when ($.totalSpend > 100000 => 'PLATINUM', "
+ + "$.totalSpend > 10000 => 'GOLD', "
+ + "$.totalSpend > 100 => 'SILVER', "
+ + "default => 'BRONZE')")
+ .execute();
}
}
diff --git a/examples/src/main/java/com/aerospike/examples/TypedMappingExamples.java b/examples/src/main/java/com/aerospike/examples/TypedMappingExamples.java
index 11676183..374f7ce1 100644
--- a/examples/src/main/java/com/aerospike/examples/TypedMappingExamples.java
+++ b/examples/src/main/java/com/aerospike/examples/TypedMappingExamples.java
@@ -29,7 +29,6 @@
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.TypedDataSet;
-import com.aerospike.client.sdk.exp.Exp;
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.util.MapUtil;
@@ -43,11 +42,9 @@
* to load a related {@link Gadget} through {@link RecordReadContext#getSession()} when the
* {@code related_gadget_id} bin is set.
*
- * Connection flags match other standalone examples ({@link Args} via {@link Example#parseStandaloneArgs}):
- * default {@code localhost:3000}; {@code -h} / {@code -p} to override; optional {@code -a} for services
- * alternate. Requires namespace {@code test}.
+ * The example uses the runner-managed cluster and configured namespace.
*/
-public final class TypedMappingExamples {
+public final class TypedMappingExamples extends Example {
/** Simple inventory row stored under an integer user key. */
public static final class Widget {
@@ -157,6 +154,14 @@ public String toString() {
}
public static final class WidgetMapper implements RecordMapper {
+ private final TypedDataSet gadgets;
+ private final Console console;
+
+ public WidgetMapper(TypedDataSet gadgets, Console console) {
+ this.gadgets = gadgets;
+ this.console = console;
+ }
+
private static Widget mapBins(Map map, Key recordKey) {
Widget w = new Widget();
w.setId(recordKey.userKey.toLong());
@@ -183,7 +188,6 @@ public Widget fromMap(Map map, Key recordKey, int generation, Re
return w;
}
Session session = ctx.getSession();
- TypedDataSet gadgets = TypedDataSet.of("test", "typed_demo_gadgets", Gadget.class);
Optional peer = session.query(gadgets.id(relatedId))
.readingOnlyBins("name", "enabled")
.limit(1)
@@ -192,10 +196,10 @@ public Widget fromMap(Map map, Key recordKey, int generation, Re
.map(RecordResult::toObject);
if (peer.isPresent()) {
Gadget g = peer.get();
- System.out.println(" [WidgetMapper 4-arg fromMap] Session read of related gadget id="
+ console.info(" [WidgetMapper 4-arg fromMap] Session read of related gadget id="
+ relatedId + " -> name='" + g.getName() + "', enabled=" + g.isEnabled());
} else {
- System.out.println(" [WidgetMapper 4-arg fromMap] No gadget found for rel_gadget_id="
+ console.info(" [WidgetMapper 4-arg fromMap] No gadget found for rel_gadget_id="
+ relatedId);
}
return w;
@@ -242,74 +246,66 @@ public Object id(Gadget gadget) {
}
}
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments).connect()) {
-
- WidgetMapper widgetMapper = new WidgetMapper();
- GadgetMapper gadgetMapper = new GadgetMapper();
- cluster.setRecordMappingFactory(DefaultRecordMappingFactory.of(
- Widget.class, widgetMapper,
- Gadget.class, gadgetMapper));
-
- Session session = cluster.createSession(Behavior.DEFAULT);
-
- TypedDataSet widgets = TypedDataSet.of("test", "typed_demo_widgets", Widget.class);
- TypedDataSet gadgets = TypedDataSet.of("test", "typed_demo_gadgets", Gadget.class);
-
- session.truncate(widgets);
- session.truncate(gadgets);
-
- // Writes: factory resolves mapper from object class (no .using()).
- // Insert gadget first so widget mapper's 4-arg fromMap can resolve it via session.query.
- session.insert(gadgets).object(new Gadget(1, "notifications", true)).execute();
- session.insert(widgets).object(new Widget(1, "alpha", 10, 1L)).execute();
- session.insert(widgets).object(new Widget(2, "beta", 20)).execute();
-
- // Single-row read via typed query stream: factory only (no .using() / no mapper arg).
- // Widget id=1 has related_gadget_id=1; WidgetMapper 4-arg fromMap loads that gadget via session.
- System.out.println("Typed query getFirstObject — watch for [WidgetMapper 4-arg fromMap] line:");
- Widget one = session.query(widgets)
- .where("$.qty == 10")
- .limit(1)
- .execute()
- .getFirstObject()
- .orElseThrow();
- System.out.println("Typed query getFirstObject (factory): " + one);
-
- // Typed dataset query → TypedRecordStream: mapper-less toObjectList() (4-arg fromMap per row).
- List fromScan = session.query(widgets)
- .where("$.qty == 20")
- .execute()
- .toObjectList();
- System.out.println("Typed query (factory, qty=20 only): " + fromScan);
-
- System.out.println("Typed query toObjectList(all widgets) — id=1 triggers session read again:");
- List allWidgets = session.query(widgets).limit(10).execute().toObjectList();
- System.out.println(" " + allWidgets);
-
- Optional firstTyped = session.query(widgets)
- .where("$.label == 'alpha'")
- .execute()
- .getFirstObject();
- System.out.println("Typed query first (factory): " + firstTyped.orElseThrow());
-
- // Heterogeneous batch: each leg carries Class>; map per row with toObject() (embedded session).
- RecordStream batch = session
- .query(widgets.id(2))
- .readingOnlyBins("label", "qty")
- .query(gadgets.id(1))
- .readingOnlyBins("name", "enabled")
- .execute();
- try (batch) {
- Widget w = batch.next().toObject();
- Gadget g = batch.next().toObject();
- System.out.println("Batch widget: " + w);
- System.out.println("Batch gadget: " + g);
- }
+ @Override
+ public void runExample() throws Exception {
+ Cluster cluster = cluster();
+ TypedDataSet widgets = TypedDataSet.of(namespace(), "typed_demo_widgets", Widget.class);
+ TypedDataSet gadgets = TypedDataSet.of(namespace(), "typed_demo_gadgets", Gadget.class);
+
+ WidgetMapper widgetMapper = new WidgetMapper(gadgets, console);
+ GadgetMapper gadgetMapper = new GadgetMapper();
+ cluster.setRecordMappingFactory(DefaultRecordMappingFactory.of(
+ Widget.class, widgetMapper,
+ Gadget.class, gadgetMapper));
+
+ Session session = cluster.createSession(Behavior.DEFAULT);
+
+ // Writes: factory resolves mapper from object class (no .using()).
+ // Insert gadget first so widget mapper's 4-arg fromMap can resolve it via session.query.
+ session.insert(gadgets).object(new Gadget(1, "notifications", true)).execute();
+ session.insert(widgets).object(new Widget(1, "alpha", 10, 1L)).execute();
+ session.insert(widgets).object(new Widget(2, "beta", 20)).execute();
+
+ // Single-row read via typed query stream: factory only (no .using() / no mapper arg).
+ // Widget id=1 has related_gadget_id=1; WidgetMapper 4-arg fromMap loads that gadget via session.
+ console.info("Typed query getFirstObject — watch for [WidgetMapper 4-arg fromMap] line:");
+ Widget one = session.query(widgets)
+ .where("$.qty == 10")
+ .limit(1)
+ .execute()
+ .getFirstObject()
+ .orElseThrow();
+ console.info("Typed query getFirstObject (factory): " + one);
+
+ // Typed dataset query → TypedRecordStream: mapper-less toObjectList() (4-arg fromMap per row).
+ List fromScan = session.query(widgets)
+ .where("$.qty == 20")
+ .execute()
+ .toObjectList();
+ console.info("Typed query (factory, qty=20 only): " + fromScan);
+
+ console.info("Typed query toObjectList(all widgets) — id=1 triggers session read again:");
+ List allWidgets = session.query(widgets).limit(10).execute().toObjectList();
+ console.info(" " + allWidgets);
+
+ Optional firstTyped = session.query(widgets)
+ .where("$.label == 'alpha'")
+ .execute()
+ .getFirstObject();
+ console.info("Typed query first (factory): " + firstTyped.orElseThrow());
+
+ // Heterogeneous batch: each leg carries Class>; map per row with toObject() (embedded session).
+ RecordStream batch = session
+ .query(widgets.id(2))
+ .readingOnlyBins("label", "qty")
+ .query(gadgets.id(1))
+ .readingOnlyBins("name", "enabled")
+ .execute();
+ try (batch) {
+ Widget w = batch.next().toObject();
+ Gadget g = batch.next().toObject();
+ console.info("Batch widget: " + w);
+ console.info("Batch gadget: " + g);
}
}
-
- private TypedMappingExamples() {
- }
}
diff --git a/examples/src/main/java/com/aerospike/examples/YamlConfigConnectionExample.java b/examples/src/main/java/com/aerospike/examples/YamlConfigConnectionExample.java
index 08f1d4cb..01d0255f 100644
--- a/examples/src/main/java/com/aerospike/examples/YamlConfigConnectionExample.java
+++ b/examples/src/main/java/com/aerospike/examples/YamlConfigConnectionExample.java
@@ -24,7 +24,6 @@
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.policy.ResolvedSettings;
-import com.aerospike.client.sdk.util.Util;
/**
* Example demonstrating how to connect to an Aerospike cluster using a custom
@@ -71,12 +70,8 @@ public class YamlConfigConnectionExample extends Example {
private static final String ENV_CONFIG_URL = "AEROSPIKE_CLIENT_CONFIG_URL";
- public YamlConfigConnectionExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster clusterNotUsed, Args args) throws Exception {
+ public void runExample() throws Exception {
// Check if the configuration environment variable is set
String configPath = System.getenv(ENV_CONFIG_URL);
@@ -92,20 +87,19 @@ public void runExample(Cluster clusterNotUsed, Args args) throws Exception {
console.write("The client will load behaviors and settings from this file.\n");
}
- console.write("Connection settings: " + args);
- console.write("Connecting to Aerospike cluster at " + args.host + ":" + args.port);
- if (args.useServicesAlternate) {
+ console.write("Connecting to Aerospike cluster at " + host() + ":" + port());
+ if (useServicesAlternate()) {
console.write("Using alternate services for cluster discovery");
}
// Create cluster definition
// When connect() is called, it automatically checks for AEROSPIKE_CLIENT_CONFIG_URL
// and loads the configuration if the environment variable is set
- ClusterDefinition clusterDef = new ClusterDefinition(args.host, args.port)
+ ClusterDefinition clusterDef = new ClusterDefinition(host(), port())
.appId("yaml-config-example")
.failIfNotConnected(true);
- if (args.useServicesAlternate) {
+ if (useServicesAlternate()) {
clusterDef.usingServicesAlternate();
}
@@ -122,14 +116,11 @@ public void runExample(Cluster clusterNotUsed, Args args) throws Exception {
console.write("Using behavior: " + behavior.name() + "\n");
// Perform example operations
- performExampleOperations(session, args);
+ performExampleOperations(session);
// Demonstrate using different behaviors for different operations
demonstrateBehaviorSwitching(cluster);
- } catch (Throwable t) {
- console.error("Error: " + Util.getErrorMessage(t));
- t.printStackTrace();
} finally {
// Clean up monitoring if it was started
if (Behavior.isMonitoring()) {
@@ -185,10 +176,10 @@ private Behavior getBehaviorOrDefault(String behaviorName) {
/**
* Perform example CRUD operations using the session
*/
- private void performExampleOperations(Session session, Args args) {
+ private void performExampleOperations(Session session) throws Exception {
console.write("=== Performing Example Operations ===");
- DataSet dataSet = DataSet.of(args.namespace, "yaml-config-demo");
+ DataSet dataSet = dataSet("yaml-config-demo");
try {
// Write a record
@@ -223,13 +214,9 @@ private void performExampleOperations(Session session, Args args) {
var existsResults = session.exists(dataSet.ids("user-001", "user-002", "user-999")).execute();
console.write(" Exists results: " + existsResults);
- // Clean up - delete test records
- console.write("Cleaning up test records...");
- session.delete(dataSet.ids("user-001", "user-002", "user-003", "user-004")).execute();
- console.write(" Records deleted.");
-
} catch (Exception e) {
console.error("Operation failed: " + e.getMessage());
+ throw e;
}
console.write("");
diff --git a/examples/src/main/java/com/aerospike/examples/YamlConfigExample.java b/examples/src/main/java/com/aerospike/examples/YamlConfigExample.java
index 4130e5cb..2d309ddf 100644
--- a/examples/src/main/java/com/aerospike/examples/YamlConfigExample.java
+++ b/examples/src/main/java/com/aerospike/examples/YamlConfigExample.java
@@ -17,13 +17,13 @@
package com.aerospike.examples;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.SystemSettingsRegistry;
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.policy.BehaviorYamlLoader;
@@ -53,12 +53,8 @@
*/
public class YamlConfigExample extends Example {
- public YamlConfigExample(Console console) {
- super(console);
- }
-
@Override
- public void runExample(Cluster clusterNotUsed, Args args) throws Exception {
+ public void runExample() throws Exception {
// Example 1: Load from a file
loadFromFile();
@@ -78,21 +74,21 @@ private void loadFromFile() throws IOException {
System.out.println("=== Loading from file ===");
File configFile = new File("src/main/resources/example-config.yml");
- if (configFile.exists()) {
- Map behaviors = BehaviorYamlLoader.loadBehaviorsFromFile(configFile);
+ if (!configFile.exists()) {
+ throw new FileNotFoundException("Config file not found: " + configFile.getAbsolutePath());
+ }
- System.out.println("Loaded " + behaviors.size() + " behaviors:");
- for (String name : behaviors.keySet()) {
- System.out.println(" - " + name);
- }
+ Map behaviors = BehaviorYamlLoader.loadBehaviorsFromFile(configFile);
- // System settings are automatically registered in SystemSettingsRegistry
- SystemSettingsRegistry registry = SystemSettingsRegistry.getInstance();
- System.out.println("Default system settings: " + registry.getDefaultSettings());
- System.out.println("Production cluster settings: " + registry.getClusterSettings("production"));
- } else {
- System.out.println("Config file not found: " + configFile.getAbsolutePath());
+ System.out.println("Loaded " + behaviors.size() + " behaviors:");
+ for (String name : behaviors.keySet()) {
+ System.out.println(" - " + name);
}
+
+ // System settings are automatically registered in SystemSettingsRegistry
+ SystemSettingsRegistry registry = SystemSettingsRegistry.getInstance();
+ System.out.println("Default system settings: " + registry.getDefaultSettings());
+ System.out.println("Production cluster settings: " + registry.getClusterSettings("production"));
}
/**
@@ -186,14 +182,14 @@ private void useWithCluster() throws IOException {
// Example of how you would use it with a real cluster:
/*
- try (Cluster cluster = new ClusterDefinition("localhost", 3000)
+ try (Cluster cluster = new ClusterDefinition(host(), port())
.connect()) {
// Create a session using the loaded behavior
Session session = cluster.createSession(productionBehavior);
// Use the session for operations
- DataSet myDataSet = DataSet.of("test", "mySet");
+ DataSet myDataSet = dataSet("mySet");
session.upsert(myDataSet.id(1))
.bin("name").setTo("example")
.execute();
diff --git a/examples/src/main/java/com/aerospike/examples/ecommerce/EcommerceExample.java b/examples/src/main/java/com/aerospike/examples/ecommerce/EcommerceExample.java
index 288af591..66b10a93 100644
--- a/examples/src/main/java/com/aerospike/examples/ecommerce/EcommerceExample.java
+++ b/examples/src/main/java/com/aerospike/examples/ecommerce/EcommerceExample.java
@@ -22,7 +22,6 @@
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicInteger;
-import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.DefaultRecordMappingFactory;
import com.aerospike.client.sdk.ErrorStrategy;
import com.aerospike.client.sdk.RecordResult;
@@ -30,8 +29,6 @@
import com.aerospike.client.sdk.TypedDataSet;
import com.aerospike.client.sdk.policy.Behavior;
import com.aerospike.client.sdk.task.ExecuteTask;
-
-import com.aerospike.examples.Args;
import com.aerospike.examples.Example;
/**
@@ -42,81 +39,77 @@
* product stock, create the order, and decrement inventory -- all composed with
* CompletableFuture. Then we scan products using Flow.Publisher with backpressure.
*
- * Run with the same host/port flags as other examples ({@link Args} via {@link Example#parseStandaloneArgs}):
- * default {@code localhost:3000}; {@code -h} / {@code -p} override.
*/
-public class EcommerceExample {
+public class EcommerceExample extends Example {
static final CustomerMapper CUSTOMER_MAPPER = new CustomerMapper();
static final ProductMapper PRODUCT_MAPPER = new ProductMapper();
static final OrderMapper ORDER_MAPPER = new OrderMapper();
- public static void main(String[] args) throws Exception {
- Args arguments = Example.parseStandaloneArgs(args);
- try (Cluster cluster = Example.clusterDefinition(arguments).connect()) {
- cluster.setRecordMappingFactory(DefaultRecordMappingFactory.of(
- Customer.class, CUSTOMER_MAPPER,
- Product.class, PRODUCT_MAPPER,
- Order.class, ORDER_MAPPER));
-
- Session session = cluster.createSession(Behavior.DEFAULT);
-
- TypedDataSet customers = TypedDataSet.of("test", "customers", Customer.class);
- TypedDataSet products = TypedDataSet.of("test", "products", Product.class);
- TypedDataSet orders = TypedDataSet.of("test", "orders", Order.class);
-
- // ==========================================
- // 1. Seed 20 customers, 100 products, and
- // 54 orders into Aerospike
- // ==========================================
- SeedData.seed(session, customers, products, orders);
-
- // ==========================================
- // 2. Place an order using CompletableFuture
- // (async lookup -> validate -> batch write)
- // ==========================================
- placeOrder(session, customers, products, orders, "C-100", "SKU-LAP01", 1);
-
- // ==========================================
- // 3. Demonstrate error handling on a
- // non-existent customer
- // ==========================================
- placeOrderWithErrorHandling(session, customers, products, orders,
- "C-MISSING", "SKU-LAP01", 1);
-
- // ==========================================
- // 4. Stream orders for a customer using
- // Flow.Publisher with backpressure
- // ==========================================
- streamOrders(session, orders, "C-100");
-
- // ==========================================
- // 5. Batch query: top-spender dashboard
- // ==========================================
- topSpenderDashboard(session, customers, orders);
-
- // ==========================================
- // 6. Map operations: product ratings
- // ==========================================
- productRatings(session, products);
-
- // ==========================================
- // 7. Scan for affordable, well-stocked
- // products using Flow.Publisher
- // ==========================================
- scanAffordableProducts(session, products);
-
- // ==========================================
- // 8. Background scan: apply sale prices to
- // overstocked, cheap products
- // ==========================================
- applySalePrices(session, products);
-
- // ==========================================
- // 9. Re-display stock after sale prices
- // ==========================================
- scanAffordableProducts(session, products);
- }
+ @Override
+ public void runExample() throws Exception {
+ cluster().setRecordMappingFactory(DefaultRecordMappingFactory.of(
+ Customer.class, CUSTOMER_MAPPER,
+ Product.class, PRODUCT_MAPPER,
+ Order.class, ORDER_MAPPER));
+
+ Session session = cluster().createSession(Behavior.DEFAULT);
+
+ TypedDataSet customers = TypedDataSet.of(namespace(), "customers", Customer.class);
+ TypedDataSet products = TypedDataSet.of(namespace(), "products", Product.class);
+ TypedDataSet orders = TypedDataSet.of(namespace(), "orders", Order.class);
+
+ // ==========================================
+ // 1. Seed 20 customers, 95 products, and
+ // 54 orders into Aerospike
+ // ==========================================
+ SeedData.seed(session, customers, products, orders);
+
+ // ==========================================
+ // 2. Place an order using CompletableFuture
+ // (async lookup -> validate -> batch write)
+ // ==========================================
+ placeOrder(session, customers, products, orders, "C-100", "SKU-LAP01", 1);
+
+ // ==========================================
+ // 3. Demonstrate error handling on a
+ // non-existent customer
+ // ==========================================
+ placeOrderWithErrorHandling(session, customers, products, orders,
+ "C-MISSING", "SKU-LAP01", 1);
+
+ // ==========================================
+ // 4. Stream orders for a customer using
+ // Flow.Publisher with backpressure
+ // ==========================================
+ streamOrders(session, orders, "C-100");
+
+ // ==========================================
+ // 5. Batch query: top-spender dashboard
+ // ==========================================
+ topSpenderDashboard(session, customers, orders);
+
+ // ==========================================
+ // 6. Map operations: product ratings
+ // ==========================================
+ productRatings(session, products);
+
+ // ==========================================
+ // 7. Scan for affordable, well-stocked
+ // products using Flow.Publisher
+ // ==========================================
+ scanAffordableProducts(session, products);
+
+ // ==========================================
+ // 8. Background scan: apply sale prices to
+ // overstocked, cheap products
+ // ==========================================
+ applySalePrices(session, products);
+
+ // ==========================================
+ // 9. Re-display stock after sale prices
+ // ==========================================
+ scanAffordableProducts(session, products);
}
// ------------------------------------------------------------------
diff --git a/examples/src/main/java/com/aerospike/examples/fixtures/ExampleAssertions.java b/examples/src/main/java/com/aerospike/examples/fixtures/ExampleAssertions.java
new file mode 100644
index 00000000..50c48ada
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/fixtures/ExampleAssertions.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012-2026 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.examples.fixtures;
+
+import java.util.List;
+import java.util.Objects;
+
+import com.aerospike.client.sdk.DataSet;
+import com.aerospike.client.sdk.Record;
+import com.aerospike.client.sdk.RecordResult;
+import com.aerospike.client.sdk.RecordStream;
+import com.aerospike.client.sdk.Session;
+
+public final class ExampleAssertions {
+ private ExampleAssertions() {
+ }
+
+ public static void truncate(Session session, DataSet dataSet) {
+ session.truncate(dataSet);
+ }
+
+ public static long count(Session session, DataSet dataSet) {
+ long count = 0;
+
+ try (RecordStream stream = session.query(dataSet).execute()) {
+ while (stream.hasNext()) {
+ stream.next().orThrow();
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ public static void assertCount(Session session, DataSet dataSet, long expected) {
+ long actual = count(session, dataSet);
+
+ if (actual != expected) {
+ throw new AssertionError("Expected " + expected + " records in " + dataSet + " but found " + actual);
+ }
+ }
+
+ public static Record assertRecordExists(Session session, DataSet dataSet, Object id) {
+ try (RecordStream stream = session.query(dataSet.ids(List.of(id))).execute()) {
+ if (!stream.hasNext()) {
+ throw new AssertionError("Expected record id " + id + " in " + dataSet + " but no result was returned");
+ }
+
+ RecordResult result = stream.next().orThrow();
+ Record record = result.recordOrThrow();
+
+ if (record == null) {
+ throw new AssertionError("Expected record id " + id + " in " + dataSet + " but record payload was null");
+ }
+
+ return record;
+ }
+ }
+
+ public static void assertRecordMissing(Session session, DataSet dataSet, Object id) {
+ try (RecordStream stream = session.query(dataSet.ids(List.of(id))).execute()) {
+ if (!stream.hasNext()) {
+ return;
+ }
+
+ RecordResult result = stream.next();
+
+ if (result.isOk()) {
+ throw new AssertionError("Expected record id " + id + " in " + dataSet + " to be missing");
+ }
+ }
+ }
+
+ public static void assertBinEquals(Session session, DataSet dataSet, Object id, String binName, Object expected) {
+ Record record = assertRecordExists(session, dataSet, id);
+ Object actual = record.getValue(binName);
+
+ if (!Objects.equals(expected, actual)) {
+ throw new AssertionError(
+ "Expected " + dataSet + " id " + id + " bin " + binName + " to be " + expected + " but found " + actual);
+ }
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/fixtures/ExampleFixtures.java b/examples/src/main/java/com/aerospike/examples/fixtures/ExampleFixtures.java
new file mode 100644
index 00000000..ed4bb62f
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/fixtures/ExampleFixtures.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2012-2026 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.examples.fixtures;
+
+import java.util.List;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import com.aerospike.client.sdk.AerospikeException;
+import com.aerospike.client.sdk.DataSet;
+import com.aerospike.client.sdk.Session;
+import com.aerospike.examples.ExampleContext;
+import com.aerospike.examples.ExampleFixture;
+
+public final class ExampleFixtures {
+ private static final String COMMON_INDEX_NAME = "ageidx";
+
+ private ExampleFixtures() {
+ }
+
+ // =====================================================================
+ // Generic builders
+ //
+ // Most fixtures follow the same shape: truncate one or more sets before
+ // and after the example, then assert something in verify. These builders
+ // capture that shape so each fixture only has to express what varies (the
+ // sets it owns and the verification), keeping the doc-facing examples free
+ // of setup/teardown noise.
+ // =====================================================================
+
+ /**
+ * Fixture that truncates the sets resolved from {@code sets} before and after the example
+ * runs, and executes {@code verify} once the example completes.
+ */
+ private static ExampleFixture truncating(
+ Function> sets,
+ Consumer verify
+ ) {
+ return new ExampleFixture() {
+ @Override
+ public void setup(ExampleContext context) {
+ truncateAll(context, sets);
+ }
+
+ @Override
+ public void verify(ExampleContext context) {
+ verify.accept(context);
+ }
+
+ @Override
+ public void cleanup(ExampleContext context) {
+ truncateAll(context, sets);
+ }
+ };
+ }
+
+ /** Convenience builder for a fixture that owns a single named set. */
+ private static ExampleFixture truncating(String set, Consumer verify) {
+ return truncating(context -> List.of(context.dataSet(set)), verify);
+ }
+
+ private static void truncateAll(ExampleContext context, Function> sets) {
+ Session session = context.session();
+
+ for (DataSet dataSet : sets.apply(context)) {
+ ExampleAssertions.truncate(session, dataSet);
+ }
+ }
+
+ // =====================================================================
+ // Fixtures
+ // =====================================================================
+
+ public static ExampleFixture batchExample() {
+ return truncating(
+ context -> List.of(context.dataSet()),
+ context -> {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet();
+
+ ExampleAssertions.assertCount(session, dataSet, 7);
+ ExampleAssertions.assertRecordMissing(session, dataSet, 1);
+ ExampleAssertions.assertBinEquals(session, dataSet, 2, "value", 15L);
+ ExampleAssertions.assertBinEquals(session, dataSet, 6, "name", "Wilma");
+ });
+ }
+
+ public static ExampleFixture commonExample() {
+ // Not expressed via truncating(...) because this example also creates a
+ // secondary index that must be dropped in setup and cleanup.
+ return new ExampleFixture() {
+ @Override
+ public void setup(ExampleContext context) {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet();
+
+ dropIndexIfExists(session, dataSet, COMMON_INDEX_NAME);
+ ExampleAssertions.truncate(session, dataSet);
+ }
+
+ @Override
+ public void verify(ExampleContext context) {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet();
+
+ ExampleAssertions.assertRecordMissing(session, dataSet, 118);
+ ExampleAssertions.assertBinEquals(session, dataSet, 1, "name", "Tim");
+ ExampleAssertions.assertBinEquals(session, dataSet, 1, "writeBin", 342L);
+ }
+
+ @Override
+ public void cleanup(ExampleContext context) {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet();
+
+ dropIndexIfExists(session, dataSet, COMMON_INDEX_NAME);
+ ExampleAssertions.truncate(session, dataSet);
+ }
+ };
+ }
+
+ public static ExampleFixture yamlConfigConnectionExample() {
+ return truncating(
+ "yaml-config-demo",
+ context -> {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet("yaml-config-demo");
+
+ ExampleAssertions.assertCount(session, dataSet, 4);
+ ExampleAssertions.assertBinEquals(session, dataSet, "user-001", "name", "Alice");
+ ExampleAssertions.assertBinEquals(session, dataSet, "user-004", "age", 28L);
+ });
+ }
+
+ public static ExampleFixture studentScoresExample() {
+ return truncating(
+ "class10a",
+ context -> {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet("class10a");
+
+ ExampleAssertions.assertCount(session, dataSet, 30);
+ ExampleAssertions.assertBinEquals(session, dataSet, "student-1", "name", "Student 1");
+ ExampleAssertions.assertBinEquals(session, dataSet, "student-30", "name", "Student 30");
+ });
+ }
+
+ public static ExampleFixture mapRemoveByKeyRangeTest() {
+ return truncating(
+ "map_remove_test",
+ context -> {
+ TreeMap expected = new TreeMap<>();
+ expected.put("a", 1L);
+ expected.put("b", 2L);
+ expected.put("c", 3L);
+ expected.put("d", 4L);
+ expected.put("e", 5L);
+
+ Session session = context.session();
+ DataSet dataSet = context.dataSet("map_remove_test");
+
+ ExampleAssertions.assertCount(session, dataSet, 1);
+ ExampleAssertions.assertBinEquals(session, dataSet, 1, "m", expected);
+ });
+ }
+
+ public static ExampleFixture transactionProcessingExample() {
+ return truncating(
+ context -> List.of(
+ context.dataSet("customers"),
+ context.dataSet("accounts"),
+ context.dataSet("txns")),
+ context -> {
+ Session session = context.session();
+ DataSet customers = context.dataSet("customers");
+ DataSet accounts = context.dataSet("accounts");
+ DataSet txns = context.dataSet("txns");
+
+ ExampleAssertions.assertCount(session, customers, 1);
+ ExampleAssertions.assertCount(session, accounts, 1);
+ ExampleAssertions.assertCount(session, txns, 1);
+ ExampleAssertions.assertBinEquals(session, customers, "CUST-10042", "totalSpend", 45000L);
+ ExampleAssertions.assertBinEquals(session, customers, "CUST-10042", "statusLevel", "GOLD");
+ ExampleAssertions.assertBinEquals(session, accounts, "4532015112830366", "balanceCents", 45000L);
+ ExampleAssertions.assertBinEquals(session, txns, "TXN-00001", "desc", "Car repairs");
+ });
+ }
+
+ public static ExampleFixture completeYamlConfigExample() {
+ return truncating(
+ "complete-yaml-demo",
+ context -> ExampleAssertions.assertCount(context.session(), context.dataSet("complete-yaml-demo"), 0));
+ }
+
+ public static ExampleFixture aelTestSpecRunner() {
+ return truncating(
+ "ael_test_spec",
+ context -> ExampleAssertions.assertCount(context.session(), context.dataSet("ael_test_spec"), 11));
+ }
+
+ public static ExampleFixture operationDifferences() {
+ return truncating(
+ "ael_diff_test",
+ context -> ExampleAssertions.assertCount(context.session(), context.dataSet("ael_diff_test"), 7));
+ }
+
+ public static ExampleFixture queryExamples() {
+ return truncating(
+ context -> List.of(context.dataSet("person"), context.dataSet("users")),
+ context -> {
+ Session session = context.session();
+ DataSet person = context.dataSet("person");
+
+ ExampleAssertions.assertRecordExists(session, person, 999);
+ ExampleAssertions.assertRecordExists(session, person, 500);
+ });
+ }
+
+ public static ExampleFixture ecommerceExample() {
+ return truncating(
+ context -> List.of(
+ context.dataSet("customers"),
+ context.dataSet("products"),
+ context.dataSet("orders")),
+ context -> {
+ Session session = context.session();
+ DataSet customers = context.dataSet("customers");
+ DataSet products = context.dataSet("products");
+ DataSet orders = context.dataSet("orders");
+
+ ExampleAssertions.assertCount(session, customers, 20);
+ ExampleAssertions.assertCount(session, products, 95);
+ ExampleAssertions.assertCount(session, orders, 55);
+ ExampleAssertions.assertBinEquals(session, customers, "C-100", "balance", 89999L);
+ ExampleAssertions.assertBinEquals(session, products, "SKU-LAP01", "stock", 24L);
+ ExampleAssertions.assertBinEquals(session, orders, "ORD-2001", "status", "CONFIRMED");
+ });
+ }
+
+ public static ExampleFixture cdtPathExpressionExample() {
+ return truncating(
+ "cdt-path-demo",
+ context -> {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet("cdt-path-demo");
+
+ ExampleAssertions.assertCount(session, dataSet, 5);
+ ExampleAssertions.assertBinEquals(session, dataSet, 1, "nums", List.of(11L, 12L, 13L));
+ ExampleAssertions.assertBinEquals(session, dataSet, 2, "nums", List.of(3L, 2L));
+ });
+ }
+
+ public static ExampleFixture stringOperationsExample() {
+ return truncating(
+ "string-ops-demo",
+ context -> {
+ Session session = context.session();
+ DataSet dataSet = context.dataSet("string-ops-demo");
+
+ ExampleAssertions.assertCount(session, dataSet, 1);
+ ExampleAssertions.assertBinEquals(session, dataSet, "row1", "message", "hello");
+ });
+ }
+
+ public static ExampleFixture typedMappingExamples() {
+ return truncating(
+ context -> List.of(
+ context.dataSet("typed_demo_widgets"),
+ context.dataSet("typed_demo_gadgets")),
+ context -> {
+ Session session = context.session();
+ DataSet widgets = context.dataSet("typed_demo_widgets");
+ DataSet gadgets = context.dataSet("typed_demo_gadgets");
+
+ ExampleAssertions.assertCount(session, widgets, 2);
+ ExampleAssertions.assertCount(session, gadgets, 1);
+ ExampleAssertions.assertBinEquals(session, widgets, 1, "label", "alpha");
+ ExampleAssertions.assertBinEquals(session, widgets, 2, "qty", 20L);
+ ExampleAssertions.assertBinEquals(session, gadgets, 1, "name", "notifications");
+ });
+ }
+
+ private static void dropIndexIfExists(Session session, DataSet dataSet, String indexName) {
+ try {
+ session.dropIndex(dataSet, indexName).waitTillComplete();
+ }
+ catch (AerospikeException ignored) {
+ // The cleanup path should be idempotent for local reruns and CI retries.
+ }
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/query/Address.java b/examples/src/main/java/com/aerospike/examples/query/Address.java
new file mode 100644
index 00000000..2ee799d8
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/query/Address.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012-2026 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.examples.query;
+
+/**
+ * Simple immutable value object used by {@link com.aerospike.examples.QueryExamples}
+ * to demonstrate nested object mapping.
+ */
+public class Address {
+ private final String line1;
+ private final String city;
+ private final String state;
+ private final String country;
+ private final String zipCode;
+
+ public Address(String line1, String city, String state, String country, String zipCode) {
+ super();
+ this.line1 = line1;
+ this.city = city;
+ this.state = state;
+ this.country = country;
+ this.zipCode = zipCode;
+ }
+
+ public String getLine1() {
+ return line1;
+ }
+ public String getCity() {
+ return city;
+ }
+
+ public String getState() {
+ return state;
+ }
+ public String getCountry() {
+ return country;
+ }
+ public String getZipCode() {
+ return zipCode;
+ }
+ @Override
+ public String toString() {
+ return "Address [line1=" + line1 + ", city=" + city + ", state=" + state + ", country=" + country + ", zipCode="
+ + zipCode + "]";
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/query/AddressMapper.java b/examples/src/main/java/com/aerospike/examples/query/AddressMapper.java
new file mode 100644
index 00000000..15faf9a9
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/query/AddressMapper.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2026 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.examples.query;
+
+import java.util.Map;
+
+import com.aerospike.client.sdk.Key;
+import com.aerospike.client.sdk.RecordMapper;
+import com.aerospike.client.sdk.util.MapUtil;
+
+public class AddressMapper implements RecordMapper {
+
+ @Override
+ public Address fromMap(Map map, Key recordKey, int generation) {
+ Address result = new Address(
+ MapUtil.asString(map, "line1"),
+ MapUtil.asString(map, "city"),
+ MapUtil.asString(map, "state"),
+ MapUtil.asString(map, "country"),
+ MapUtil.asString(map, "zip"));
+ return result;
+ }
+
+ @Override
+ public Map toMap(Address addr) {
+ return MapUtil.buildMap()
+ .add("line1", addr.getLine1())
+ .add("city", addr.getCity())
+ .add("state", addr.getState())
+ .add("country", addr.getCountry())
+ .add("zip", addr.getZipCode())
+ .done();
+ }
+
+ @Override
+ public Object id(Address element) {
+ return null;
+ }
+
+}
diff --git a/examples/src/main/java/com/aerospike/examples/query/Customer.java b/examples/src/main/java/com/aerospike/examples/query/Customer.java
new file mode 100644
index 00000000..fe13e862
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/query/Customer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012-2026 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.examples.query;
+
+import java.util.Date;
+
+/**
+ * Mutable POJO used by {@link com.aerospike.examples.QueryExamples} to demonstrate
+ * object mapping, including a nested {@link Address}.
+ */
+public class Customer {
+ private long id;
+ private String name;
+ private int age;
+ private Date dob;
+ private Address address;
+
+ public Customer() {
+ super();
+ }
+ public Customer(long id, String name, int age, Date dob) {
+ this(id, name, age, dob, null);
+ }
+ public Customer(long id, String name, int age, Date dob, Address address) {
+ super();
+ this.id = id;
+ this.name = name;
+ this.age = age;
+ this.dob = dob;
+ this.address = address;
+ }
+
+ public long getId() {
+ return id;
+ }
+ public void setId(long id) {
+ this.id = id;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public int getAge() {
+ return age;
+ }
+ public void setAge(int age) {
+ this.age = age;
+ }
+ public Date getDob() {
+ return dob;
+ }
+ public void setDob(Date dob) {
+ this.dob = dob;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ @Override
+ public String toString() {
+ return "Customer [id=" + id + ", name=" + name + ", age=" + age + ", dob=" + dob + ", address=" + address + "]";
+ }
+}
diff --git a/examples/src/main/java/com/aerospike/examples/query/CustomerMapper.java b/examples/src/main/java/com/aerospike/examples/query/CustomerMapper.java
new file mode 100644
index 00000000..b320f225
--- /dev/null
+++ b/examples/src/main/java/com/aerospike/examples/query/CustomerMapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2026 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.examples.query;
+
+import java.util.Map;
+
+import com.aerospike.client.sdk.Key;
+import com.aerospike.client.sdk.RecordMapper;
+import com.aerospike.client.sdk.util.MapUtil;
+
+public class CustomerMapper implements RecordMapper {
+ @Override
+ public Customer fromMap(Map map, Key recordKey, int generation) {
+ Customer result = new Customer();
+ result.setId(recordKey.userKey.toLong());
+ result.setAge(MapUtil.asInt(map, "age"));
+ result.setDob(MapUtil.asDateFromLong(map, "dob"));
+ result.setName(MapUtil.asString(map, "name"));
+ result.setAddress(MapUtil.asObjectFromMap(map, "address", new AddressMapper()));
+ return result;
+ }
+
+ @Override
+ public Map toMap(Customer customer) {
+ return MapUtil.buildMap()
+ .add("id", customer.getId())
+ .add("age", customer.getAge())
+ .addAsLong("dob", customer.getDob())
+ .add("name", customer.getName())
+ .add("address", customer.getAddress(), new AddressMapper())
+ .done();
+ }
+
+ @Override
+ public Object id(Customer customer) {
+ return customer.getId();
+ }
+}