")
+ .upsert(upsert)
+ .records(new ArrayList<>(recordsToRetry))
+ .build());
+ System.out.println("retry response:\t" + retryResponse);
+ }
} catch (SkyflowException e) {
- // Step 10: Handle any errors that occur during the process
+ // Step 12: Handle any errors that occur during the process
System.err.println("Error in Skyflow operations: " + e.getMessage());
}
}
-}
+}
\ No newline at end of file
diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java
index c0f15834..3dbfccbe 100644
--- a/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java
+++ b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java
@@ -8,12 +8,15 @@
import com.skyflow.vault.data.BulkInsertRequest;
import com.skyflow.vault.data.BulkInsertRequestRecord;
import com.skyflow.vault.data.BulkInsertResponse;
+import com.skyflow.vault.data.BulkInsertResponseRecord;
import com.skyflow.vault.data.InsertRequestRecord;
+import com.skyflow.vault.data.Token;
import com.skyflow.vault.data.UpsertOptions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -23,7 +26,8 @@
* 1. Setting up credentials and vault configuration
* 2. Creating multiple records to be inserted
* 3. Building and executing an async bulk insert request
- * 4. Handling the insert response or errors using CompletableFuture
+ * 4. Reading the per-record outcome and the summary from the response
+ * 5. Handling the insert response or errors using CompletableFuture
*
* Multi-table mode: the table name is set on every record instead of on the request.
* The SDK rejects a request that sets it at both levels, or on only some of the records.
@@ -58,7 +62,7 @@ public static void main(String[] args) {
List upsertColumns = new ArrayList<>();
upsertColumns.add("");
- // upsert is optional; when updateType is omitted the vault defaults to "UPDATE".
+ // upsert is optional; when updateType is omitted the vault treats it the same as "UPDATE".
// Set .updateType("REPLACE") to replace the matched row instead.
UpsertOptions upsert = UpsertOptions.builder()
.uniqueColumns(upsertColumns)
@@ -94,16 +98,45 @@ public static void main(String[] args) {
// Step 8: Execute the async bulk insert operation and handle response using callbacks
CompletableFuture future = skyflowClient.vault().bulkInsertAsync(request);
- // Add success and error callbacks
future.thenAccept(response -> {
System.out.println("Async bulk insert resolved with response:\t" + response);
+
+ // Read the summary, then walk the per-record outcomes. A record succeeded when
+ // its error is null; requestId identifies the batch an error came from and is
+ // set on failures only.
+ System.out.println("inserted:\t" + response.getSummary().getTotalInserted()
+ + " of " + response.getSummary().getTotalRecords());
+
+ for (BulkInsertResponseRecord record : response.getRecords()) {
+ if (record.getError() == null) {
+ System.out.printf("[%d] %s -> skyflowId=%s%n",
+ record.getIndex(), record.getTableName(), record.getSkyflowId());
+ // getTokens() returns a typed Map>
+ // - no casting needed to read token/tokenGroupName.
+ for (Map.Entry> column : record.getTokens().entrySet()) {
+ for (Token token : column.getValue()) {
+ System.out.printf(" %s[%s] -> %s%n",
+ column.getKey(), token.getTokenGroupName(), token.getToken());
+ }
+ }
+ } else {
+ System.out.printf("[%d] failed (%d): %s [requestId=%s]%n",
+ record.getIndex(), record.getHttpCode(), record.getError(), record.getRequestId());
+ }
+ }
+
+ // Records that failed with a retryable status (5xx other than 529) come back
+ // unchanged and can be resubmitted as-is.
+ if (!response.getRecordsToRetry().isEmpty()) {
+ System.out.println("records to retry:\t" + response.getRecordsToRetry().size());
+ }
}).exceptionally(throwable -> {
System.err.println("Async bulk insert rejected with error:\t" + throwable.getMessage());
throw new CompletionException(throwable);
- });
+ }).join(); // sample-run only: block so main() doesn't exit before the async callback prints
} catch (Exception e) {
// Step 9: Handle any synchronous errors that occur during setup
System.err.println("Error in Skyflow operations:\t" + e.getMessage());
}
}
-}
+}
\ No newline at end of file
diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java
index a3c1211b..ddc835ac 100644
--- a/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java
+++ b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java
@@ -9,12 +9,15 @@
import com.skyflow.vault.data.BulkInsertRequest;
import com.skyflow.vault.data.BulkInsertRequestRecord;
import com.skyflow.vault.data.BulkInsertResponse;
+import com.skyflow.vault.data.BulkInsertResponseRecord;
import com.skyflow.vault.data.InsertRequestRecord;
+import com.skyflow.vault.data.Token;
import com.skyflow.vault.data.UpsertOptions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* This sample demonstrates how to perform a synchronous bulk insert operation using the Skyflow Java SDK.
@@ -22,7 +25,8 @@
* 1. Setting up credentials and vault configuration
* 2. Creating multiple records to be inserted
* 3. Building and executing a bulk insert request
- * 4. Handling the insert response or any potential errors
+ * 4. Reading the per-record outcome and the summary from the response
+ * 5. Handling the insert response or any potential errors
*
* Multi-table mode: the table name is set on every record instead of on the request.
* The SDK rejects a request that sets it at both levels, or on only some of the records.
@@ -57,7 +61,7 @@ public static void main(String[] args) {
List upsertColumns = new ArrayList<>();
upsertColumns.add("");
- // upsert is optional; when updateType is omitted the vault defaults to "UPDATE".
+ // upsert is optional; when updateType is omitted the vault treats it the same as "UPDATE".
// Set .updateType("REPLACE") to replace the matched row instead.
UpsertOptions upsert = UpsertOptions.builder()
.uniqueColumns(upsertColumns)
@@ -94,8 +98,44 @@ public static void main(String[] args) {
// Step 8: Execute the bulk insert operation and print the response
BulkInsertResponse response = skyflowClient.vault().bulkInsert(request);
System.out.println(response);
+
+ // Step 9: Read the summary, then walk the per-record outcomes. A record succeeded
+ // when its error is null; requestId identifies the batch an error came from and is
+ // set on failures only.
+ System.out.println("inserted:\t" + response.getSummary().getTotalInserted()
+ + " of " + response.getSummary().getTotalRecords());
+
+ for (BulkInsertResponseRecord record : response.getRecords()) {
+ if (record.getError() == null) {
+ System.out.printf("[%d] %s -> skyflowId=%s%n",
+ record.getIndex(), record.getTableName(), record.getSkyflowId());
+ // getTokens() returns a typed Map>
+ // - no casting needed to read token/tokenGroupName.
+ for (Map.Entry> column : record.getTokens().entrySet()) {
+ for (Token token : column.getValue()) {
+ System.out.printf(" %s[%s] -> %s%n",
+ column.getKey(), token.getTokenGroupName(), token.getToken());
+ }
+ }
+ } else {
+ System.out.printf("[%d] failed (%d): %s [requestId=%s]%n",
+ record.getIndex(), record.getHttpCode(), record.getError(), record.getRequestId());
+ }
+ }
+
+ // Step 10: Optionally retry the records that failed with a retryable status (5xx other
+ // than 529). Each returned record still carries its own tableName/upsert, so the retry
+ // request needs no request-level tableName.
+ List recordsToRetry = response.getRecordsToRetry();
+ if (!recordsToRetry.isEmpty()) {
+ BulkInsertResponse retryResponse = skyflowClient.vault().bulkInsert(
+ BulkInsertRequest.builder()
+ .records(new ArrayList<>(recordsToRetry))
+ .build());
+ System.out.println("retry response:\t" + retryResponse);
+ }
} catch (SkyflowException e) {
- // Step 9: Handle any errors that occur during the process
+ // Step 11: Handle any errors that occur during the process
System.err.println("Error in Skyflow operations: " + e.getMessage());
}
}
diff --git a/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java b/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java
index 4dc4d67c..b947239c 100644
--- a/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java
+++ b/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java
@@ -10,12 +10,15 @@
import com.skyflow.vault.data.BulkInsertRequestRecord;
import com.skyflow.vault.data.BulkInsertResponse;
import com.skyflow.vault.data.BulkInsertOptions;
+import com.skyflow.vault.data.BulkInsertResponseRecord;
import com.skyflow.vault.data.InsertRequestRecord;
+import com.skyflow.vault.data.Token;
import com.skyflow.vault.data.UpsertOptions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -66,7 +69,7 @@ public static void main(String[] args) {
}
// Step 5: Configure upsert. uniqueColumns is required; updateType accepts "UPDATE"
- // (default) or "REPLACE".
+ // or "REPLACE" — if omitted, the vault treats it the same as "UPDATE".
List upsertColumns = new ArrayList<>();
upsertColumns.add("");
@@ -92,13 +95,36 @@ public static void main(String[] args) {
// Step 8: Execute the async bulk insert operation and handle response using callbacks
CompletableFuture future =
skyflowClient.vault().bulkInsertAsync(request, options);
- // Add success and error callbacks
future.thenAccept(response -> {
System.out.println("Async bulk insert resolved with response:\t" + response);
+
+ // Read the summary, then walk the per-record outcomes. A record succeeded when
+ // its error is null; requestId identifies the batch an error came from and is
+ // set on failures only — the same header this sample attaches to the outgoing
+ // request shows up here on any batch that failed.
+ System.out.println("inserted:\t" + response.getSummary().getTotalInserted()
+ + " of " + response.getSummary().getTotalRecords());
+
+ for (BulkInsertResponseRecord record : response.getRecords()) {
+ if (record.getError() == null) {
+ System.out.printf("[%d] skyflowId=%s%n", record.getIndex(), record.getSkyflowId());
+ // getTokens() returns a typed Map>
+ // - no casting needed to read token/tokenGroupName.
+ for (Map.Entry> column : record.getTokens().entrySet()) {
+ for (Token token : column.getValue()) {
+ System.out.printf(" %s[%s] -> %s%n",
+ column.getKey(), token.getTokenGroupName(), token.getToken());
+ }
+ }
+ } else {
+ System.out.printf("[%d] failed (%d): %s [requestId=%s]%n",
+ record.getIndex(), record.getHttpCode(), record.getError(), record.getRequestId());
+ }
+ }
}).exceptionally(throwable -> {
System.err.println("Async bulk insert rejected with error:\t" + throwable.getMessage());
throw new CompletionException(throwable);
- });
+ }).join(); // sample-run only: block so main() doesn't exit before the async callback prints
} catch (Exception e) {
// Step 9: Handle any synchronous errors that occur during setup
System.err.println("Error in Skyflow operations:\t" + e.getMessage());
@@ -110,4 +136,4 @@ public static String getRequestId() {
System.out.println("id=>" + id);
return id;
}
-}
+}
\ No newline at end of file
diff --git a/flowvault/src/main/java/com/skyflow/Skyflow.java b/flowvault/src/main/java/com/skyflow/Skyflow.java
index 5c2e682c..c2647824 100644
--- a/flowvault/src/main/java/com/skyflow/Skyflow.java
+++ b/flowvault/src/main/java/com/skyflow/Skyflow.java
@@ -36,8 +36,7 @@ public static SkyflowClientBuilder builder() {
}
public VaultConfig getVaultConfig() {
- Object[] array = this.builder.vaultConfigMap.values().toArray();
- return (VaultConfig) array[0];
+ return this.builder.vaultConfigMap.values().stream().findFirst().orElse(null);
}
/**
diff --git a/flowvault/src/main/java/com/skyflow/utils/Utils.java b/flowvault/src/main/java/com/skyflow/utils/Utils.java
index d6011dec..31d1b331 100644
--- a/flowvault/src/main/java/com/skyflow/utils/Utils.java
+++ b/flowvault/src/main/java/com/skyflow/utils/Utils.java
@@ -49,6 +49,7 @@
import com.skyflow.vault.data.ErrorRecord;
import com.skyflow.vault.data.InsertRequest;
import com.skyflow.vault.data.InsertRequestRecord;
+import com.skyflow.vault.data.Token;
import com.skyflow.vault.data.TokenGroupRedactions;
import com.skyflow.vault.data.UpsertOptions;
@@ -351,7 +352,7 @@ public static BulkInsertResponseRecord createInsertErrorRecord(Map handleBulkInsertBatchException(
} else {
errorMessage = apiException.getMessage();
}
- err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), errorMessage, requestId);
+ err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, null, apiException.statusCode(), errorMessage, requestId);
}
allRecords.add(err);
@@ -432,7 +433,7 @@ public static List handleBulkInsertBatchException(
if (allRecords.isEmpty()) {
for (int j = 0; j < batch.size(); j++) {
- allRecords.add(new BulkInsertResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), apiException.getMessage(), requestId));
+ allRecords.add(new BulkInsertResponseRecord(indexNumber, null, null, null, null, null, apiException.statusCode(), apiException.getMessage(), requestId));
indexNumber++;
}
}
@@ -452,7 +453,7 @@ public static List handleBulkInsertBatchException(
if (message == null || message.isEmpty() || message.trim().isEmpty()){
message = ex.getMessage();
}
- BulkInsertResponseRecord err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, 500, message, null);
+ BulkInsertResponseRecord err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, null, 500, message, null);
allRecords.add(err);
indexNumber++;
}
@@ -760,7 +761,8 @@ public static BulkInsertResponse formatBulkInsertResponse(V1InsertResponse respo
indexNumber,
current.getTableName().orElse(null),
current.getSkyflowId().orElse(null),
- current.getTokens().orElse(null),
+ Token.parseTokens(current.getTokens().orElse(null)),
+ current.getData().orElse(null),
current.getHashedData().orElse(null),
current.getHttpCode().orElse(current.getError().isPresent() ? 500 : 200),
current.getError().orElse(null),
diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java
index 1ea6b757..1d85c7b6 100644
--- a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java
+++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java
@@ -2,6 +2,7 @@
import com.google.gson.Gson;
+import java.util.List;
import java.util.Map;
// Bulk counterpart of InsertResponseRecord. Adds the caller-facing position of the record
@@ -10,10 +11,21 @@ public class BulkInsertResponseRecord extends InsertResponseRecord {
private final int index;
private final String requestId;
+ /**
+ * @deprecated Use {@link #BulkInsertResponseRecord(int, String, String, Map, Map, Map, int, String, String)}
+ * instead, which also lets you populate {@code data}. This overload always leaves {@code data} null.
+ */
+ @Deprecated(since = "1.0.2", forRemoval = true)
public BulkInsertResponseRecord(int index, String tableName, String skyflowId,
- Map fields, Map hashedData,
+ Map> tokens, Map hashedData,
int httpCode, String error, String requestId) {
- super(tableName, skyflowId, fields, hashedData, httpCode, error);
+ this(index, tableName, skyflowId, tokens, null, hashedData, httpCode, error, requestId);
+ }
+
+ public BulkInsertResponseRecord(int index, String tableName, String skyflowId,
+ Map> tokens, Map data, Map hashedData,
+ int httpCode, String error, String requestId) {
+ super(tableName, skyflowId, tokens, data, hashedData, httpCode, error);
this.index = index;
this.requestId = requestId;
}
diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java
index 442d3749..57c7bcf9 100644
--- a/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java
+++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java
@@ -1,20 +1,36 @@
package com.skyflow.vault.data;
+import com.skyflow.logs.InfoLogs;
+import com.skyflow.utils.logger.LogUtil;
+
+import java.util.List;
import java.util.Map;
public class InsertResponseRecord {
private final String tableName;
private final String skyflowId;
- private final Map fields;
+ private final Map> tokens;
+ private final Map data;
private final Map hashedData;
private final int httpCode;
private final String error;
- public InsertResponseRecord(String tableName, String skyflowId, Map fields,
+ /**
+ * @deprecated Use {@link #InsertResponseRecord(String, String, Map, Map, Map, int, String)} instead,
+ * which also lets you populate {@code data}. This overload always leaves {@code data} null.
+ */
+ @Deprecated(since = "1.0.2", forRemoval = true)
+ public InsertResponseRecord(String tableName, String skyflowId, Map> tokens,
Map hashedData, int httpCode, String error) {
+ this(tableName, skyflowId, tokens, null, hashedData, httpCode, error);
+ }
+
+ public InsertResponseRecord(String tableName, String skyflowId, Map> tokens,
+ Map data, Map hashedData, int httpCode, String error) {
this.tableName = tableName;
this.skyflowId = skyflowId;
- this.fields = fields;
+ this.tokens = tokens;
+ this.data = data;
this.hashedData = hashedData;
this.httpCode = httpCode;
this.error = error;
@@ -28,8 +44,27 @@ public String getSkyflowId() {
return skyflowId;
}
- public Map getFields() {
- return fields;
+ /**
+ * Per-column token data. The API models a column's tokens generically (see
+ * {@link Token#parseTokens(Map)}), but the SDK parses that into {@link Token} objects here
+ * so callers get {@link Token#getToken()}/{@link Token#getTokenGroupName()} directly, with no
+ * casting required.
+ */
+ public Map> getTokens() {
+ return tokens;
+ }
+
+ /**
+ * @deprecated Response key 'fields' is deprecated. Use {@link #getTokens()} instead.
+ */
+ @Deprecated(since = "1.0.2", forRemoval = true)
+ public Map> getFields() {
+ LogUtil.printWarningLog(InfoLogs.DEPRECATED_INSERT_FIELDS_GETTER.getLog());
+ return getTokens();
+ }
+
+ public Map getData() {
+ return data;
}
public Map getHashedData() {
diff --git a/flowvault/src/main/java/com/skyflow/vault/data/Token.java b/flowvault/src/main/java/com/skyflow/vault/data/Token.java
new file mode 100644
index 00000000..e84efb8f
--- /dev/null
+++ b/flowvault/src/main/java/com/skyflow/vault/data/Token.java
@@ -0,0 +1,103 @@
+package com.skyflow.vault.data;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.Expose;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * One token-group outcome for a single column value, as returned inside
+ * {@link InsertResponseRecord#getTokens()}. A column tokenized against more than one
+ * token group comes back as a list of these, one per group.
+ */
+public class Token {
+ @Expose(serialize = true)
+ private final String token;
+ @Expose(serialize = true)
+ private final String tokenGroupName;
+
+ public Token(String token, String tokenGroupName) {
+ this.token = token;
+ this.tokenGroupName = tokenGroupName;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public String getTokenGroupName() {
+ return tokenGroupName;
+ }
+
+ @Override
+ public String toString() {
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ return gson.toJson(this);
+ }
+
+ /**
+ * Parses the API's raw, generically-typed per-column token data (as returned by the wire
+ * type, {@code Map}) into {@code Map>}. The API models
+ * a column's tokens generically to stay flexible, so this parses every shape that generic
+ * value is known to take — a list of {@code {token, tokenGroupName}} entries (a column
+ * tokenized against more than one group), a single such entry, or a bare token value with
+ * no group information — into a consistently-typed {@code List} per column.
+ *
+ * Returns {@code null} when {@code rawTokens} is {@code null} (e.g. a failed record). A
+ * column whose raw value cannot be parsed into any of the above shapes is omitted, rather
+ * than throwing. A {@code null} element inside a column's list is skipped the same way.
+ */
+ public static Map> parseTokens(Map rawTokens) {
+ if (rawTokens == null) {
+ return null;
+ }
+ Map> parsed = new LinkedHashMap<>();
+ for (Map.Entry entry : rawTokens.entrySet()) {
+ List tokens = parseTokenEntries(entry.getValue());
+ if (tokens != null) {
+ parsed.put(entry.getKey(), tokens);
+ }
+ }
+ return parsed;
+ }
+
+ private static List parseTokenEntries(Object rawValue) {
+ if (rawValue == null) {
+ return null;
+ }
+ List parsed = new ArrayList<>();
+ if (rawValue instanceof List) {
+ for (Object entry : (List>) rawValue) {
+ Token token = toToken(entry);
+ if (token != null) {
+ parsed.add(token);
+ }
+ }
+ } else {
+ Token token = toToken(rawValue);
+ if (token != null) {
+ parsed.add(token);
+ }
+ }
+ return parsed;
+ }
+
+ private static Token toToken(Object entry) {
+ if (entry instanceof Map) {
+ Map, ?> entryMap = (Map, ?>) entry;
+ Object token = entryMap.get("token");
+ Object tokenGroupName = entryMap.get("tokenGroupName");
+ return new Token(token != null ? token.toString() : null,
+ tokenGroupName != null ? tokenGroupName.toString() : null);
+ }
+ if (entry != null) {
+ // A column tokenized against a single, unnamed group can come back as a bare value.
+ return new Token(entry.toString(), null);
+ }
+ return null;
+ }
+}
diff --git a/flowvault/src/test/java/com/skyflow/SkyflowTests.java b/flowvault/src/test/java/com/skyflow/SkyflowTests.java
index 7986e68a..4052c4a9 100644
--- a/flowvault/src/test/java/com/skyflow/SkyflowTests.java
+++ b/flowvault/src/test/java/com/skyflow/SkyflowTests.java
@@ -509,11 +509,66 @@ public void testVaultById_removedVaultThrowsWhileOthersStillResolve() throws Sky
}
}
- // ── getVaultConfig ────────────────────────────────────────────────────────
+ // ── getVaultConfig() ──────────────────────────────────────────────────────
+
+ @Test
+ public void testGetVaultConfig_returnsTheOnlyConfiguredVault() throws SkyflowException {
+ // addVaultConfigTemplate stores cloneVaultConfig(vaultConfig), not the same reference,
+ // so compare fields rather than identity against the config passed into addVaultConfig.
+ Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build();
+ Assert.assertEquals("vault1", client.getVaultConfig().getVaultId());
+ Assert.assertEquals("cluster1", client.getVaultConfig().getClusterId());
+ }
+
+ @Test
+ public void testGetVaultConfig_returnsFirstConfiguredVaultAmongSeveral() throws SkyflowException {
+ Skyflow client = Skyflow.builder()
+ .addVaultConfig(buildConfig("vault1", "cluster1"))
+ .addVaultConfig(buildConfig("vault2", "cluster2"))
+ .build();
+
+ Assert.assertEquals("vault1", client.getVaultConfig().getVaultId());
+ // Consistent with vault(): the no-arg accessor always resolves to the first-registered vault.
+ Assert.assertSame(client.getVaultConfig("vault1"), client.getVaultConfig());
+ }
+
+ @Test
+ public void testGetVaultConfig_returnsNullWhenNoConfigExists() {
+ // Regression test: this used to be Object[] array = ...toArray(); return (VaultConfig)
+ // array[0], which threw ArrayIndexOutOfBoundsException on an empty map instead of
+ // failing gracefully like every other lookup in this class.
+ Assert.assertNull(Skyflow.builder().build().getVaultConfig());
+ }
+
+ @Test
+ public void testGetVaultConfig_returnsNullAfterTheOnlyVaultIsRemoved() throws SkyflowException {
+ Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build();
+ client.removeVaultConfig("vault1");
+
+ Assert.assertNull(client.getVaultConfig());
+ }
+
+ @Test
+ public void testGetVaultConfig_fallsBackToTheRemainingVaultAfterTheFirstIsRemoved() throws SkyflowException {
+ Skyflow client = Skyflow.builder()
+ .addVaultConfig(buildConfig("vault1", "cluster1"))
+ .addVaultConfig(buildConfig("vault2", "cluster2"))
+ .build();
+ client.removeVaultConfig("vault1");
+
+ Assert.assertEquals("vault2", client.getVaultConfig().getVaultId());
+ }
+
+ // ── getVaultConfig(vaultId) ───────────────────────────────────────────────
@Test
public void testGetVaultConfig_returnsNullForUnknownVaultId() throws SkyflowException {
Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build();
Assert.assertNull(client.getVaultConfig("vault-unknown"));
}
+
+ @Test
+ public void testGetVaultConfig_byIdReturnsNullWhenNoConfigExists() {
+ Assert.assertNull(Skyflow.builder().build().getVaultConfig("vault1"));
+ }
}
diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java
index 4e034bfd..2ba3effd 100644
--- a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java
+++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java
@@ -1687,9 +1687,12 @@ public void testHandleBulkTokenizeBatchException_nullBatchReturnsEmpty() {
public void testFormatBulkInsertResponse_success() {
Map tokens = new HashMap<>();
tokens.put("name", "tok-abc");
+ Map data = new HashMap<>();
+ data.put("name", "john");
V1RecordResponseObject record = V1RecordResponseObject.builder()
.skyflowId("sky-id-1")
.tokens(tokens)
+ .data(data)
.build();
V1InsertResponse response = V1InsertResponse.builder().records(Collections.singletonList(record)).build();
@@ -1698,7 +1701,12 @@ public void testFormatBulkInsertResponse_success() {
Assert.assertEquals(1, result.getRecords().size());
BulkInsertResponseRecord inserted = result.getRecords().get(0);
Assert.assertEquals("sky-id-1", inserted.getSkyflowId());
- Assert.assertEquals(tokens, inserted.getFields());
+ // The wire type's raw tokens map is parsed into typed Token objects before reaching the
+ // caller - see ResponseComponentTests's Token.parseTokens() tests for the parsing logic.
+ Assert.assertEquals("tok-abc", inserted.getTokens().get("name").get(0).getToken());
+ // getFields() is deprecated but still delegates to getTokens() for backward compatibility.
+ Assert.assertEquals("tok-abc", inserted.getFields().get("name").get(0).getToken());
+ Assert.assertEquals(data, inserted.getData());
Assert.assertEquals(0, inserted.getIndex());
Assert.assertEquals(200, inserted.getHttpCode());
Assert.assertNull(inserted.getError());
diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java
index 72918e46..7d668283 100644
--- a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java
+++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java
@@ -41,6 +41,7 @@
import com.skyflow.vault.data.DeleteTokensOptions;
import com.skyflow.vault.data.InsertRequestRecord;
import com.skyflow.vault.data.RequestInterceptor;
+import com.skyflow.vault.data.Token;
import com.skyflow.vault.data.TokenGroupRedactions;
import com.skyflow.vault.data.TokenizeOptions;
import com.skyflow.vault.data.TokenizeRequestRecord;
@@ -704,14 +705,14 @@ public void testBulkInsert_successWithListOfMapsTokenShape() throws Exception {
Assert.assertEquals(1, response.getRecords().size());
BulkInsertResponseRecord inserted = response.getRecords().get(0);
- Assert.assertNotNull(inserted.getFields());
- // The token map is surfaced verbatim as `fields`, so a List