Skip to content
Closed
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
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
29 changes: 7 additions & 22 deletions v3/src/main/java/com/skyflow/utils/validations/Validations.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,15 @@ public static void validateInsertRequest(InsertRequest insertRequest) throws Sky
}
}
}
// upsert check 1
if (insertRequest.getTable() != null && !table.trim().isEmpty()){ // if table name specified at both place
for (InsertRecord record : records) {
if (record.getUpsert() != null && record.getUpsert().isEmpty()) {
LogUtil.printErrorLog(Utils.parameterizedString(
ErrorLogs.EMPTY_UPSERT_VALUES.getLog(), InterfaceName.INSERT.getName()
));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyUpsertValues.getMessage());
}
if (record.getUpsert() != null && !record.getUpsert().isEmpty()){
LogUtil.printErrorLog(Utils.parameterizedString(
ErrorLogs.UPSERT_TABLE_REQUEST_AT_RECORD_LEVEL.getLog(), InterfaceName.INSERT.getName()
));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage());
}
}
}
// upsert check 2
if (insertRequest.getTable() == null || table.trim().isEmpty()){
if (insertRequest.getUpsert() != null && !insertRequest.getUpsert().isEmpty()){
// Upsert placement (request level vs record level) is validated by the backend,
// which returns the authoritative error message. The SDK only guards against
// empty upsert column lists.
for (InsertRecord record : records) {
if (record.getUpsert() != null && record.getUpsert().isEmpty()) {
LogUtil.printErrorLog(Utils.parameterizedString(
ErrorLogs.UPSERT_TABLE_REQUEST_AT_REQUEST_LEVEL.getLog(), InterfaceName.INSERT.getName()
ErrorLogs.EMPTY_UPSERT_VALUES.getLog(), InterfaceName.INSERT.getName()
));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage());
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyUpsertValues.getMessage());
}
}

Expand Down
69 changes: 69 additions & 0 deletions v3/src/test/java/com/skyflow/VaultClientTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,75 @@ public void testMixedTableAndUpsertLevels() {
Assert.assertEquals("col4", result.getUpsert().get().getUniqueColumns().get().get(0));
}

@Test
public void testUpsertAtRequestLevelWithoutUpsertTypeOmitsUpdateType() {
// When upsertType is not specified, the SDK must not send updateType so the
// backend applies its default (UPDATE).
Map<String, Object> data = new HashMap<>();
data.put("key", "value");
InsertRecord record = InsertRecord.builder().data(data).build();
ArrayList<InsertRecord> records = new ArrayList<>();
records.add(record);

com.skyflow.vault.data.InsertRequest request =
com.skyflow.vault.data.InsertRequest.builder()
.records(records)
.upsert(Arrays.asList("col1"))
.build();

V1InsertRequest result = vaultClient.getBulkInsertRequestBody(request, vaultConfig);
Assert.assertNotNull(result.getUpsert());
Assert.assertEquals("col1", result.getUpsert().get().getUniqueColumns().get().get(0));
Assert.assertFalse(result.getUpsert().get().getUpdateType().isPresent());
}

@Test
public void testUpsertAtRecordLevelWithoutUpsertTypeOmitsUpdateType() {
// When upsertType is not specified at the record level, the SDK must not send
// updateType so the backend applies its default (UPDATE).
Map<String, Object> data = new HashMap<>();
data.put("key", "value");
InsertRecord record = InsertRecord.builder().data(data).upsert(Arrays.asList("col2")).build();
ArrayList<InsertRecord> records = new ArrayList<>();
records.add(record);

com.skyflow.vault.data.InsertRequest request =
com.skyflow.vault.data.InsertRequest.builder()
.records(records)
.build();

V1InsertRequest result = vaultClient.getBulkInsertRequestBody(request, vaultConfig);
Assert.assertNotNull(result.getRecords().get().get(0).getUpsert());
Assert.assertEquals("col2", result.getRecords().get().get(0).getUpsert().get().getUniqueColumns().get().get(0));
Assert.assertFalse(result.getRecords().get().get(0).getUpsert().get().getUpdateType().isPresent());
}

@Test
public void testUpsertTypeAtBothRequestAndRecordLevelSerializesBoth() {
// The SDK serializes updateType wherever it is set; the backend rejects the
// both-levels combination. This asserts the SDK does not silently drop either.
Map<String, Object> data = new HashMap<>();
data.put("key", "value");
InsertRecord record = InsertRecord.builder()
.data(data)
.upsert(Arrays.asList("col2"))
.upsertType(UpsertType.UPDATE)
.build();
ArrayList<InsertRecord> records = new ArrayList<>();
records.add(record);

com.skyflow.vault.data.InsertRequest request =
com.skyflow.vault.data.InsertRequest.builder()
.records(records)
.upsert(Arrays.asList("col1"))
.upsertType(UpsertType.REPLACE)
.build();

V1InsertRequest result = vaultClient.getBulkInsertRequestBody(request, vaultConfig);
Assert.assertEquals("REPLACE", result.getUpsert().get().getUpdateType().get().name());
Assert.assertEquals("UPDATE", result.getRecords().get().get(0).getUpsert().get().getUpdateType().get().name());
}

@Test
public void testSetBearerTokenWhenTokenIsNull() throws Exception {
Credentials credentials = new Credentials();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ public void validateInsertRequest_tableMissing_throws() {
}

@Test
public void validateInsertRequest_upsertAtRecordLevel_throws() {
public void validateInsertRequest_upsertAtRecordLevelWithTableAtRequestLevel_deferredToBackend() throws SkyflowException {
// Upsert placement is no longer validated client-side; the backend owns the rule.
ArrayList<InsertRecord> records = new ArrayList<>();
records.add(InsertRecord.builder()
.table(null)
Expand All @@ -85,21 +86,20 @@ public void validateInsertRequest_upsertAtRecordLevel_throws() {
.table("requestTable")
.records(records)
.build();
assertSkyflowException(() -> Validations.validateInsertRequest(request),
ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage());
Validations.validateInsertRequest(request);
}

@Test
public void validateInsertRequest_upsertAtRequestLevelWithoutTable_throws() {
public void validateInsertRequest_upsertAtRequestLevelWithTableAtRecordLevel_deferredToBackend() throws SkyflowException {
// Upsert placement is no longer validated client-side; the backend owns the rule.
ArrayList<InsertRecord> records = new ArrayList<>();
records.add(InsertRecord.builder().table("recordTable").build());
InsertRequest request = InsertRequest.builder()
.table(null)
.upsert(Collections.singletonList("key"))
.records(records)
.build();
assertSkyflowException(() -> Validations.validateInsertRequest(request),
ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage());
Validations.validateInsertRequest(request);
}

@Test
Expand Down
31 changes: 7 additions & 24 deletions v3/src/test/java/com/skyflow/vault/data/InsertTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,20 +326,18 @@ public void testTableSpecifiedAtBothRequestAndRecordLevel() {
}
}

// Upsert placement (request vs record level) is validated by the backend, not the SDK.
// The SDK must accept these combinations and let the backend return the authoritative error.

@Test
public void testUpsertSpecifiedAtBothRequestAndRecordLevel() {
InsertRecord record = InsertRecord.builder().data(valueMap).upsert(upsert).build();
values.add(record);
InsertRequest request = InsertRequest.builder().table(table).records(values).upsert(upsert).build();
try {
Validations.validateInsertRequest(request);
Assert.fail(EXCEPTION_NOT_THROWN);
} catch (SkyflowException e) {
Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
Assert.assertEquals(
Utils.parameterizedString(ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage(), Constants.SDK_PREFIX),
e.getMessage()
);
Assert.fail(INVALID_EXCEPTION_THROWN);
}
}

Expand All @@ -350,13 +348,8 @@ public void testUpsertAtRecordLevelWithTableAtRequestLevel() {
InsertRequest request = InsertRequest.builder().table(table).records(values).build();
try {
Validations.validateInsertRequest(request);
Assert.fail(EXCEPTION_NOT_THROWN);
} catch (SkyflowException e) {
Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
Assert.assertEquals(
Utils.parameterizedString(ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage(), Constants.SDK_PREFIX),
e.getMessage()
);
Assert.fail(INVALID_EXCEPTION_THROWN);
}
}

Expand All @@ -367,13 +360,8 @@ public void testUpsertAtRequestLevelWithNoTable() {
InsertRequest request = InsertRequest.builder().records(values).upsert(upsert).build();
try {
Validations.validateInsertRequest(request);
Assert.fail(EXCEPTION_NOT_THROWN);
} catch (SkyflowException e) {
Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
Assert.assertEquals(
Utils.parameterizedString(ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage(), Constants.SDK_PREFIX),
e.getMessage()
);
Assert.fail(INVALID_EXCEPTION_THROWN);
}
}

Expand All @@ -387,13 +375,8 @@ public void testUpsertAtRequestLevelWithEmptyTable() {
InsertRequest request = InsertRequest.builder().table("").records(values).upsert(upsert).build();
try {
Validations.validateInsertRequest(request);
Assert.fail(EXCEPTION_NOT_THROWN);
} catch (SkyflowException e) {
Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode());
Assert.assertEquals(
Utils.parameterizedString(ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage(), Constants.SDK_PREFIX),
e.getMessage()
);
Assert.fail(INVALID_EXCEPTION_THROWN);
}
}

Expand Down
Loading