Skip to content

Commit f811d35

Browse files
authored
Fix to handle FLOAT and BLOB data types in PutToMutable function (#297)
1 parent ae4be12 commit f811d35

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
@@ -70,7 +70,16 @@
7070
import com.scalar.db.api.TransactionState;
7171
import com.scalar.db.common.CoreError;
7272
import 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;
7381
import com.scalar.db.io.Key;
82+
import com.scalar.db.io.TextColumn;
7483
import com.scalar.dl.client.exception.ClientException;
7584
import com.scalar.dl.client.service.GenericContractClientService;
7685
import com.scalar.dl.ledger.error.LedgerError;
@@ -79,7 +88,11 @@
7988
import com.scalar.dl.ledger.service.StatusCode;
8089
import com.scalar.dl.ledger.util.JacksonSerDe;
8190
import java.io.IOException;
91+
import java.time.Instant;
92+
import java.time.LocalDate;
8293
import java.time.LocalDateTime;
94+
import java.time.LocalTime;
95+
import java.time.ZoneId;
8396
import java.util.HashSet;
8497
import java.util.List;
8598
import 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(

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

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

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

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

0 commit comments

Comments
 (0)