Skip to content

Commit 339e164

Browse files
committed
Fix to handle FLOAT and BLOB data types in PutToMutable function (#297)
1 parent 05b3aa9 commit 339e164

File tree

4 files changed

+232
-47
lines changed

4 files changed

+232
-47
lines changed

generic-contracts/scripts/objects-table-schema.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@
1111
"object_id": "TEXT",
1212
"version": "TEXT",
1313
"status": "INT",
14-
"registered_at": "TIMESTAMP"
14+
"registered_at": "TIMESTAMP",
15+
"column_boolean": "BOOLEAN",
16+
"column_bigint": "BIGINT",
17+
"column_float": "FLOAT",
18+
"column_double": "DOUBLE",
19+
"column_text": "TEXT",
20+
"column_blob": "BLOB",
21+
"column_date": "DATE",
22+
"column_time": "TIME",
23+
"column_timestamptz": "TIMESTAMPTZ"
1524
},
1625
"compaction-strategy": "LCS"
1726
}
1827
}
19-

generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractObjectAndCollectionEndToEndTest.java

Lines changed: 195 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,16 @@
6161
import com.scalar.db.api.TransactionState;
6262
import com.scalar.db.common.error.CoreError;
6363
import 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;
6472
import com.scalar.db.io.Key;
73+
import com.scalar.db.io.TextColumn;
6574
import com.scalar.dl.client.exception.ClientException;
6675
import com.scalar.dl.client.service.GenericContractClientService;
6776
import com.scalar.dl.genericcontracts.object.Constants;
@@ -71,7 +80,11 @@
7180
import com.scalar.dl.ledger.service.StatusCode;
7281
import com.scalar.dl.ledger.util.JacksonSerDe;
7382
import java.io.IOException;
83+
import java.time.Instant;
84+
import java.time.LocalDate;
7485
import java.time.LocalDateTime;
86+
import java.time.LocalTime;
87+
import java.time.ZoneId;
7588
import java.util.HashSet;
7689
import java.util.List;
7790
import 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(

generic-contracts/src/main/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabase.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ private Column<?> getColumn(JsonNode jsonColumn) {
171171
}
172172

173173
if (dataType.equals(DataType.FLOAT)) {
174-
if (!value.isFloat()) {
174+
// The JSON deserializer does not distinguish between float and double values; all JSON
175+
// numbers with a decimal point are deserialized as double. Therefore, we check for isDouble()
176+
// here even for FLOAT columns.
177+
if (!value.isDouble()) {
175178
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT);
176179
}
177180
return FloatColumn.of(columnName, value.floatValue());
@@ -192,7 +195,9 @@ private Column<?> getColumn(JsonNode jsonColumn) {
192195
}
193196

194197
if (dataType.equals(DataType.BLOB)) {
195-
if (!value.isBinary()) {
198+
// BLOB data is expected as a Base64-encoded string due to JSON limitations. JSON cannot
199+
// represent binary data directly, so BLOBs must be provided as Base64-encoded strings.
200+
if (!value.isTextual()) {
196201
throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT);
197202
}
198203
try {

0 commit comments

Comments
 (0)