Skip to content

Commit ea945f7

Browse files
ArtDuclaude
andcommitted
feat(mapping): add CRUD metadata and TupleMapper
- Add TupleMapper utility class for easy tuple-to-POJO mapping using field format - Update CRUD operations (select, insert, get) to propagate format metadata from CrudResponse - Add getFormats() overload with CRUD metadata fallback - Add integration tests for select, insert, get with format metadata - Add test for fields filtering with GetOptions - Update TarantoolBoxClientTest to use TupleMapper instead of manual IntStream mapping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ed7c898 commit ea945f7

File tree

10 files changed

+700
-142
lines changed

10 files changed

+700
-142
lines changed

documentation/doc-src/pages/client/arch/tuple_pojo_mapping.en.md

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ objects.
2727

2828
## Efficient Mapping (Flatten input, Flatten output)
2929

30-
By default, field mapping in any of the clients (CrudClient, BoxClient), is performed in the most
30+
By default, field mapping in any of the clients (CrudClient, BoxClient) is performed in the most
3131
efficient way — by the field's ordinal number.
3232

3333
This means that if the field order in Tarantool is:
@@ -193,7 +193,7 @@ public class TestClass {
193193

194194
## Flexible Mapping Using Keys
195195

196-
You can also configure flexible mapping and work with keys in several ways. In these ways
196+
You can also configure flexible mapping and work with keys in several ways. In these approaches
197197
we will use the same data schema on the Tarantool side that we used for efficient
198198
mapping:
199199

@@ -225,7 +225,7 @@ public class UnorderedPerson {
225225
public Boolean isMarried;
226226
public Integer id;
227227

228-
public Person(
228+
public UnorderedPerson(
229229
@JsonProperty("is_married") Boolean isMarried,
230230
@JsonProperty("id") Integer id,
231231
@JsonProperty("name") String name) {
@@ -343,8 +343,8 @@ Deserialization is possible in several ways:
343343

344344
1. ```
345345
Flatten output method -- Using standard Tarantool read methods -> receiving Msgpack array \
346-
Converting array using data format to POJO format
347-
Getting the {field key -> field number} map in any way /
346+
Converting array using data format to POJO format
347+
Getting the {field key -> field number} map in any way /
348348
```
349349
2. ```
350350
Unflatten output -- Receiving Msgpack Map -> converting Msgpack Map to POJO using Jackson
@@ -371,7 +371,7 @@ List<UnorderedPerson> persons = routerClient.eval("""
371371
"person",
372372
Arrays.asList(Arrays.asList("==", "pk", 1))
373373
),
374-
new TypeReference<List<List<PersonWithDifferentFieldsOrder>>>() {}
374+
new TypeReference<List<List<UnorderedPerson>>>() {}
375375
).thenApply(
376376
tarantoolResponse -> tarantoolResponse.get() // unwrap TarantoolResponse
377377
.get(0) // get first object from multi return
@@ -424,29 +424,11 @@ public class TestClass {
424424
@Test
425425
public void test() {
426426
space.select(Arrays.asList(1)).thenApply(
427-
list -> {
428-
var result = new ArrayList<>();
429-
430-
for (var t : list.get()) { // unwrap tuple struct from select response struct
431-
List<?> dataList = t.get(); // unwrap data from tuple struct
432-
Map<String, ?> map = IntStream // create map {key -> value}
433-
.range(0, dataList.size())
434-
.boxed()
435-
.collect(
436-
Collectors.toMap(
437-
(i) -> tupleFormat.get(i).getName(),
438-
dataList::get
439-
)
440-
);
441-
// use jackson mapper to map from Map to Person POJO
442-
// import static io.tarantool.mapping.BaseTarantoolJacksonMapping.objectMapper;
443-
UnorderedPerson person = objectMapper.convertValue(map, UnorderedPerson.class);
444-
445-
result.add(person);
446-
}
447-
448-
return result;
449-
}
427+
list -> TupleMapper.mapToPojoList(
428+
list.get(),
429+
tupleFormat,
430+
UnorderedPerson.class
431+
)
450432
).join();
451433
// [UnorderedPerson{name='artyom', isMarried=true, id=1}]
452434
}
@@ -456,7 +438,7 @@ public class TestClass {
456438
##### 2. Using tarantool/crud Response Metadata
457439

458440
More information about the tarantool/crud response structure can be found
459-
here [github.com/tarantool/crud](https://github.com/tarantool/crud?tab=readme-ov-file#api).
441+
here [github.com/tarantool/crud](https://github.com/tarantool/crud?tab=readme-ov-file#api).
460442
Create a TarantoolCrud client that is a proxy to the tarantool/crud module API.
461443

462444
```java
@@ -481,7 +463,30 @@ person:format({
481463
var space = client.space("person");
482464
```
483465

484-
If you have a connector version that does not return tarantool/crud response metadata,
466+
###### Connector version > 1.5.0
467+
468+
Metadata can be obtained from TUPLE_EXT if the crud method supports TUPLE_EXT format, or from
469+
crud response metadata.
470+
471+
```java
472+
public class TestClass {
473+
474+
@Test
475+
public void test() {
476+
// Format is automatically passed from CrudResponse.metadata
477+
List<Tuple<List<?>>> tuples = space.select(
478+
Collections.singletonList(Condition.create(EQ, "id", 1))
479+
).join();
480+
481+
// Map using format from tuple
482+
UnorderedPerson person = TupleMapper.mapToPojo(tuples.get(0), UnorderedPerson.class);
483+
}
484+
}
485+
```
486+
487+
###### Connector version <= 1.5.0
488+
489+
If you have a connector version that does not return tarantool/crud response metadata,
485490
you can call the tarantool/crud methods directly:
486491

487492
```java
@@ -510,19 +515,8 @@ public class TestClass {
510515
}
511516

512517
for (List<?> tuple : tuples) {
513-
Map<Object, ?> map = IntStream // create map {key -> value}
514-
.range(0, tuple.size())
515-
.boxed()
516-
.collect(
517-
Collectors.toMap(
518-
(i) -> metadata.get(i).getName(),
519-
tuple::get
520-
)
521-
);
522-
523-
// use jackson mapper to map from Map to Person POJO
524-
// import static io.tarantool.mapping.BaseTarantoolJacksonMapping.objectMapper;
525-
UnorderedPerson person = objectMapper.convertValue(map, UnorderedPerson.class);
518+
// use TupleMapper to map from tuple data and format to Person POJO
519+
UnorderedPerson person = TupleMapper.mapToPojo(tuple, metadata, UnorderedPerson.class);
526520

527521
result.add(person);
528522
}
@@ -566,19 +560,9 @@ public class TestClass {
566560
}
567561

568562
for (Tuple<List<?>> tuple : tuples) {
569-
List<io.tarantool.mapping.Field> format = tuple.getFormat();
570-
List<?> data = tuple.get();
571-
Map<Object, ?> map = IntStream
572-
.range(0, data.size())
573-
.boxed()
574-
.collect(
575-
Collectors.toMap(
576-
(i) -> format.get(i).getName(),
577-
data::get
578-
)
579-
);
563+
// use TupleMapper to map tuple with embedded format to POJO
580564
result.add(
581-
objectMapper.convertValue(map, PersonWithDifferentFieldsOrder.class)
565+
TupleMapper.mapToPojo(tuple, PersonWithDifferentFieldsOrder.class)
582566
);
583567
}
584568

documentation/doc-src/pages/client/arch/tuple_pojo_mapping.md

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ title: Маппинг данных
88
Jackson.
99

1010
С помощью Jackson можно преобразовывать Java объекты в JSON (сериализация) и наоборот
11-
(десериализация). Также можно использовать расширения Jackson для других форматов сериализация
11+
(десериализация). Также можно использовать расширения Jackson для других форматов сериализации
1212
данных.
1313

1414
В `tarantool-java-sdk` используется библиотека Jackson с расширением для работы с `Msgpack`, которая
@@ -227,7 +227,7 @@ public class UnorderedPerson {
227227
public Boolean isMarried;
228228
public Integer id;
229229

230-
public Person(
230+
public UnorderedPerson(
231231
@JsonProperty("is_married") Boolean isMarried,
232232
@JsonProperty("id") Integer id,
233233
@JsonProperty("name") String name) {
@@ -314,7 +314,7 @@ person:format({
314314
})
315315
```
316316

317-
То метод в POJO должен выглядит так:
317+
То метод в POJO должен выглядеть так:
318318

319319
```java
320320
public class UnorderedPerson {
@@ -340,9 +340,9 @@ public class TestClass {
340340

341341
Что позволит передавать POJO как кортеж, при этом, продолжая, работать с объектом UnorderedPerson.
342342

343-
### Чтения POJO из Tarantool
343+
### Чтение POJO из Tarantool
344344

345-
Десериалиация возможна несколькими способами:
345+
Десериализация возможна несколькими способами:
346346

347347
1. ```
348348
Flatten output method -- Использование стандартных методов чтения Tarantool -> получение Msgpack массива \
@@ -374,7 +374,7 @@ List<UnorderedPerson> persons = routerClient.eval("""
374374
"person",
375375
Arrays.asList(Arrays.asList("==", "pk", 1))
376376
),
377-
new TypeReference<List<List<PersonWithDifferentFieldsOrder>>>() {}
377+
new TypeReference<List<List<UnorderedPerson>>>() {}
378378
).thenApply(
379379
tarantoolResponse -> tarantoolResponse.get() // unwrap TarantoolResponse
380380
.get(0) // get first object from multi return
@@ -428,29 +428,11 @@ public class TestClass {
428428
@Test
429429
public void test() {
430430
space.select(Arrays.asList(1)).thenApply(
431-
list -> {
432-
var result = new ArrayList<>();
433-
434-
for (var t : list.get()) { // unwrap tuple struct from select response struct
435-
List<?> dataList = t.get(); // unwrap data from tuple struct
436-
Map<String, ?> map = IntStream // create map {key -> value}
437-
.range(0, dataList.size())
438-
.boxed()
439-
.collect(
440-
Collectors.toMap(
441-
(i) -> tupleFormat.get(i).getName(),
442-
dataList::get
443-
)
444-
);
445-
// use jackson mapper to map from Map to Person POJO
446-
// import static io.tarantool.mapping.BaseTarantoolJacksonMapping.objectMapper;
447-
UnorderedPerson person = objectMapper.convertValue(map, UnorderedPerson.class);
448-
449-
result.add(person);
450-
}
451-
452-
return result;
453-
}
431+
list -> TupleMapper.mapToPojoList(
432+
list.get(),
433+
tupleFormat,
434+
UnorderedPerson.class
435+
)
454436
).join();
455437
// [UnorderedPerson{name='artyom', isMarried=true, id=1}]
456438
}
@@ -485,6 +467,29 @@ person:format({
485467
var space = client.space("person");
486468
```
487469

470+
###### Connector version > 1.5.0
471+
472+
Метаданные могут быть получены из TUPLE_EXT, если метод crud поддерживает TUPLE_EXT формат, или из
473+
metadata ответа crud.
474+
475+
```java
476+
public class TestClass {
477+
478+
@Test
479+
public void test() {
480+
// Формат автоматически передается из CrudResponse.metadata
481+
List<Tuple<List<?>>> tuples = space.select(
482+
Collections.singletonList(Condition.create(EQ, "id", 1))
483+
).join();
484+
485+
// Маппим используя формат из tuple
486+
UnorderedPerson person = TupleMapper.mapToPojo(tuples.get(0), UnorderedPerson.class);
487+
}
488+
}
489+
```
490+
491+
###### Connector version <= 1.5.0
492+
488493
Если у вас версия коннектора, которая не выдает метаданные ответа tarantool/crud,
489494
то можно вызывать методы tarantool/crud напрямую:
490495

@@ -514,19 +519,8 @@ public class TestClass {
514519
}
515520

516521
for (List<?> tuple : tuples) {
517-
Map<Object, ?> map = IntStream // create map {key -> value}
518-
.range(0, tuple.size())
519-
.boxed()
520-
.collect(
521-
Collectors.toMap(
522-
(i) -> metadata.get(i).getName(),
523-
tuple::get
524-
)
525-
);
526-
527-
// use jackson mapper to map from Map to Person POJO
528-
// import static io.tarantool.mapping.BaseTarantoolJacksonMapping.objectMapper;
529-
UnorderedPerson person = objectMapper.convertValue(map, UnorderedPerson.class);
522+
// use TupleMapper to map from tuple data and format to Person POJO
523+
UnorderedPerson person = TupleMapper.mapToPojo(tuple, metadata, UnorderedPerson.class);
530524

531525
result.add(person);
532526
}
@@ -545,19 +539,19 @@ IPROTO пакета. Отличие от предыдущего варианта
545539

546540
???+ warning "Важно"
547541

548-
В `crud` версии `1.7.1` функционал возврата формата ответа в отдельном поле IPROTO пакета не
542+
В `crud` версии `1.7.1` функционал возврата формата ответа в отдельном поле IPROTO пакета не
549543
поддерживается.
550544

551545
Воспользуемся Box API клиентом:
552546

553547
```java
554548
public class TestClass {
555-
549+
556550
@Test
557551
public void test() {
558-
559-
// other code
560-
552+
553+
// other code
554+
561555
space.select(Arrays.asList(1))
562556
.thenApply(
563557
selectResponse -> {
@@ -570,19 +564,9 @@ public class TestClass {
570564
}
571565

572566
for (Tuple<List<?>> tuple : tuples) {
573-
List<io.tarantool.mapping.Field> format = tuple.getFormat();
574-
List<?> data = tuple.get();
575-
Map<Object, ?> map = IntStream
576-
.range(0, data.size())
577-
.boxed()
578-
.collect(
579-
Collectors.toMap(
580-
(i) -> format.get(i).getName(),
581-
data::get
582-
)
583-
);
567+
// use TupleMapper to map tuple with embedded format to POJO
584568
result.add(
585-
objectMapper.convertValue(map, PersonWithDifferentFieldsOrder.class)
569+
TupleMapper.mapToPojo(tuple, UnorderedPerson.class)
586570
);
587571
}
588572

tarantool-client/src/test/java/io/tarantool/client/integration/TarantoolBoxClientTest.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@
1313
import java.util.HashMap;
1414
import java.util.HashSet;
1515
import java.util.List;
16-
import java.util.Map;
1716
import java.util.Set;
1817
import java.util.concurrent.CompletionException;
1918
import java.util.concurrent.TimeoutException;
2019
import java.util.function.Consumer;
2120
import java.util.function.Function;
2221
import java.util.stream.Collectors;
23-
import java.util.stream.IntStream;
2422
import java.util.stream.Stream;
2523

2624
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -49,7 +47,6 @@
4947
import org.testcontainers.shaded.com.google.common.base.CaseFormat;
5048

5149
import static io.tarantool.client.box.TarantoolBoxSpace.WITHOUT_ENABLED_FETCH_SCHEMA_OPTION_FOR_TARANTOOL_LESS_3_0_0;
52-
import static io.tarantool.mapping.BaseTarantoolJacksonMapping.objectMapper;
5350
import io.tarantool.client.BaseOptions;
5451
import io.tarantool.client.ClientType;
5552
import io.tarantool.client.Options;
@@ -69,6 +66,7 @@
6966
import io.tarantool.mapping.SelectResponse;
7067
import io.tarantool.mapping.TarantoolResponse;
7168
import io.tarantool.mapping.Tuple;
69+
import io.tarantool.mapping.TupleMapper;
7270
import io.tarantool.schema.NoSchemaException;
7371
import io.tarantool.schema.Space;
7472
import io.tarantool.schema.TarantoolSchemaFetcher;
@@ -399,12 +397,8 @@ public void testMappingWithFormat() {
399397

400398
for (Tuple<List<?>> t : list.get()) {
401399
List<io.tarantool.mapping.Field> tupleFormat = formatGetter.apply(t);
402-
List<?> dataList = t.get();
403-
Map<String, ?> map =
404-
IntStream.range(0, dataList.size())
405-
.boxed()
406-
.collect(Collectors.toMap((i) -> tupleFormat.get(i).getName(), dataList::get));
407-
result.add(objectMapper.convertValue(map, PersonWithDifferentFieldsOrder.class));
400+
result.add(
401+
TupleMapper.mapToPojo(t.get(), tupleFormat, PersonWithDifferentFieldsOrder.class));
408402
}
409403
return result;
410404
};

0 commit comments

Comments
 (0)