Skip to content

Commit 16b9eb0

Browse files
committed
fix: add domain to request bodies and fix whereWithKeys format
- Add domain as string to find/create/update/delete requests - Fix whereWithKeys to use where: {keys: [...]} format - Add WhereClause sealed interface for type-safe where clauses - Change create endpoint from /row/insert to /row - Add integration tests with InventoryHistory model - Update .gitignore to exclude CLAUDE.md and sensitive files
1 parent 5db8caa commit 16b9eb0

11 files changed

Lines changed: 335 additions & 47 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ replay_pid*
9595

9696
# Claude Code
9797
.claude/
98+
CLAUDE.md
9899

99100
# JetBrains
100101
cmake-build-*/
@@ -115,4 +116,3 @@ cmake-build-*/
115116
.idea/**/gradle.xml
116117
.idea/**/libraries
117118
.idea/**/mongoSettings.xml
118-
*.iws

src/main/java/com/sbxcloud/sbx/client/SBXService.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ public <T> SBXFindResponse<T> find(FindQuery query, Class<T> type) {
6565
* Finds records matching the query.
6666
*/
6767
public <T> SBXFindResponse<T> find(SBXFindRequest request, Class<T> type) {
68+
var requestWithDomain = request.withDomain(domain);
6869
if (debug) {
69-
log.info("SBX find: {}", request);
70+
log.info("SBX find: {}", requestWithDomain);
7071
}
7172

7273
try {
7374
var response = restClient.post()
7475
.uri("/api/data/v1/row/find")
75-
.body(request)
76+
.body(requestWithDomain)
7677
.retrieve()
7778
.body(new ParameterizedTypeReference<Map<String, Object>>() {});
7879

@@ -141,7 +142,7 @@ public <T> SBXFindResponse<T> findAll(FindQuery query, Class<T> type) {
141142
* Creates new records.
142143
*/
143144
public <T> SBXResponse<T> create(String model, List<Map<String, Object>> rows) {
144-
return upsert("/api/data/v1/row/insert", model, rows, true);
145+
return upsert("/api/data/v1/row", model, rows, true);
145146
}
146147

147148
/**
@@ -175,7 +176,7 @@ public SBXResponse<Void> delete(String model, List<String> keys) {
175176

176177
try {
177178
for (List<String> chunk : partition(keys, DEFAULT_CHUNK_SIZE)) {
178-
var request = SBXDeleteRequest.of(model, chunk);
179+
var request = SBXDeleteRequest.of(model, domain, chunk);
179180
var response = restClient.post()
180181
.uri("/api/data/v1/row/delete")
181182
.body(request)
@@ -698,7 +699,7 @@ private <T> SBXResponse<T> upsert(String endpoint, String model, List<Map<String
698699
List<String> allKeys = new ArrayList<>();
699700

700701
for (List<Map<String, Object>> chunk : partition(cleanedRows, DEFAULT_CHUNK_SIZE)) {
701-
var request = SBXUpsertRequest.of(model, chunk);
702+
var request = SBXUpsertRequest.of(model, domain, chunk);
702703
var response = restClient.post()
703704
.uri(endpoint)
704705
.body(request)

src/main/java/com/sbxcloud/sbx/model/SBXDeleteRequest.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44
import com.fasterxml.jackson.annotation.JsonProperty;
55

66
import java.util.List;
7+
import java.util.Map;
78

89
/**
910
* Request payload for delete operations.
1011
*/
1112
@JsonInclude(JsonInclude.Include.NON_NULL)
1213
public record SBXDeleteRequest(
1314
@JsonProperty("row_model") String rowModel,
14-
List<String> keys
15+
String domain,
16+
Map<String, List<String>> where
1517
) {
16-
public static SBXDeleteRequest of(String model, List<String> keys) {
17-
return new SBXDeleteRequest(model, keys);
18+
public static SBXDeleteRequest of(String model, int domain, List<String> keys) {
19+
return new SBXDeleteRequest(model, String.valueOf(domain), Map.of("keys", keys));
1820
}
1921

20-
public static SBXDeleteRequest ofSingle(String model, String key) {
21-
return new SBXDeleteRequest(model, List.of(key));
22+
public static SBXDeleteRequest ofSingle(String model, int domain, String key) {
23+
return of(model, domain, List.of(key));
2224
}
2325
}

src/main/java/com/sbxcloud/sbx/model/SBXFindRequest.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,45 @@
1111
@JsonInclude(JsonInclude.Include.NON_NULL)
1212
public record SBXFindRequest(
1313
@JsonProperty("row_model") String rowModel,
14-
List<LogicalGroup> where,
14+
String domain,
15+
WhereClause where,
1516
Integer page,
1617
Integer size,
1718
@JsonProperty("fetch") List<String> fetchModels,
1819
@JsonProperty("rfetch") List<String> fetchReferencingModels,
19-
@JsonProperty("autowire") List<String> autowire,
20-
List<String> keys
20+
@JsonProperty("autowire") List<String> autowire
2121
) {
2222
public static Builder builder(String model) {
2323
return new Builder(model);
2424
}
2525

26+
/**
27+
* Returns a copy of this request with the given domain.
28+
*/
29+
public SBXFindRequest withDomain(int domain) {
30+
return new SBXFindRequest(rowModel, String.valueOf(domain), where, page, size, fetchModels, fetchReferencingModels, autowire);
31+
}
32+
2633
public static class Builder {
2734
private final String rowModel;
28-
private List<LogicalGroup> where;
35+
private String domain;
36+
private WhereClause where;
2937
private Integer page;
3038
private Integer size;
3139
private List<String> fetchModels;
3240
private List<String> fetchReferencingModels;
3341
private List<String> autowire;
34-
private List<String> keys;
3542

3643
private Builder(String rowModel) {
3744
this.rowModel = rowModel;
3845
}
3946

40-
public Builder where(List<LogicalGroup> where) {
47+
public Builder domain(int domain) {
48+
this.domain = String.valueOf(domain);
49+
return this;
50+
}
51+
52+
public Builder where(WhereClause where) {
4153
this.where = where;
4254
return this;
4355
}
@@ -67,13 +79,8 @@ public Builder autowire(List<String> autowire) {
6779
return this;
6880
}
6981

70-
public Builder keys(List<String> keys) {
71-
this.keys = keys;
72-
return this;
73-
}
74-
7582
public SBXFindRequest build() {
76-
return new SBXFindRequest(rowModel, where, page, size, fetchModels, fetchReferencingModels, autowire, keys);
83+
return new SBXFindRequest(rowModel, domain, where, page, size, fetchModels, fetchReferencingModels, autowire);
7784
}
7885
}
7986
}

src/main/java/com/sbxcloud/sbx/model/SBXProperty.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package com.sbxcloud.sbx.model;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
34
import com.fasterxml.jackson.annotation.JsonProperty;
45

56
/**
67
* Model field/property definition.
78
*/
9+
@JsonIgnoreProperties(ignoreUnknown = true)
810
public record SBXProperty(
911
Integer id,
1012
String name,
1113
FieldType type,
12-
@JsonProperty("reference_model") String referenceModel,
14+
@JsonProperty("reference_model") Object referenceModel,
1315
@JsonProperty("reference_model_id") Integer referenceModelId,
16+
@JsonProperty("reference_type_name") String referenceTypeName,
1417
@JsonProperty("default_value") String defaultValue,
1518
Boolean required
1619
) {

src/main/java/com/sbxcloud/sbx/model/SBXUpsertRequest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
@JsonInclude(JsonInclude.Include.NON_NULL)
1313
public record SBXUpsertRequest(
1414
@JsonProperty("row_model") String rowModel,
15+
String domain,
1516
List<Map<String, Object>> rows
1617
) {
17-
public static SBXUpsertRequest of(String model, List<Map<String, Object>> rows) {
18-
return new SBXUpsertRequest(model, rows);
18+
public static SBXUpsertRequest of(String model, int domain, List<Map<String, Object>> rows) {
19+
return new SBXUpsertRequest(model, String.valueOf(domain), rows);
1920
}
2021

21-
public static SBXUpsertRequest ofSingle(String model, Map<String, Object> row) {
22-
return new SBXUpsertRequest(model, List.of(row));
22+
public static SBXUpsertRequest ofSingle(String model, int domain, Map<String, Object> row) {
23+
return of(model, domain, List.of(row));
2324
}
2425
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.sbxcloud.sbx.model;
2+
3+
import com.fasterxml.jackson.core.JsonGenerator;
4+
import com.fasterxml.jackson.databind.JsonSerializer;
5+
import com.fasterxml.jackson.databind.SerializerProvider;
6+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
7+
8+
import java.io.IOException;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
/**
13+
* Represents the "where" clause in SBX queries.
14+
* Can be either condition-based (list of groups) or key-based.
15+
*/
16+
@JsonSerialize(using = WhereClause.Serializer.class)
17+
public sealed interface WhereClause permits WhereClause.Conditions, WhereClause.Keys {
18+
19+
/**
20+
* Condition-based where clause (list of logical groups).
21+
*/
22+
record Conditions(List<LogicalGroup> groups) implements WhereClause {}
23+
24+
/**
25+
* Key-based where clause for finding by primary keys.
26+
*/
27+
record Keys(List<String> keys) implements WhereClause {}
28+
29+
static WhereClause conditions(List<LogicalGroup> groups) {
30+
return new Conditions(groups);
31+
}
32+
33+
static WhereClause keys(List<String> keys) {
34+
return new Keys(keys);
35+
}
36+
37+
/**
38+
* Custom serializer to output the correct JSON format.
39+
*/
40+
class Serializer extends JsonSerializer<WhereClause> {
41+
@Override
42+
public void serialize(WhereClause value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
43+
switch (value) {
44+
case Conditions c -> gen.writeObject(c.groups());
45+
case Keys k -> gen.writeObject(Map.of("keys", k.keys()));
46+
}
47+
}
48+
}
49+
}

src/main/java/com/sbxcloud/sbx/query/FindQuery.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,15 +426,24 @@ public FindQuery setPageSize(int pageSize) {
426426
* Compiles the query into a request payload.
427427
*/
428428
public SBXFindRequest compile() {
429+
WhereClause whereClause;
430+
if (keys != null && !keys.isEmpty()) {
431+
whereClause = WhereClause.keys(keys);
432+
} else if (!groups.isEmpty()) {
433+
whereClause = WhereClause.conditions(groups);
434+
} else {
435+
whereClause = null;
436+
}
437+
429438
return new SBXFindRequest(
430439
model,
431-
groups.isEmpty() ? null : groups,
440+
null, // domain is added by SBXService
441+
whereClause,
432442
page,
433443
pageSize,
434444
fetchModels,
435445
fetchReferencingModels,
436-
autowire,
437-
keys
446+
autowire
438447
);
439448
}
440449

src/test/java/com/sbxcloud/sbx/FindQueryTest.java

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.sbxcloud.sbx.model.AndOr;
55
import com.sbxcloud.sbx.model.Operation;
6+
import com.sbxcloud.sbx.model.WhereClause;
67
import com.sbxcloud.sbx.query.FindQuery;
78
import org.junit.jupiter.api.Test;
89

@@ -22,11 +23,14 @@ void shouldBuildSimpleQuery() {
2223

2324
assertEquals("contact", query.rowModel());
2425
assertNotNull(query.where());
25-
assertEquals(1, query.where().size());
26-
assertEquals(AndOr.AND, query.where().get(0).andOr());
27-
assertEquals(1, query.where().get(0).group().size());
26+
assertInstanceOf(WhereClause.Conditions.class, query.where());
2827

29-
var expr = query.where().get(0).group().get(0);
28+
var conditions = (WhereClause.Conditions) query.where();
29+
assertEquals(1, conditions.groups().size());
30+
assertEquals(AndOr.AND, conditions.groups().get(0).andOr());
31+
assertEquals(1, conditions.groups().get(0).group().size());
32+
33+
var expr = conditions.groups().get(0).group().get(0);
3034
assertEquals("status", expr.field());
3135
assertEquals(Operation.EQUAL, expr.operation());
3236
assertEquals("ACTIVE", expr.value());
@@ -44,15 +48,16 @@ void shouldBuildQueryWithMultipleConditions() {
4448
.compile();
4549

4650
assertEquals("contact", query.rowModel());
47-
assertEquals(2, query.where().size());
51+
var conditions = (WhereClause.Conditions) query.where();
52+
assertEquals(2, conditions.groups().size());
4853

4954
// First group: AND
50-
assertEquals(AndOr.AND, query.where().get(0).andOr());
51-
assertEquals(2, query.where().get(0).group().size());
55+
assertEquals(AndOr.AND, conditions.groups().get(0).andOr());
56+
assertEquals(2, conditions.groups().get(0).group().size());
5257

5358
// Second group: OR
54-
assertEquals(AndOr.OR, query.where().get(1).andOr());
55-
assertEquals(2, query.where().get(1).group().size());
59+
assertEquals(AndOr.OR, conditions.groups().get(1).andOr());
60+
assertEquals(2, conditions.groups().get(1).group().size());
5661
}
5762

5863
@Test
@@ -86,7 +91,11 @@ void shouldBuildQueryWithKeys() {
8691
.whereWithKeys("key1", "key2", "key3")
8792
.compile();
8893

89-
assertEquals(List.of("key1", "key2", "key3"), query.keys());
94+
assertNotNull(query.where());
95+
assertInstanceOf(WhereClause.Keys.class, query.where());
96+
97+
var keysClause = (WhereClause.Keys) query.where();
98+
assertEquals(List.of("key1", "key2", "key3"), keysClause.keys());
9099
}
91100

92101
@Test
@@ -95,7 +104,8 @@ void shouldBuildQueryWithInOperator() {
95104
.andWhereIsIn("status", "ACTIVE", "PENDING")
96105
.compile();
97106

98-
var expr = query.where().get(0).group().get(0);
107+
var conditions = (WhereClause.Conditions) query.where();
108+
var expr = conditions.groups().get(0).group().get(0);
99109
assertEquals(Operation.IN, expr.operation());
100110
assertEquals(List.of("ACTIVE", "PENDING"), expr.value());
101111
}
@@ -107,13 +117,14 @@ void shouldBuildQueryWithNullChecks() {
107117
.andWhereIsNotNull("email")
108118
.compile();
109119

110-
assertEquals(2, query.where().get(0).group().size());
120+
var conditions = (WhereClause.Conditions) query.where();
121+
assertEquals(2, conditions.groups().get(0).group().size());
111122

112-
var nullExpr = query.where().get(0).group().get(0);
123+
var nullExpr = conditions.groups().get(0).group().get(0);
113124
assertEquals(Operation.IS, nullExpr.operation());
114125
assertNull(nullExpr.value());
115126

116-
var notNullExpr = query.where().get(0).group().get(1);
127+
var notNullExpr = conditions.groups().get(0).group().get(1);
117128
assertEquals(Operation.IS_NOT, notNullExpr.operation());
118129
assertNull(notNullExpr.value());
119130
}
@@ -126,7 +137,8 @@ void shouldBuildQueryWithLikeOperations() {
126137
.andWhereContains("address", "Street")
127138
.compile();
128139

129-
var group = query.where().get(0).group();
140+
var conditions = (WhereClause.Conditions) query.where();
141+
var group = conditions.groups().get(0).group();
130142

131143
assertEquals("John%", group.get(0).value());
132144
assertEquals("%@example.com", group.get(1).value());
@@ -139,7 +151,8 @@ void shouldEscapePercentInContains() {
139151
.andWhereContains("name", "100%")
140152
.compile();
141153

142-
var expr = query.where().get(0).group().get(0);
154+
var conditions = (WhereClause.Conditions) query.where();
155+
var expr = conditions.groups().get(0).group().get(0);
143156
assertEquals("%100%", expr.value());
144157
}
145158
}

0 commit comments

Comments
 (0)