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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,11 @@ public class InsertSchema {
**Note**:
- The table name can be specified either at the request level `InsertRequest` or at the record level `InsertRecord`, but not both.
- If table name is not specified at the request level `InsertRequest`, then it must be specified in all record objects.
- If table name is specified at the request level `InsertRequest`, then upsert must also be specified at the request level.
- If table name is specified at the record level `InsertRecord`, then upsert must also be specified at the record level `InsertRecord`.
- Upsert must be specified in the same place as the table name: if table name is specified at the request level `InsertRequest`, specify upsert at the request level; if table name is specified at the record level `InsertRecord`, specify upsert at the record level `InsertRecord`.
- `upsertType` is optional and can be set alongside `upsert` at either the request level or the record level (matching the table/upsert placement):
- `UpsertType.UPDATE` — updates only the columns provided in the request on the matched row; other existing columns are retained.
- `UpsertType.REPLACE` — replaces the matched row with the provided values; columns not provided are cleared.
- If `upsertType` is not specified, the vault applies the default of `UPDATE`.

### An [example](https://github.com/skyflowapi/skyflow-java/blob/v3/samples/src/main/java/com/example/vault/BulkInsertSync.java) of a sync bulkInsert call

Expand Down
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,19 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<argLine>
@{argLine}
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.text=ALL-UNNAMED
--add-opens java.base/sun.security.x509=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
Expand Down
2 changes: 1 addition & 1 deletion samples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<dependency>
<groupId>com.skyflow</groupId>
<artifactId>skyflow-java</artifactId>
<version>3.0.0-beta.6</version>
<version>3.0.0-beta.11</version>
</dependency>

</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.enums.UpsertType;
import com.skyflow.vault.data.InsertRecord;
import com.skyflow.vault.data.InsertRequest;
import com.skyflow.vault.data.InsertResponse;
Expand Down Expand Up @@ -59,7 +58,8 @@ public static void main(String[] args) {
.data(recordData1)
.table("<YOUR_TABLE_NAME>")
.upsert(upsertColumns)
.upsertType(UpsertType.UPDATE)
// upsertType is optional; when omitted the vault defaults to UpsertType.UPDATE.
// Set .upsertType(UpsertType.REPLACE) to replace the matched row instead.
.build();

// Step 5: Prepare second record for insertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.enums.UpsertType;
import com.skyflow.errors.SkyflowException;
import com.skyflow.vault.data.InsertRecord;
import com.skyflow.vault.data.InsertRequest;
Expand Down Expand Up @@ -58,7 +57,8 @@ public static void main(String[] args) {
.data(recordData1)
.table("<YOUR_TABLE_NAME>")
.upsert(upsertColumns)
.upsertType(UpsertType.UPDATE)
// upsertType is optional; when omitted the vault defaults to UpsertType.UPDATE.
// Set .upsertType(UpsertType.REPLACE) to replace the matched row instead.
.build();

// Step 5: Prepare second record for insertion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.example.vault;

import com.skyflow.Skyflow;
import com.skyflow.config.Credentials;
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.Env;
import com.skyflow.enums.LogLevel;
import com.skyflow.errors.SkyflowException;

/**
* This sample demonstrates how to configure HTTP timeout and retry behavior in the Skyflow Java SDK.
*
* <p>Configurable settings (all optional):
* <ul>
* <li>{@code timeout} – overall call timeout in <b>seconds</b> (bounds the whole
* request including retries and backoff). Default: 60.</li>
* <li>{@code maxRetries} – retry attempts after the first failure (retries on HTTP
* 408 / 429 / 5xx). Default: 0 — retries are OFF unless you set this (avoids auto-retrying non-idempotent writes).</li>
* <li>{@code initialRetryDelayMillis} – base backoff before the first retry, in <b>milliseconds</b>.
* Default: 500.</li>
* <li>{@code maxRetryDelayMillis} – cap on the (exponentially growing) backoff, in
* <b>milliseconds</b>. Default: 2000.</li>
* </ul>
*
* <p><b>Two levels + precedence:</b> set client-wide defaults on {@code Skyflow.builder()}, and/or
* per-vault overrides on {@code VaultConfig}. The most specific value wins, resolved per field:
* <b>per-vault &rarr; client-wide &rarr; SDK default</b>.
*/
public class TimeoutAndRetryConfigExample {

public static void main(String[] args) {
try {
// Step 1: Initialize credentials with the path to your service account key file
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
Credentials credentials = new Credentials();
credentials.setPath(filePath);

// Step 2: Configure the vault. Optionally override timeout/retry settings for THIS vault only.
VaultConfig vaultConfig = new VaultConfig();
vaultConfig.setVaultId("<YOUR_VAULT_ID>");
vaultConfig.setClusterId("<YOUR_CLUSTER_ID>");
vaultConfig.setEnv(Env.PROD);
vaultConfig.setCredentials(credentials);
// Per-vault overrides (optional). Any field left unset inherits the client-wide default below,
// and then the SDK default.
vaultConfig.setTimeout(30); // seconds – tighter overall ceiling for this vault
vaultConfig.setMaxRetries(2); // fewer retries for this vault
vaultConfig.setInitialRetryDelayMillis(500L);
vaultConfig.setMaxRetryDelayMillis(1000L);

// Step 3: Create the Skyflow client. Client-wide defaults apply to every vault
// unless that vault overrides them (as above).
Skyflow skyflowClient = Skyflow.builder()
.setLogLevel(LogLevel.ERROR)
.timeout(60) // seconds – client-wide overall call timeout
.maxRetries(3) // client-wide retry attempts
.initialRetryDelayMillis(500L) // client-wide base backoff (ms)
.maxRetryDelayMillis(2000L) // client-wide backoff cap (ms)
.addVaultConfig(vaultConfig)
.build();

// Step 4: Use the client as usual. Requests now fail fast at the configured timeout and
// retry transient 408/429/5xx responses with exponential backoff + jitter.
System.out.println("Skyflow client configured with custom timeout & retry settings: " + skyflowClient);

// Example (uncomment and fill in a real request to try it):
// DetokenizeResponse response = skyflowClient.vault().detokenize(detokenizeRequest);
// System.out.println(response);
} catch (SkyflowException e) {
// Step 5: Handle any errors that occur during the process
System.err.println("Error in Skyflow operations: " + e.getMessage());
}
}
}
2 changes: 1 addition & 1 deletion v3/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</parent>

<artifactId>skyflow-java</artifactId>
<version>3.0.0-beta.11</version>
<version>2.1.1-dev.3196451</version>
<packaging>jar</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>Skyflow V3 SDK for the Java programming language</description>
Expand Down
43 changes: 42 additions & 1 deletion v3/src/main/java/com/skyflow/Skyflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public VaultController vault() throws SkyflowException {
public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder {
private final LinkedHashMap<String, VaultConfig> vaultConfigMap;
private final LinkedHashMap<String, VaultController> vaultClientsMap;
// Client-wide HTTP config defaults (apply to all vaults unless a vault overrides). null => SDK default.
private Integer timeout;
private Integer maxRetries;
private Long initialRetryDelayMillis;
private Long maxRetryDelayMillis;

public SkyflowClientBuilder() {
this.vaultConfigMap = new LinkedHashMap<>();
Expand All @@ -78,7 +83,9 @@ public SkyflowClientBuilder addVaultConfig(VaultConfig vaultConfig) throws Skyfl
ErrorMessage.VaultIdAlreadyInConfigList.getMessage());
} else {
this.vaultConfigMap.put(vaultConfigCopy.getVaultId(), vaultConfigCopy); // add new config in map
this.vaultClientsMap.put(vaultConfigCopy.getVaultId(), new VaultController(vaultConfigCopy, this.skyflowCredentials)); // add new controller with new config
VaultController controller = new VaultController(vaultConfigCopy, this.skyflowCredentials); // new controller with new config
controller.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis);
this.vaultClientsMap.put(vaultConfigCopy.getVaultId(), controller);
LogUtil.printInfoLog(Utils.parameterizedString(
InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfigCopy.getVaultId()));
}
Expand All @@ -100,6 +107,40 @@ public SkyflowClientBuilder addSkyflowCredentials(Credentials credentials) throw
return this;
}

/** Client-wide overall call timeout in seconds. Applies to all vaults unless a vault overrides it. */
public SkyflowClientBuilder timeout(int timeout) {
this.timeout = timeout;
propagateHttpConfig();
return this;
}

/** Client-wide retry attempt count. Applies to all vaults unless a vault overrides it. */
public SkyflowClientBuilder maxRetries(int maxRetries) {
this.maxRetries = maxRetries;
propagateHttpConfig();
return this;
}

/** Client-wide base retry backoff in milliseconds. Applies to all vaults unless a vault overrides it. */
public SkyflowClientBuilder initialRetryDelayMillis(long initialRetryDelayMillis) {
this.initialRetryDelayMillis = initialRetryDelayMillis;
propagateHttpConfig();
return this;
}

/** Client-wide retry backoff cap in milliseconds. Applies to all vaults unless a vault overrides it. */
public SkyflowClientBuilder maxRetryDelayMillis(long maxRetryDelayMillis) {
this.maxRetryDelayMillis = maxRetryDelayMillis;
propagateHttpConfig();
return this;
}

private void propagateHttpConfig() {
for (VaultController vault : this.vaultClientsMap.values()) {
vault.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis);
}
}

@Override
public SkyflowClientBuilder setLogLevel(LogLevel logLevel) {
super.setLogLevel(logLevel);
Expand Down
54 changes: 53 additions & 1 deletion v3/src/main/java/com/skyflow/VaultClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import com.skyflow.config.Credentials;
Expand All @@ -19,6 +20,7 @@
import com.skyflow.generated.rest.types.V1Upsert;
import com.skyflow.logs.InfoLogs;
import com.skyflow.serviceaccount.util.Token;
import com.skyflow.generated.rest.core.RetryInterceptor;
import com.skyflow.utils.Constants;
import com.skyflow.utils.Utils;
import com.skyflow.utils.logger.LogUtil;
Expand All @@ -45,6 +47,16 @@ public class VaultClient {
private String apiKey;
private OkHttpClient sharedHttpClient = null;
private String currentVaultURL = null;
// Client-wide (Skyflow builder) HTTP config defaults; null => fall back to the SDK defaults below.
private Integer commonTimeout;
private Integer commonMaxRetries;
private Long commonInitialRetryDelayMillis;
private Long commonMaxRetryDelayMillis;
// SDK defaults, used when neither the vault-level nor the client-wide value is set.
private static final int DEFAULT_TIMEOUT_SECONDS = 60;
private static final int DEFAULT_MAX_RETRIES = 0; // retries OFF by default (opt-in) so non-idempotent writes aren't auto-retried
private static final long DEFAULT_INITIAL_RETRY_DELAY_MILLIS = 500L;
private static final long DEFAULT_MAX_RETRY_DELAY_MILLIS = 2000L;

protected VaultClient(VaultConfig vaultConfig, Credentials credentials) throws SkyflowException {
super();
Expand All @@ -68,6 +80,20 @@ protected void setCommonCredentials(Credentials commonCredentials) throws Skyflo
prioritiseCredentials();
}

/**
* Client-wide HTTP timeout/retry defaults from the Skyflow builder. Nulls out the cached client
* so the next call rebuilds with the new values.
*/
protected void setCommonHttpConfig(Integer timeout, Integer maxRetries,
Long initialRetryDelayMillis, Long maxRetryDelayMillis) {
this.commonTimeout = timeout;
this.commonMaxRetries = maxRetries;
this.commonInitialRetryDelayMillis = initialRetryDelayMillis;
this.commonMaxRetryDelayMillis = maxRetryDelayMillis;
this.sharedHttpClient = null;
this.apiClient = null;
}

protected void setBearerToken() throws SkyflowException {
prioritiseCredentials();
Validations.validateCredentials(this.finalCredentials);
Expand Down Expand Up @@ -143,9 +169,19 @@ private void prioritiseCredentials() throws SkyflowException {

protected void updateExecutorInHTTP() {
if (sharedHttpClient == null) {
int timeoutSeconds = resolveInt(vaultConfig.getTimeout(), commonTimeout, DEFAULT_TIMEOUT_SECONDS);
int maxRetries = resolveInt(vaultConfig.getMaxRetries(), commonMaxRetries, DEFAULT_MAX_RETRIES);
long initialRetryDelayMillis = resolveLong(
vaultConfig.getInitialRetryDelayMillis(), commonInitialRetryDelayMillis, DEFAULT_INITIAL_RETRY_DELAY_MILLIS);
long maxRetryDelayMillis = resolveLong(
vaultConfig.getMaxRetryDelayMillis(), commonMaxRetryDelayMillis, DEFAULT_MAX_RETRY_DELAY_MILLIS);

sharedHttpClient = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES))
.addInterceptor(chain -> {
.callTimeout(timeoutSeconds, TimeUnit.SECONDS) // overall ceiling; bounds the whole call incl. retries
.addInterceptor(new RetryInterceptor( // OUTER: retries (Fern generated; jitter default 0.2)
maxRetries, Optional.of(initialRetryDelayMillis), Optional.of(maxRetryDelayMillis), Optional.empty()))
.addInterceptor(chain -> { // INNER: auth (reads this.token per request)
Request requestWithAuth = chain.request().newBuilder()
.header("Authorization", "Bearer " + this.token)
.build();
Expand All @@ -156,6 +192,22 @@ protected void updateExecutorInHTTP() {
}
}

/** Resolve an int setting: vault-level override, else client-wide default, else SDK default. */
private static int resolveInt(Integer vaultLevel, Integer clientLevel, int defaultValue) {
if (vaultLevel != null) {
return vaultLevel;
}
return clientLevel != null ? clientLevel : defaultValue;
}

/** Resolve a long setting: vault-level override, else client-wide default, else SDK default. */
private static long resolveLong(Long vaultLevel, Long clientLevel, long defaultValue) {
if (vaultLevel != null) {
return vaultLevel;
}
return clientLevel != null ? clientLevel : defaultValue;
}

protected V1InsertRequest getBulkInsertRequestBody(com.skyflow.vault.data.InsertRequest request, VaultConfig config) {
ArrayList<InsertRecord> records = request.getRecords();
List<V1InsertRecordData> insertRecordDataList = new ArrayList<>();
Expand Down
45 changes: 45 additions & 0 deletions v3/src/main/java/com/skyflow/config/VaultConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ public class VaultConfig implements Cloneable {
private String vaultURL;
private Env env;
private Credentials credentials;
// HTTP timeout & retry config (vault-level overrides). null => inherit client-wide default, then SDK default.
private Integer timeout; // overall call timeout, in seconds
private Integer maxRetries; // retry attempts after the first failure
private Long initialRetryDelayMillis; // base backoff before the first retry, in ms
private Long maxRetryDelayMillis; // cap on the (exponentially growing) backoff, in ms

public VaultConfig() {
this.vaultId = null;
this.clusterId = null;
this.vaultURL = null;
this.env = Env.PROD;
this.credentials = null;
this.timeout = null;
this.maxRetries = null;
this.initialRetryDelayMillis = null;
this.maxRetryDelayMillis = null;
}

public String getVaultId() {
Expand Down Expand Up @@ -57,6 +66,42 @@ public void setVaultURL(String vaultURL) {
this.vaultURL = vaultURL;
}

public Integer getTimeout() {
return timeout;
}

/** Overall call timeout in seconds for this vault. Overrides the client-wide default. */
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}

public Integer getMaxRetries() {
return maxRetries;
}

/** Number of retry attempts after the first failure for this vault. Overrides the client-wide default. */
public void setMaxRetries(Integer maxRetries) {
this.maxRetries = maxRetries;
}

public Long getInitialRetryDelayMillis() {
return initialRetryDelayMillis;
}

/** Base retry backoff in milliseconds for this vault. Overrides the client-wide default. */
public void setInitialRetryDelayMillis(Long initialRetryDelayMillis) {
this.initialRetryDelayMillis = initialRetryDelayMillis;
}

public Long getMaxRetryDelayMillis() {
return maxRetryDelayMillis;
}

/** Cap on retry backoff in milliseconds for this vault. Overrides the client-wide default. */
public void setMaxRetryDelayMillis(Long maxRetryDelayMillis) {
this.maxRetryDelayMillis = maxRetryDelayMillis;
}

@Override
public Object clone() throws CloneNotSupportedException {
VaultConfig cloned = (VaultConfig) super.clone();
Expand Down
Loading
Loading