11
11
import tech .ydb .yoj .databind .FieldValueType ;
12
12
import tech .ydb .yoj .databind .schema .ObjectSchema ;
13
13
import tech .ydb .yoj .databind .schema .Schema .JavaField ;
14
- import tech .ydb .yoj .databind .schema .Schema .JavaFieldValue ;
15
14
16
15
import javax .annotation .Nullable ;
17
16
import java .lang .reflect .Type ;
18
17
import java .time .Instant ;
18
+ import java .util .ArrayList ;
19
19
import java .util .Collections ;
20
20
import java .util .List ;
21
21
import java .util .Map ;
22
22
import java .util .Objects ;
23
+ import java .util .UUID ;
23
24
import java .util .stream .Stream ;
24
25
25
26
import static java .util .stream .Collectors .collectingAndThen ;
26
27
import static java .util .stream .Collectors .joining ;
27
- import static java .util .stream .Collectors .toList ;
28
+ import static java .util .stream .Collectors .toCollection ;
28
29
import static lombok .AccessLevel .PRIVATE ;
29
30
30
31
@ Value
@@ -37,40 +38,46 @@ public class FieldValue {
37
38
Instant timestamp ;
38
39
Tuple tuple ;
39
40
ByteArray byteArray ;
41
+ UUID uuid ;
40
42
41
43
@ NonNull
42
44
public static FieldValue ofStr (@ NonNull String str ) {
43
- return new FieldValue (str , null , null , null , null , null , null );
45
+ return new FieldValue (str , null , null , null , null , null , null , null );
44
46
}
45
47
46
48
@ NonNull
47
49
public static FieldValue ofNum (long num ) {
48
- return new FieldValue (null , num , null , null , null , null , null );
50
+ return new FieldValue (null , num , null , null , null , null , null , null );
49
51
}
50
52
51
53
@ NonNull
52
54
public static FieldValue ofReal (double real ) {
53
- return new FieldValue (null , null , real , null , null , null , null );
55
+ return new FieldValue (null , null , real , null , null , null , null , null );
54
56
}
55
57
56
58
@ NonNull
57
59
public static FieldValue ofBool (boolean bool ) {
58
- return new FieldValue (null , null , null , bool , null , null , null );
60
+ return new FieldValue (null , null , null , bool , null , null , null , null );
59
61
}
60
62
61
63
@ NonNull
62
64
public static FieldValue ofTimestamp (@ NonNull Instant timestamp ) {
63
- return new FieldValue (null , null , null , null , timestamp , null , null );
65
+ return new FieldValue (null , null , null , null , timestamp , null , null , null );
64
66
}
65
67
66
68
@ NonNull
67
69
public static FieldValue ofTuple (@ NonNull Tuple tuple ) {
68
- return new FieldValue (null , null , null , null , null , tuple , null );
70
+ return new FieldValue (null , null , null , null , null , tuple , null , null );
69
71
}
70
72
71
73
@ NonNull
72
74
public static FieldValue ofByteArray (@ NonNull ByteArray byteArray ) {
73
- return new FieldValue (null , null , null , null , null , null , byteArray );
75
+ return new FieldValue (null , null , null , null , null , null , byteArray , null );
76
+ }
77
+
78
+ @ NonNull
79
+ public static FieldValue ofUuid (@ NonNull UUID uuid ) {
80
+ return new FieldValue (null , null , null , null , null , null , null , uuid );
74
81
}
75
82
76
83
@ NonNull
@@ -100,28 +107,36 @@ public static FieldValue ofObj(@NonNull Object obj, @NonNull JavaField schemaFie
100
107
case TIMESTAMP -> {
101
108
return ofTimestamp ((Instant ) obj );
102
109
}
110
+ case UUID -> {
111
+ return ofUuid ((UUID ) obj );
112
+ }
103
113
case COMPOSITE -> {
104
- ObjectSchema schema = ObjectSchema .of (obj .getClass ());
114
+ ObjectSchema <?> schema = ObjectSchema .of (obj .getClass ());
105
115
List <JavaField > flatFields = schema .flattenFields ();
106
- Map <String , Object > flattenedObj = schema .flatten (obj );
107
116
108
- List <JavaFieldValue > allFieldValues = flatFields .stream ()
109
- .map (jf -> new JavaFieldValue (jf , flattenedObj .get (jf .getName ())))
110
- .collect (collectingAndThen (toList (), Collections ::unmodifiableList ));
117
+ @ SuppressWarnings ({"rawtypes" , "unchecked" })
118
+ Map <String , Object > flattenedObj = ((ObjectSchema ) schema ).flatten (obj );
119
+
120
+ List <FieldAndValue > allFieldValues = tupleValues (flatFields , flattenedObj );
111
121
if (allFieldValues .size () == 1 ) {
112
- JavaFieldValue singleValue = allFieldValues .iterator ().next ();
113
- Preconditions .checkArgument (singleValue . getValue () != null , "Wrappers must have a non-null value inside them" );
114
- return ofObj ( singleValue . getValue (), singleValue . getField ()) ;
122
+ FieldValue singleValue = allFieldValues .iterator ().next (). value ();
123
+ Preconditions .checkArgument (singleValue != null , "Wrappers must have a non-null value inside them" );
124
+ return singleValue ;
115
125
}
116
126
return ofTuple (new Tuple (obj , allFieldValues ));
117
127
}
118
- default -> throw new UnsupportedOperationException (
119
- "Unsupported value type: not a string, integer, timestamp, enum, "
120
- + "floating-point number, byte array, tuple or wrapper of the above"
121
- );
128
+ default -> throw new UnsupportedOperationException ("Unsupported value type: not a string, integer, timestamp, UUID, enum, "
129
+ + "floating-point number, byte array, tuple or wrapper of the above" );
122
130
}
123
131
}
124
132
133
+ private static @ NonNull List <FieldAndValue > tupleValues (List <JavaField > flatFields , Map <String , Object > flattenedObj ) {
134
+ return flatFields .stream ()
135
+ .map (jf -> new FieldAndValue (jf , flattenedObj ))
136
+ // Tuple values are allowed to be null, so we explicitly use ArrayList, just make it unmodifiable
137
+ .collect (collectingAndThen (toCollection (ArrayList ::new ), Collections ::unmodifiableList ));
138
+ }
139
+
125
140
public boolean isNumber () {
126
141
return num != null ;
127
142
}
@@ -150,17 +165,18 @@ public boolean isByteArray() {
150
165
return byteArray != null ;
151
166
}
152
167
168
+ public boolean isUuid () {
169
+ return uuid != null ;
170
+ }
171
+
153
172
@ Nullable
154
173
public static Comparable <?> getComparable (@ NonNull Map <String , Object > values ,
155
174
@ NonNull JavaField field ) {
156
175
if (field .isFlat ()) {
157
176
Object rawValue = values .get (field .getName ());
158
177
return rawValue == null ? null : ofObj (rawValue , field .toFlatField ()).getComparable (field );
159
178
} else {
160
- List <JavaFieldValue > components = field .flatten ()
161
- .map (jf -> new JavaFieldValue (jf , values .get (jf .getName ())))
162
- .toList ();
163
- return new Tuple (null , components );
179
+ return new Tuple (null , tupleValues (field .flatten ().toList (), values ));
164
180
}
165
181
}
166
182
@@ -221,6 +237,21 @@ public Comparable<?> getComparable(@NonNull JavaField field) {
221
237
}
222
238
throw new IllegalStateException ("Value cannot be converted to timestamp: " + this );
223
239
}
240
+ case UUID -> {
241
+ // Compare UUIDs as String representations
242
+ // Rationale: @see https://devblogs.microsoft.com/oldnewthing/20190913-00/?p=102859
243
+ if (isUuid ()) {
244
+ return uuid .toString ();
245
+ } else if (isString ()) {
246
+ try {
247
+ UUID .fromString (str );
248
+ return str ;
249
+ } catch (IllegalArgumentException ignored ) {
250
+ // ...no-op here because we will throw IllegalStateException right after the try() and if (isString())
251
+ }
252
+ }
253
+ throw new IllegalStateException ("Value cannot be converted to UUID: " + this );
254
+ }
224
255
case BOOLEAN -> {
225
256
Preconditions .checkState (isBool (), "Value is not a boolean: %s" , this );
226
257
return bool ;
@@ -252,8 +283,14 @@ public String toString() {
252
283
return bool .toString ();
253
284
} else if (isTimestamp ()) {
254
285
return "#" + timestamp + "#" ;
255
- } else {
286
+ } else if (isByteArray ()) {
287
+ return byteArray .toString ();
288
+ } else if (isTuple ()) {
256
289
return tuple .toString ();
290
+ } else if (isUuid ()) {
291
+ return "uuid(" + uuid + ")" ;
292
+ } else {
293
+ return "???" ;
257
294
}
258
295
}
259
296
@@ -272,7 +309,9 @@ public boolean equals(Object o) {
272
309
&& Objects .equals (bool , that .bool )
273
310
&& Objects .equals (timestamp , that .timestamp )
274
311
&& Objects .equals (real , that .real )
275
- && Objects .equals (tuple , that .tuple );
312
+ && Objects .equals (tuple , that .tuple )
313
+ && Objects .equals (byteArray , that .byteArray )
314
+ && Objects .equals (uuid , that .uuid );
276
315
}
277
316
278
317
@ Override
@@ -291,18 +330,52 @@ public int hashCode() {
291
330
if (tuple != null ) {
292
331
result = result * 59 + tuple .hashCode ();
293
332
}
333
+ if (byteArray != null ) {
334
+ result = result * 59 + byteArray .hashCode ();
335
+ }
336
+ if (uuid != null ) {
337
+ result = result * 59 + uuid .hashCode ();
338
+ }
294
339
295
340
return result ;
296
341
}
297
342
343
+ public record FieldAndValue (
344
+ @ NonNull JavaField field ,
345
+ @ Nullable FieldValue value
346
+ ) {
347
+ public FieldAndValue (@ NonNull JavaField jf , @ NonNull Map <String , Object > flattenedObj ) {
348
+ this (jf , getValue (jf , flattenedObj ));
349
+ }
350
+
351
+ @ Nullable
352
+ private static FieldValue getValue (@ NonNull JavaField jf , @ NonNull Map <String , Object > flattenedObj ) {
353
+ String name = jf .getName ();
354
+ return flattenedObj .containsKey (name ) ? FieldValue .ofObj (flattenedObj .get (name ), jf ) : null ;
355
+ }
356
+
357
+ @ Nullable
358
+ public Comparable <?> toComparable () {
359
+ return value == null ? null : value .getComparable (field );
360
+ }
361
+
362
+ public Type fieldType () {
363
+ return field .getType ();
364
+ }
365
+
366
+ public String fieldPath () {
367
+ return field .getPath ();
368
+ }
369
+ }
370
+
298
371
@ Value
299
372
public static class Tuple implements Comparable <Tuple > {
300
373
@ Nullable
301
374
@ EqualsAndHashCode .Exclude
302
375
Object composite ;
303
376
304
377
@ NonNull
305
- List <JavaFieldValue > components ;
378
+ List <FieldAndValue > components ;
306
379
307
380
@ NonNull
308
381
public Type getType () {
@@ -317,13 +390,13 @@ public Object asComposite() {
317
390
}
318
391
319
392
@ NonNull
320
- public Stream <JavaFieldValue > streamComponents () {
393
+ public Stream <FieldAndValue > streamComponents () {
321
394
return components .stream ();
322
395
}
323
396
324
397
@ NonNull
325
398
public String toString () {
326
- return components .stream ().map (c -> String .valueOf (c . getValue ())).collect (joining (", " , "<" , ">" ));
399
+ return components .stream ().map (fv -> String .valueOf (fv . value ())).collect (joining (", " , "<" , ">" ));
327
400
}
328
401
329
402
@ Override
@@ -340,11 +413,11 @@ public int compareTo(@NonNull FieldValue.Tuple other) {
340
413
var thisIter = components .iterator ();
341
414
var otherIter = other .components .iterator ();
342
415
while (thisIter .hasNext ()) {
343
- JavaFieldValue thisComponent = thisIter .next ();
344
- JavaFieldValue otherComponent = otherIter .next ();
416
+ FieldAndValue thisComponent = thisIter .next ();
417
+ FieldAndValue otherComponent = otherIter .next ();
345
418
346
- Object thisValue = thisComponent .getValue ();
347
- Object otherValue = otherComponent .getValue ();
419
+ Comparable <?> thisValue = thisComponent .toComparable ();
420
+ Comparable <?> otherValue = otherComponent .toComparable ();
348
421
// sort null first
349
422
if (thisValue == null && otherValue == null ) {
350
423
continue ;
@@ -357,9 +430,9 @@ public int compareTo(@NonNull FieldValue.Tuple other) {
357
430
}
358
431
359
432
Preconditions .checkState (
360
- thisComponent .getFieldType ().equals (otherComponent .getFieldType ()),
433
+ thisComponent .fieldType ().equals (otherComponent .fieldType ()),
361
434
"Different tuple component types at [%s](%s): %s and %s" ,
362
- i , thisComponent .getFieldPath (), thisComponent .getFieldType (), otherComponent .getFieldType ()
435
+ i , thisComponent .fieldPath (), thisComponent .fieldType (), otherComponent .fieldType ()
363
436
);
364
437
365
438
@ SuppressWarnings ({"rawtypes" , "unchecked" })
0 commit comments