6161import com .scalar .db .api .TransactionState ;
6262import com .scalar .db .common .error .CoreError ;
6363import com .scalar .db .exception .storage .ExecutionException ;
64+ import com .scalar .db .io .BigIntColumn ;
65+ import com .scalar .db .io .BlobColumn ;
66+ import com .scalar .db .io .BooleanColumn ;
67+ import com .scalar .db .io .Column ;
68+ import com .scalar .db .io .DataType ;
69+ import com .scalar .db .io .DoubleColumn ;
70+ import com .scalar .db .io .FloatColumn ;
71+ import com .scalar .db .io .IntColumn ;
6472import com .scalar .db .io .Key ;
73+ import com .scalar .db .io .TextColumn ;
6574import com .scalar .dl .client .exception .ClientException ;
6675import com .scalar .dl .client .service .GenericContractClientService ;
6776import com .scalar .dl .genericcontracts .object .Constants ;
7180import com .scalar .dl .ledger .service .StatusCode ;
7281import com .scalar .dl .ledger .util .JacksonSerDe ;
7382import java .io .IOException ;
83+ import java .time .Instant ;
84+ import java .time .LocalDate ;
7485import java .time .LocalDateTime ;
86+ import java .time .LocalTime ;
87+ import java .time .ZoneId ;
7588import java .util .HashSet ;
7689import java .util .List ;
7790import java .util .Map ;
@@ -87,9 +100,6 @@ public class GenericContractObjectAndCollectionEndToEndTest
87100 private static final String ASSET_ID = "id" ;
88101 private static final String ASSET_AGE = "age" ;
89102 private static final String ASSET_OUTPUT = "output" ;
90- private static final String DATA_TYPE_INT = "INT" ;
91- private static final String DATA_TYPE_TIMESTAMP = "TIMESTAMP" ;
92- private static final String DATA_TYPE_TEXT = "TEXT" ;
93103
94104 private static final String PACKAGE_OBJECT = "object" ;
95105 private static final String PACKAGE_COLLECTION = "collection" ;
@@ -136,8 +146,29 @@ public class GenericContractObjectAndCollectionEndToEndTest
136146 private static final String SOME_COLUMN_NAME_2 = "version" ;
137147 private static final String SOME_COLUMN_NAME_3 = "status" ;
138148 private static final String SOME_COLUMN_NAME_4 = "registered_at" ;
149+ private static final String SOME_COLUMN_NAME_BOOLEAN = "column_boolean" ;
150+ private static final String SOME_COLUMN_NAME_BIGINT = "column_bigint" ;
151+ private static final String SOME_COLUMN_NAME_FLOAT = "column_float" ;
152+ private static final String SOME_COLUMN_NAME_DOUBLE = "column_double" ;
153+ private static final String SOME_COLUMN_NAME_TEXT = "column_text" ;
154+ private static final String SOME_COLUMN_NAME_BLOB = "column_blob" ;
155+ private static final String SOME_COLUMN_NAME_DATE = "column_date" ;
156+ private static final String SOME_COLUMN_NAME_TIME = "column_time" ;
157+ private static final String SOME_COLUMN_NAME_TIMESTAMPTZ = "column_timestamptz" ;
158+ private static final String SOME_DATE_TEXT = "2021-02-03" ;
159+ private static final String SOME_TIME_TEXT = "05:45:00" ;
139160 private static final String SOME_TIMESTAMP_TEXT = "2021-02-03 05:45:00" ;
161+ private static final String SOME_TIMESTAMPTZ_TEXT = "2021-02-03 05:45:00.000 Z" ;
162+ private static final boolean SOME_BOOLEAN_VALUE = false ;
163+ private static final long SOME_BIGINT_VALUE = BigIntColumn .MAX_VALUE ;
164+ private static final float SOME_FLOAT_VALUE = Float .MAX_VALUE ;
165+ private static final double SOME_DOUBLE_VALUE = Double .MAX_VALUE ;
166+ private static final byte [] SOME_BLOB_VALUE = {1 , 2 , 3 , 4 , 5 };
167+ private static final LocalDate SOME_DATE_VALUE = LocalDate .of (2021 , 2 , 3 );
168+ private static final LocalTime SOME_TIME_VALUE = LocalTime .of (5 , 45 );
140169 private static final LocalDateTime SOME_TIMESTAMP_VALUE = LocalDateTime .of (2021 , 2 , 3 , 5 , 45 );
170+ private static final Instant SOME_TIMESTAMPTZ_VALUE =
171+ SOME_TIMESTAMP_VALUE .atZone (ZoneId .of ("UTC" )).toInstant ();
141172 private static final String SOME_COLLECTION_ID = "set" ;
142173 private static final ArrayNode SOME_DEFAULT_OBJECT_IDS =
143174 mapper .createArrayNode ().add ("object1" ).add ("object2" ).add ("object3" ).add ("object4" );
@@ -233,41 +264,96 @@ private void prepareCollection() {
233264 prepareCollection (clientService );
234265 }
235266
236- private JsonNode createColumn (String name , int value ) {
267+ private JsonNode createColumn (Column <?> column ) {
268+ ObjectNode jsonColumn =
269+ mapper
270+ .createObjectNode ()
271+ .put (COLUMN_NAME , column .getName ())
272+ .put (DATA_TYPE , column .getDataType ().name ());
273+
274+ switch (column .getDataType ()) {
275+ case BOOLEAN :
276+ jsonColumn .put (VALUE , column .getBooleanValue ());
277+ break ;
278+ case INT :
279+ jsonColumn .put (VALUE , column .getIntValue ());
280+ break ;
281+ case BIGINT :
282+ jsonColumn .put (VALUE , column .getBigIntValue ());
283+ break ;
284+ case FLOAT :
285+ jsonColumn .put (VALUE , column .getFloatValue ());
286+ break ;
287+ case DOUBLE :
288+ jsonColumn .put (VALUE , column .getDoubleValue ());
289+ break ;
290+ case TEXT :
291+ jsonColumn .put (VALUE , column .getTextValue ());
292+ break ;
293+ case BLOB :
294+ jsonColumn .put (VALUE , column .getBlobValueAsBytes ());
295+ break ;
296+ default :
297+ throw new IllegalArgumentException ("Invalid data type: " + column .getDataType ());
298+ }
299+
300+ return jsonColumn ;
301+ }
302+
303+ private JsonNode createColumn (String columnName , DataType dataType , String value ) {
237304 return mapper
238305 .createObjectNode ()
239- .put (COLUMN_NAME , name )
306+ .put (COLUMN_NAME , columnName )
240307 .put (VALUE , value )
241- .put (DATA_TYPE , DATA_TYPE_INT );
308+ .put (DATA_TYPE , dataType . name () );
242309 }
243310
244- private JsonNode createColumn (String name , String value ) {
311+ private JsonNode createNullColumn (String columnName , DataType dataType ) {
245312 return mapper
246313 .createObjectNode ()
247- .put (COLUMN_NAME , name )
248- .put (VALUE , value )
249- .put ( DATA_TYPE , DATA_TYPE_TEXT );
314+ .put (COLUMN_NAME , columnName )
315+ .put (DATA_TYPE , dataType . name () )
316+ .set ( VALUE , null );
250317 }
251318
252- private JsonNode createTimestampColumn ( String name , String value ) {
319+ private ArrayNode createColumns ( int status ) {
253320 return mapper
254- .createObjectNode ()
255- .put (COLUMN_NAME , name )
256- .put (VALUE , value )
257- .put (DATA_TYPE , DATA_TYPE_TIMESTAMP );
321+ .createArrayNode ()
322+ .add (createColumn (IntColumn .of (SOME_COLUMN_NAME_3 , status )))
323+ .add (createColumn (SOME_COLUMN_NAME_4 , DataType .TIMESTAMP , SOME_TIMESTAMP_TEXT ))
324+ .add (createColumn (BooleanColumn .of (SOME_COLUMN_NAME_BOOLEAN , SOME_BOOLEAN_VALUE )))
325+ .add (createColumn (BigIntColumn .of (SOME_COLUMN_NAME_BIGINT , SOME_BIGINT_VALUE )))
326+ .add (createColumn (FloatColumn .of (SOME_COLUMN_NAME_FLOAT , SOME_FLOAT_VALUE )))
327+ .add (createColumn (DoubleColumn .of (SOME_COLUMN_NAME_DOUBLE , SOME_DOUBLE_VALUE )))
328+ .add (createColumn (BlobColumn .of (SOME_COLUMN_NAME_BLOB , SOME_BLOB_VALUE )))
329+ .add (createColumn (SOME_COLUMN_NAME_DATE , DataType .DATE , SOME_DATE_TEXT ))
330+ .add (createColumn (SOME_COLUMN_NAME_TIME , DataType .TIME , SOME_TIME_TEXT ))
331+ .add (
332+ createColumn (
333+ SOME_COLUMN_NAME_TIMESTAMPTZ , DataType .TIMESTAMPTZ , SOME_TIMESTAMPTZ_TEXT ));
258334 }
259335
260- private JsonNode createFunctionArguments (
261- String objectId , String version , int status , long registeredAt ) {
336+ private ArrayNode createNullColumns () {
337+ return mapper
338+ .createArrayNode ()
339+ .add (createNullColumn (SOME_COLUMN_NAME_3 , DataType .INT ))
340+ .add (createNullColumn (SOME_COLUMN_NAME_4 , DataType .TIMESTAMP ))
341+ .add (createNullColumn (SOME_COLUMN_NAME_BOOLEAN , DataType .BOOLEAN ))
342+ .add (createNullColumn (SOME_COLUMN_NAME_BIGINT , DataType .BIGINT ))
343+ .add (createNullColumn (SOME_COLUMN_NAME_FLOAT , DataType .FLOAT ))
344+ .add (createNullColumn (SOME_COLUMN_NAME_DOUBLE , DataType .DOUBLE ))
345+ .add (createNullColumn (SOME_COLUMN_NAME_TEXT , DataType .TEXT ))
346+ .add (createNullColumn (SOME_COLUMN_NAME_BLOB , DataType .BLOB ))
347+ .add (createNullColumn (SOME_COLUMN_NAME_DATE , DataType .DATE ))
348+ .add (createNullColumn (SOME_COLUMN_NAME_TIME , DataType .TIME ))
349+ .add (createNullColumn (SOME_COLUMN_NAME_TIMESTAMPTZ , DataType .TIMESTAMPTZ ));
350+ }
351+
352+ private ObjectNode createFunctionArguments (String objectId , String version , ArrayNode columns ) {
262353 ArrayNode partitionKey =
263- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_1 , objectId ));
354+ mapper .createArrayNode ().add (createColumn (TextColumn . of ( SOME_COLUMN_NAME_1 , objectId ) ));
264355 ArrayNode clusteringKey =
265- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_2 , version ));
266- ArrayNode columns =
267- mapper
268- .createArrayNode ()
269- .add (createColumn (SOME_COLUMN_NAME_3 , status ))
270- .add (createTimestampColumn (SOME_COLUMN_NAME_4 , SOME_TIMESTAMP_TEXT ));
356+ mapper .createArrayNode ().add (createColumn (TextColumn .of (SOME_COLUMN_NAME_2 , version )));
271357
272358 ObjectNode arguments = mapper .createObjectNode ();
273359 arguments .put (NAMESPACE , getFunctionNamespace ());
@@ -279,6 +365,14 @@ private JsonNode createFunctionArguments(
279365 return arguments ;
280366 }
281367
368+ private ObjectNode createFunctionArguments (String objectId , String version , int status ) {
369+ return createFunctionArguments (objectId , version , createColumns (status ));
370+ }
371+
372+ private ObjectNode createFunctionArgumentsWithNullColumns (String objectId , String version ) {
373+ return createFunctionArguments (objectId , version , createNullColumns ());
374+ }
375+
282376 private void addObjectsToCollection (
283377 GenericContractClientService clientService , String collectionId , ArrayNode objectIds ) {
284378 JsonNode arguments =
@@ -550,9 +644,8 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
550644 .put (OBJECT_ID , SOME_OBJECT_ID )
551645 .put (HASH_VALUE , SOME_HASH_VALUE_1 )
552646 .set (METADATA , SOME_METADATA_1 );
553- JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 , 1L );
554- JsonNode functionArguments1 =
555- createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_1 , 1 , 1234567890123L );
647+ JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 );
648+ JsonNode functionArguments1 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_1 , 1 );
556649 Scan scan =
557650 Scan .newBuilder ()
558651 .namespace (getFunctionNamespace ())
@@ -575,10 +668,72 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
575668 assertThat (results .get (0 ).getText (SOME_COLUMN_NAME_2 )).isEqualTo (SOME_VERSION_ID_0 );
576669 assertThat (results .get (0 ).getInt (SOME_COLUMN_NAME_3 )).isEqualTo (0 );
577670 assertThat (results .get (0 ).getTimestamp (SOME_COLUMN_NAME_4 )).isEqualTo (SOME_TIMESTAMP_VALUE );
671+ assertThat (results .get (0 ).getBoolean (SOME_COLUMN_NAME_BOOLEAN )).isEqualTo (SOME_BOOLEAN_VALUE );
672+ assertThat (results .get (0 ).getBigInt (SOME_COLUMN_NAME_BIGINT )).isEqualTo (SOME_BIGINT_VALUE );
673+ assertThat (results .get (0 ).getFloat (SOME_COLUMN_NAME_FLOAT )).isEqualTo (SOME_FLOAT_VALUE );
674+ assertThat (results .get (0 ).getDouble (SOME_COLUMN_NAME_DOUBLE )).isEqualTo (SOME_DOUBLE_VALUE );
675+ assertThat (results .get (0 ).getBlobAsBytes (SOME_COLUMN_NAME_BLOB )).isEqualTo (SOME_BLOB_VALUE );
676+ assertThat (results .get (0 ).getDate (SOME_COLUMN_NAME_DATE )).isEqualTo (SOME_DATE_VALUE );
677+ assertThat (results .get (0 ).getTime (SOME_COLUMN_NAME_TIME )).isEqualTo (SOME_TIME_VALUE );
678+ assertThat (results .get (0 ).getTimestampTZ (SOME_COLUMN_NAME_TIMESTAMPTZ ))
679+ .isEqualTo (SOME_TIMESTAMPTZ_VALUE );
578680 assertThat (results .get (1 ).getText (SOME_COLUMN_NAME_1 )).isEqualTo (SOME_OBJECT_ID );
579681 assertThat (results .get (1 ).getText (SOME_COLUMN_NAME_2 )).isEqualTo (SOME_VERSION_ID_1 );
580682 assertThat (results .get (1 ).getInt (SOME_COLUMN_NAME_3 )).isEqualTo (1 );
581683 assertThat (results .get (1 ).getTimestamp (SOME_COLUMN_NAME_4 )).isEqualTo (SOME_TIMESTAMP_VALUE );
684+ assertThat (results .get (1 ).getBoolean (SOME_COLUMN_NAME_BOOLEAN )).isEqualTo (SOME_BOOLEAN_VALUE );
685+ assertThat (results .get (1 ).getBigInt (SOME_COLUMN_NAME_BIGINT )).isEqualTo (SOME_BIGINT_VALUE );
686+ assertThat (results .get (1 ).getFloat (SOME_COLUMN_NAME_FLOAT )).isEqualTo (SOME_FLOAT_VALUE );
687+ assertThat (results .get (1 ).getDouble (SOME_COLUMN_NAME_DOUBLE )).isEqualTo (SOME_DOUBLE_VALUE );
688+ assertThat (results .get (1 ).getBlobAsBytes (SOME_COLUMN_NAME_BLOB )).isEqualTo (SOME_BLOB_VALUE );
689+ assertThat (results .get (1 ).getDate (SOME_COLUMN_NAME_DATE )).isEqualTo (SOME_DATE_VALUE );
690+ assertThat (results .get (1 ).getTime (SOME_COLUMN_NAME_TIME )).isEqualTo (SOME_TIME_VALUE );
691+ assertThat (results .get (1 ).getTimestampTZ (SOME_COLUMN_NAME_TIMESTAMPTZ ))
692+ .isEqualTo (SOME_TIMESTAMPTZ_VALUE );
693+ } catch (IOException e ) {
694+ throw new RuntimeException (e );
695+ }
696+ }
697+
698+ @ Test
699+ public void putObject_FunctionArgumentsWithNullColumnsGiven_ShouldPutRecordToFunctionTable ()
700+ throws ExecutionException {
701+ // Arrange
702+ JsonNode contractArguments =
703+ mapper
704+ .createObjectNode ()
705+ .put (OBJECT_ID , SOME_OBJECT_ID )
706+ .put (HASH_VALUE , SOME_HASH_VALUE_0 )
707+ .set (METADATA , SOME_METADATA_0 );
708+ JsonNode functionArguments =
709+ createFunctionArgumentsWithNullColumns (SOME_OBJECT_ID , SOME_VERSION_ID_0 );
710+ Scan scan =
711+ Scan .newBuilder ()
712+ .namespace (getFunctionNamespace ())
713+ .table (getFunctionTable ())
714+ .partitionKey (Key .ofText (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID ))
715+ .build ();
716+
717+ // Act
718+ clientService .executeContract (CONTRACT_PUT , contractArguments , FUNCTION_PUT , functionArguments );
719+
720+ // Assert
721+ try (Scanner scanner = storage .scan (scan )) {
722+ List <Result > results = scanner .all ();
723+ assertThat (results ).hasSize (1 );
724+ assertThat (results .get (0 ).getText (SOME_COLUMN_NAME_1 )).isEqualTo (SOME_OBJECT_ID );
725+ assertThat (results .get (0 ).getText (SOME_COLUMN_NAME_2 )).isEqualTo (SOME_VERSION_ID_0 );
726+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_3 )).isTrue ();
727+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_4 )).isTrue ();
728+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_BOOLEAN )).isTrue ();
729+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_BIGINT )).isTrue ();
730+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_FLOAT )).isTrue ();
731+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_DOUBLE )).isTrue ();
732+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_TEXT )).isTrue ();
733+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_BLOB )).isTrue ();
734+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_DATE )).isTrue ();
735+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_TIME )).isTrue ();
736+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_TIMESTAMPTZ )).isTrue ();
582737 } catch (IOException e ) {
583738 throw new RuntimeException (e );
584739 }
@@ -601,7 +756,9 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
601756 .put (TABLE , "foo" )
602757 .set (
603758 PARTITION_KEY ,
604- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID )));
759+ mapper
760+ .createArrayNode ()
761+ .add (createColumn (TextColumn .of (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID ))));
605762
606763 // Act Assert
607764 assertThatThrownBy (
@@ -633,8 +790,8 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
633790 .put (OBJECT_ID , SOME_OBJECT_ID )
634791 .put (HASH_VALUE , SOME_HASH_VALUE_1 )
635792 .set (METADATA , SOME_METADATA_1 );
636- JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 , 1L );
637- JsonNode functionArguments1 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 1 , 1L );
793+ JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 );
794+ JsonNode functionArguments1 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 1 );
638795 Put put =
639796 Put .newBuilder ()
640797 .namespace (getFunctionNamespace ())
@@ -677,16 +834,20 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
677834 .put (TABLE , getFunctionTable ());
678835 functionArguments .set (
679836 PARTITION_KEY ,
680- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID )));
837+ mapper
838+ .createArrayNode ()
839+ .add (createColumn (TextColumn .of (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID ))));
681840 functionArguments .set (
682841 CLUSTERING_KEY ,
683- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_2 , SOME_VERSION_ID_0 )));
842+ mapper
843+ .createArrayNode ()
844+ .add (createColumn (TextColumn .of (SOME_COLUMN_NAME_2 , SOME_VERSION_ID_0 ))));
684845 functionArguments .set (
685846 COLUMNS ,
686847 mapper
687848 .createArrayNode ()
688- .add (createColumn (SOME_COLUMN_NAME_3 , 0 ))
689- .add (createTimestampColumn (SOME_COLUMN_NAME_4 , "2024-05-19" )));
849+ .add (createColumn (IntColumn . of ( SOME_COLUMN_NAME_3 , 0 ) ))
850+ .add (createColumn (SOME_COLUMN_NAME_4 , DataType . TIMESTAMP , "2024-05-19" )));
690851
691852 // Act Assert
692853 assertThatThrownBy (
0 commit comments