7070import com .scalar .db .api .TransactionState ;
7171import com .scalar .db .common .CoreError ;
7272import com .scalar .db .exception .storage .ExecutionException ;
73+ import com .scalar .db .io .BigIntColumn ;
74+ import com .scalar .db .io .BlobColumn ;
75+ import com .scalar .db .io .BooleanColumn ;
76+ import com .scalar .db .io .Column ;
77+ import com .scalar .db .io .DataType ;
78+ import com .scalar .db .io .DoubleColumn ;
79+ import com .scalar .db .io .FloatColumn ;
80+ import com .scalar .db .io .IntColumn ;
7381import com .scalar .db .io .Key ;
82+ import com .scalar .db .io .TextColumn ;
7483import com .scalar .dl .client .exception .ClientException ;
7584import com .scalar .dl .client .service .GenericContractClientService ;
7685import com .scalar .dl .ledger .error .LedgerError ;
7988import com .scalar .dl .ledger .service .StatusCode ;
8089import com .scalar .dl .ledger .util .JacksonSerDe ;
8190import java .io .IOException ;
91+ import java .time .Instant ;
92+ import java .time .LocalDate ;
8293import java .time .LocalDateTime ;
94+ import java .time .LocalTime ;
95+ import java .time .ZoneId ;
8396import java .util .HashSet ;
8497import java .util .List ;
8598import java .util .Map ;
@@ -99,9 +112,6 @@ public class GenericContractObjectAndCollectionEndToEndTest
99112 private static final String ASSET_ID = "id" ;
100113 private static final String ASSET_AGE = "age" ;
101114 private static final String ASSET_OUTPUT = "output" ;
102- private static final String DATA_TYPE_INT = "INT" ;
103- private static final String DATA_TYPE_TIMESTAMP = "TIMESTAMP" ;
104- private static final String DATA_TYPE_TEXT = "TEXT" ;
105115
106116 private static final String CONTRACT_OBJECT_GET =
107117 com .scalar .dl .genericcontracts .object .Constants .CONTRACT_GET ;
@@ -124,8 +134,29 @@ public class GenericContractObjectAndCollectionEndToEndTest
124134 private static final String SOME_COLUMN_NAME_2 = "version" ;
125135 private static final String SOME_COLUMN_NAME_3 = "status" ;
126136 private static final String SOME_COLUMN_NAME_4 = "registered_at" ;
137+ private static final String SOME_COLUMN_NAME_BOOLEAN = "column_boolean" ;
138+ private static final String SOME_COLUMN_NAME_BIGINT = "column_bigint" ;
139+ private static final String SOME_COLUMN_NAME_FLOAT = "column_float" ;
140+ private static final String SOME_COLUMN_NAME_DOUBLE = "column_double" ;
141+ private static final String SOME_COLUMN_NAME_TEXT = "column_text" ;
142+ private static final String SOME_COLUMN_NAME_BLOB = "column_blob" ;
143+ private static final String SOME_COLUMN_NAME_DATE = "column_date" ;
144+ private static final String SOME_COLUMN_NAME_TIME = "column_time" ;
145+ private static final String SOME_COLUMN_NAME_TIMESTAMPTZ = "column_timestamptz" ;
146+ private static final String SOME_DATE_TEXT = "2021-02-03" ;
147+ private static final String SOME_TIME_TEXT = "05:45:00" ;
127148 private static final String SOME_TIMESTAMP_TEXT = "2021-02-03 05:45:00" ;
149+ private static final String SOME_TIMESTAMPTZ_TEXT = "2021-02-03 05:45:00.000 Z" ;
150+ private static final boolean SOME_BOOLEAN_VALUE = false ;
151+ private static final long SOME_BIGINT_VALUE = BigIntColumn .MAX_VALUE ;
152+ private static final float SOME_FLOAT_VALUE = Float .MAX_VALUE ;
153+ private static final double SOME_DOUBLE_VALUE = Double .MAX_VALUE ;
154+ private static final byte [] SOME_BLOB_VALUE = {1 , 2 , 3 , 4 , 5 };
155+ private static final LocalDate SOME_DATE_VALUE = LocalDate .of (2021 , 2 , 3 );
156+ private static final LocalTime SOME_TIME_VALUE = LocalTime .of (5 , 45 );
128157 private static final LocalDateTime SOME_TIMESTAMP_VALUE = LocalDateTime .of (2021 , 2 , 3 , 5 , 45 );
158+ private static final Instant SOME_TIMESTAMPTZ_VALUE =
159+ SOME_TIMESTAMP_VALUE .atZone (ZoneId .of ("UTC" )).toInstant ();
129160 private static final String SOME_COLLECTION_ID = "set" ;
130161 private static final ArrayNode SOME_DEFAULT_OBJECT_IDS =
131162 mapper .createArrayNode ().add ("object1" ).add ("object2" ).add ("object3" ).add ("object4" );
@@ -202,41 +233,96 @@ private void prepareCollection() {
202233 prepareCollection (clientService );
203234 }
204235
205- private JsonNode createColumn (String name , int value ) {
236+ private JsonNode createColumn (Column <?> column ) {
237+ ObjectNode jsonColumn =
238+ mapper
239+ .createObjectNode ()
240+ .put (COLUMN_NAME , column .getName ())
241+ .put (DATA_TYPE , column .getDataType ().name ());
242+
243+ switch (column .getDataType ()) {
244+ case BOOLEAN :
245+ jsonColumn .put (VALUE , column .getBooleanValue ());
246+ break ;
247+ case INT :
248+ jsonColumn .put (VALUE , column .getIntValue ());
249+ break ;
250+ case BIGINT :
251+ jsonColumn .put (VALUE , column .getBigIntValue ());
252+ break ;
253+ case FLOAT :
254+ jsonColumn .put (VALUE , column .getFloatValue ());
255+ break ;
256+ case DOUBLE :
257+ jsonColumn .put (VALUE , column .getDoubleValue ());
258+ break ;
259+ case TEXT :
260+ jsonColumn .put (VALUE , column .getTextValue ());
261+ break ;
262+ case BLOB :
263+ jsonColumn .put (VALUE , column .getBlobValueAsBytes ());
264+ break ;
265+ default :
266+ throw new IllegalArgumentException ("Invalid data type: " + column .getDataType ());
267+ }
268+
269+ return jsonColumn ;
270+ }
271+
272+ private JsonNode createColumn (String columnName , DataType dataType , String value ) {
206273 return mapper
207274 .createObjectNode ()
208- .put (COLUMN_NAME , name )
275+ .put (COLUMN_NAME , columnName )
209276 .put (VALUE , value )
210- .put (DATA_TYPE , DATA_TYPE_INT );
277+ .put (DATA_TYPE , dataType . name () );
211278 }
212279
213- private JsonNode createColumn (String name , String value ) {
280+ private JsonNode createNullColumn (String columnName , DataType dataType ) {
214281 return mapper
215282 .createObjectNode ()
216- .put (COLUMN_NAME , name )
217- .put (VALUE , value )
218- .put ( DATA_TYPE , DATA_TYPE_TEXT );
283+ .put (COLUMN_NAME , columnName )
284+ .put (DATA_TYPE , dataType . name () )
285+ .set ( VALUE , null );
219286 }
220287
221- private JsonNode createTimestampColumn ( String name , String value ) {
288+ private ArrayNode createColumns ( int status ) {
222289 return mapper
223- .createObjectNode ()
224- .put (COLUMN_NAME , name )
225- .put (VALUE , value )
226- .put (DATA_TYPE , DATA_TYPE_TIMESTAMP );
290+ .createArrayNode ()
291+ .add (createColumn (IntColumn .of (SOME_COLUMN_NAME_3 , status )))
292+ .add (createColumn (SOME_COLUMN_NAME_4 , DataType .TIMESTAMP , SOME_TIMESTAMP_TEXT ))
293+ .add (createColumn (BooleanColumn .of (SOME_COLUMN_NAME_BOOLEAN , SOME_BOOLEAN_VALUE )))
294+ .add (createColumn (BigIntColumn .of (SOME_COLUMN_NAME_BIGINT , SOME_BIGINT_VALUE )))
295+ .add (createColumn (FloatColumn .of (SOME_COLUMN_NAME_FLOAT , SOME_FLOAT_VALUE )))
296+ .add (createColumn (DoubleColumn .of (SOME_COLUMN_NAME_DOUBLE , SOME_DOUBLE_VALUE )))
297+ .add (createColumn (BlobColumn .of (SOME_COLUMN_NAME_BLOB , SOME_BLOB_VALUE )))
298+ .add (createColumn (SOME_COLUMN_NAME_DATE , DataType .DATE , SOME_DATE_TEXT ))
299+ .add (createColumn (SOME_COLUMN_NAME_TIME , DataType .TIME , SOME_TIME_TEXT ))
300+ .add (
301+ createColumn (
302+ SOME_COLUMN_NAME_TIMESTAMPTZ , DataType .TIMESTAMPTZ , SOME_TIMESTAMPTZ_TEXT ));
227303 }
228304
229- private JsonNode createFunctionArguments (
230- String objectId , String version , int status , long registeredAt ) {
305+ private ArrayNode createNullColumns () {
306+ return mapper
307+ .createArrayNode ()
308+ .add (createNullColumn (SOME_COLUMN_NAME_3 , DataType .INT ))
309+ .add (createNullColumn (SOME_COLUMN_NAME_4 , DataType .TIMESTAMP ))
310+ .add (createNullColumn (SOME_COLUMN_NAME_BOOLEAN , DataType .BOOLEAN ))
311+ .add (createNullColumn (SOME_COLUMN_NAME_BIGINT , DataType .BIGINT ))
312+ .add (createNullColumn (SOME_COLUMN_NAME_FLOAT , DataType .FLOAT ))
313+ .add (createNullColumn (SOME_COLUMN_NAME_DOUBLE , DataType .DOUBLE ))
314+ .add (createNullColumn (SOME_COLUMN_NAME_TEXT , DataType .TEXT ))
315+ .add (createNullColumn (SOME_COLUMN_NAME_BLOB , DataType .BLOB ))
316+ .add (createNullColumn (SOME_COLUMN_NAME_DATE , DataType .DATE ))
317+ .add (createNullColumn (SOME_COLUMN_NAME_TIME , DataType .TIME ))
318+ .add (createNullColumn (SOME_COLUMN_NAME_TIMESTAMPTZ , DataType .TIMESTAMPTZ ));
319+ }
320+
321+ private ObjectNode createFunctionArguments (String objectId , String version , ArrayNode columns ) {
231322 ArrayNode partitionKey =
232- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_1 , objectId ));
323+ mapper .createArrayNode ().add (createColumn (TextColumn . of ( SOME_COLUMN_NAME_1 , objectId ) ));
233324 ArrayNode clusteringKey =
234- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_2 , version ));
235- ArrayNode columns =
236- mapper
237- .createArrayNode ()
238- .add (createColumn (SOME_COLUMN_NAME_3 , status ))
239- .add (createTimestampColumn (SOME_COLUMN_NAME_4 , SOME_TIMESTAMP_TEXT ));
325+ mapper .createArrayNode ().add (createColumn (TextColumn .of (SOME_COLUMN_NAME_2 , version )));
240326
241327 ObjectNode arguments = mapper .createObjectNode ();
242328 arguments .put (NAMESPACE , getFunctionNamespace ());
@@ -248,6 +334,14 @@ private JsonNode createFunctionArguments(
248334 return arguments ;
249335 }
250336
337+ private ObjectNode createFunctionArguments (String objectId , String version , int status ) {
338+ return createFunctionArguments (objectId , version , createColumns (status ));
339+ }
340+
341+ private ObjectNode createFunctionArgumentsWithNullColumns (String objectId , String version ) {
342+ return createFunctionArguments (objectId , version , createNullColumns ());
343+ }
344+
251345 private void addObjectsToCollection (
252346 GenericContractClientService clientService , String collectionId , ArrayNode objectIds ) {
253347 JsonNode arguments =
@@ -519,9 +613,8 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
519613 .put (OBJECT_ID , SOME_OBJECT_ID )
520614 .put (HASH_VALUE , SOME_HASH_VALUE_1 )
521615 .set (METADATA , SOME_METADATA_1 );
522- JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 , 1L );
523- JsonNode functionArguments1 =
524- createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_1 , 1 , 1234567890123L );
616+ JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 );
617+ JsonNode functionArguments1 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_1 , 1 );
525618 Scan scan =
526619 Scan .newBuilder ()
527620 .namespace (getFunctionNamespace ())
@@ -544,10 +637,72 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
544637 assertThat (results .get (0 ).getText (SOME_COLUMN_NAME_2 )).isEqualTo (SOME_VERSION_ID_0 );
545638 assertThat (results .get (0 ).getInt (SOME_COLUMN_NAME_3 )).isEqualTo (0 );
546639 assertThat (results .get (0 ).getTimestamp (SOME_COLUMN_NAME_4 )).isEqualTo (SOME_TIMESTAMP_VALUE );
640+ assertThat (results .get (0 ).getBoolean (SOME_COLUMN_NAME_BOOLEAN )).isEqualTo (SOME_BOOLEAN_VALUE );
641+ assertThat (results .get (0 ).getBigInt (SOME_COLUMN_NAME_BIGINT )).isEqualTo (SOME_BIGINT_VALUE );
642+ assertThat (results .get (0 ).getFloat (SOME_COLUMN_NAME_FLOAT )).isEqualTo (SOME_FLOAT_VALUE );
643+ assertThat (results .get (0 ).getDouble (SOME_COLUMN_NAME_DOUBLE )).isEqualTo (SOME_DOUBLE_VALUE );
644+ assertThat (results .get (0 ).getBlobAsBytes (SOME_COLUMN_NAME_BLOB )).isEqualTo (SOME_BLOB_VALUE );
645+ assertThat (results .get (0 ).getDate (SOME_COLUMN_NAME_DATE )).isEqualTo (SOME_DATE_VALUE );
646+ assertThat (results .get (0 ).getTime (SOME_COLUMN_NAME_TIME )).isEqualTo (SOME_TIME_VALUE );
647+ assertThat (results .get (0 ).getTimestampTZ (SOME_COLUMN_NAME_TIMESTAMPTZ ))
648+ .isEqualTo (SOME_TIMESTAMPTZ_VALUE );
547649 assertThat (results .get (1 ).getText (SOME_COLUMN_NAME_1 )).isEqualTo (SOME_OBJECT_ID );
548650 assertThat (results .get (1 ).getText (SOME_COLUMN_NAME_2 )).isEqualTo (SOME_VERSION_ID_1 );
549651 assertThat (results .get (1 ).getInt (SOME_COLUMN_NAME_3 )).isEqualTo (1 );
550652 assertThat (results .get (1 ).getTimestamp (SOME_COLUMN_NAME_4 )).isEqualTo (SOME_TIMESTAMP_VALUE );
653+ assertThat (results .get (1 ).getBoolean (SOME_COLUMN_NAME_BOOLEAN )).isEqualTo (SOME_BOOLEAN_VALUE );
654+ assertThat (results .get (1 ).getBigInt (SOME_COLUMN_NAME_BIGINT )).isEqualTo (SOME_BIGINT_VALUE );
655+ assertThat (results .get (1 ).getFloat (SOME_COLUMN_NAME_FLOAT )).isEqualTo (SOME_FLOAT_VALUE );
656+ assertThat (results .get (1 ).getDouble (SOME_COLUMN_NAME_DOUBLE )).isEqualTo (SOME_DOUBLE_VALUE );
657+ assertThat (results .get (1 ).getBlobAsBytes (SOME_COLUMN_NAME_BLOB )).isEqualTo (SOME_BLOB_VALUE );
658+ assertThat (results .get (1 ).getDate (SOME_COLUMN_NAME_DATE )).isEqualTo (SOME_DATE_VALUE );
659+ assertThat (results .get (1 ).getTime (SOME_COLUMN_NAME_TIME )).isEqualTo (SOME_TIME_VALUE );
660+ assertThat (results .get (1 ).getTimestampTZ (SOME_COLUMN_NAME_TIMESTAMPTZ ))
661+ .isEqualTo (SOME_TIMESTAMPTZ_VALUE );
662+ } catch (IOException e ) {
663+ throw new RuntimeException (e );
664+ }
665+ }
666+
667+ @ Test
668+ public void putObject_FunctionArgumentsWithNullColumnsGiven_ShouldPutRecordToFunctionTable ()
669+ throws ExecutionException {
670+ // Arrange
671+ JsonNode contractArguments =
672+ mapper
673+ .createObjectNode ()
674+ .put (OBJECT_ID , SOME_OBJECT_ID )
675+ .put (HASH_VALUE , SOME_HASH_VALUE_0 )
676+ .set (METADATA , SOME_METADATA_0 );
677+ JsonNode functionArguments =
678+ createFunctionArgumentsWithNullColumns (SOME_OBJECT_ID , SOME_VERSION_ID_0 );
679+ Scan scan =
680+ Scan .newBuilder ()
681+ .namespace (getFunctionNamespace ())
682+ .table (getFunctionTable ())
683+ .partitionKey (Key .ofText (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID ))
684+ .build ();
685+
686+ // Act
687+ clientService .executeContract (CONTRACT_PUT , contractArguments , FUNCTION_PUT , functionArguments );
688+
689+ // Assert
690+ try (Scanner scanner = storage .scan (scan )) {
691+ List <Result > results = scanner .all ();
692+ assertThat (results ).hasSize (1 );
693+ assertThat (results .get (0 ).getText (SOME_COLUMN_NAME_1 )).isEqualTo (SOME_OBJECT_ID );
694+ assertThat (results .get (0 ).getText (SOME_COLUMN_NAME_2 )).isEqualTo (SOME_VERSION_ID_0 );
695+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_3 )).isTrue ();
696+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_4 )).isTrue ();
697+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_BOOLEAN )).isTrue ();
698+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_BIGINT )).isTrue ();
699+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_FLOAT )).isTrue ();
700+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_DOUBLE )).isTrue ();
701+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_TEXT )).isTrue ();
702+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_BLOB )).isTrue ();
703+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_DATE )).isTrue ();
704+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_TIME )).isTrue ();
705+ assertThat (results .get (0 ).isNull (SOME_COLUMN_NAME_TIMESTAMPTZ )).isTrue ();
551706 } catch (IOException e ) {
552707 throw new RuntimeException (e );
553708 }
@@ -570,7 +725,9 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
570725 .put (TABLE , "foo" )
571726 .set (
572727 PARTITION_KEY ,
573- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID )));
728+ mapper
729+ .createArrayNode ()
730+ .add (createColumn (TextColumn .of (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID ))));
574731
575732 // Act Assert
576733 assertThatThrownBy (
@@ -602,8 +759,8 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
602759 .put (OBJECT_ID , SOME_OBJECT_ID )
603760 .put (HASH_VALUE , SOME_HASH_VALUE_1 )
604761 .set (METADATA , SOME_METADATA_1 );
605- JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 , 1L );
606- JsonNode functionArguments1 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 1 , 1L );
762+ JsonNode functionArguments0 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 0 );
763+ JsonNode functionArguments1 = createFunctionArguments (SOME_OBJECT_ID , SOME_VERSION_ID_0 , 1 );
607764 Put put =
608765 Put .newBuilder ()
609766 .namespace (getFunctionNamespace ())
@@ -648,16 +805,20 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable()
648805 .put (TABLE , getFunctionTable ());
649806 functionArguments .set (
650807 PARTITION_KEY ,
651- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID )));
808+ mapper
809+ .createArrayNode ()
810+ .add (createColumn (TextColumn .of (SOME_COLUMN_NAME_1 , SOME_OBJECT_ID ))));
652811 functionArguments .set (
653812 CLUSTERING_KEY ,
654- mapper .createArrayNode ().add (createColumn (SOME_COLUMN_NAME_2 , SOME_VERSION_ID_0 )));
813+ mapper
814+ .createArrayNode ()
815+ .add (createColumn (TextColumn .of (SOME_COLUMN_NAME_2 , SOME_VERSION_ID_0 ))));
655816 functionArguments .set (
656817 COLUMNS ,
657818 mapper
658819 .createArrayNode ()
659- .add (createColumn (SOME_COLUMN_NAME_3 , 0 ))
660- .add (createTimestampColumn (SOME_COLUMN_NAME_4 , "2024-05-19" )));
820+ .add (createColumn (IntColumn . of ( SOME_COLUMN_NAME_3 , 0 ) ))
821+ .add (createColumn (SOME_COLUMN_NAME_4 , DataType . TIMESTAMP , "2024-05-19" )));
661822
662823 // Act Assert
663824 assertThatThrownBy (
0 commit comments