Add format_bytes and format_time functions

patch by Stefan Miklosovic; reviewed by Jordan West for CASSANDRA-19546

Co-authored-by: Cheng Wang <chengw@netflix.com>
This commit is contained in:
Stefan Miklosovic 2024-04-26 13:40:42 +02:00
parent 06f0965a08
commit ba4a0d4fcb
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
10 changed files with 1356 additions and 25 deletions

View File

@ -1,4 +1,5 @@
5.1
* Add format_bytes and format_time functions (CASSANDRA-19546)
* Fix error when trying to assign a tuple to target type not being a tuple (CASSANDRA-20237)
* Fail CREATE TABLE LIKE statement if UDTs in target keyspace do not exist or they have different structure from ones in source keyspace (CASSANDRA-19966)
* Support octet_length and length functions (CASSANDRA-20102)

View File

@ -113,6 +113,7 @@ New features
the data and check the length themselves or enable UDF and register UDFs to check the length of columns.
CASSANDRA-20102 adds a subset of the SQL99 (binary) string functions: "octet_length" defined on all types
and "length" defined on UTF8 strings. See CASSANDRA-20102 for more information.
- New functions `format_bytes` and `format_time` were added. See CASSANDRA-19546.
Upgrading
---------

View File

@ -336,6 +336,189 @@ A number of functions allow to obtain the similarity score between vectors of fl
include::cassandra:partial$vector-search/vector_functions.adoc[]
[[human-helper-functions]]
==== Human helper functions
For user's convenience, there are currently two functions which are converting values to more human-friendly represetations.
[cols=",,",options="header",]
|===
| Function name | Input type | Description
| `format_bytes` |`int`, `tinyint`, `smallint`, `bigint`, `varint`, `ascii`, `text` | Converts values in bytes to a more human-friendly representation.
| `format_time` |`int`, `tinyint`, `smallint`, `bigint`, `varint`, `ascii`, `text` | Converts values in milliseconds to a more human-friendly representation.
|===
===== format_bytes
This function looks at values in a column as if it was in bytes, and it will convert it to whatever a user pleases. Supported units are: `B`, `KiB`, `MiB` and `GiB`. The result will be rounded to two decimal places.
Supported column types on which this function is possible to be applied:
`INT`, `TINYINT`, `SMALLINT`, `BIGINT`, `VARINT`, `ASCII`, `TEXT`.
For `ASCII` and `TEXT` types, text of such column has to be a non-negative number.
Return values can be max of `Long.MAX_VALUE`, If the conversion produces overflown value, `Long.MAX_VALUE` will be returned.
[NOTE]
====
The actual return value of the `Long.MAX_VALUE` will be 9223372036854776000 due to the limitations of floating-point precision.
====
There are three ways how to call this function.
Let's have this table:
[source,cql]
----
cqlsh> select * from ks.tb;
id | val
----+----------------
5 | 60000
1 | 1234234
2 | 12342341234234
4 | 60001
7 | null
6 | 43
3 | 123423
----
with schema
[source,cql]
----
CREATE TABLE ks.tb (
id int PRIMARY KEY,
val bigint
)
----
Imagine that we wanted to look at `val` values as if they were in mebibytes. We would like to have more human-friendly output in order to not visually divide the values by 1024 in order to get them in respective bigger units. The following function call may take just a column itself as an argument, and it will
automatically convert it.
[NOTE]
====
The default source unit for `format_bytes` function is _bytes_, (`B`).
====
[source,cql]
----
cqlsh> select format_bytes(val) from ks.tb;
system.format_bytes(val)
--------------------------
58.59 KiB
1.18 MiB
11494.7 GiB
58.59 KiB
null
43 B
120.53 KiB
----
The second way to call `format_bytes` functions is to specify into what size unit we would like to see all
values to be converted to. For example, we want all size to be represented in mebibytes, hence we do:
[source,cql]
----
cqlsh> select format_bytes(val, 'MiB') from ks.tb;
system.format_bytes(val, 'MiB')
----------------------------------
0.06 MiB
1.18 MiB
11770573.84 MiB
0.06 MiB
null
0 MiB
0.12 MiB
----
Lastly, we can specify a source unit and a target unit. A source unit tells what unit that column is logically of, the target unit tells what unit we want these values to be converted to. For example,
if we know that our column is logically in kibibytes and we want them to be converted into mebibytes, we would do:
[source,cql]
----
cqlsh> select format_bytes(val, 'Kib', 'MiB') from ks.tb;
system.format_bytes(val, 'KiB', 'MiB')
----------------------------------------
58.59 MiB
1205.31 MiB
12053067611.56 MiB
58.59 MiB
null
0.04 MiB
120.53 MiB
----
===== format_time
Similarly to `format_bytes`, we can do transformations on duration-like columns.
Supported units are: `d`, `h`, `m`, `s`, `ms`, `us`, `µs`, `ns`.
Supported column types on which this function is possible to be applied:
`INT`, `TINYINT`, `SMALLINT`, `BIGINT`, `VARINT`, `ASCII`, `TEXT`. For `ASCII` and `TEXT` types, text of such column has to be a non-negative number.
Return values can be max of `Double.MAX_VALUE`, If the conversion produces overflown value, `Double.MAX_VALUE` will be returned.
[NOTE]
====
The default source unit for `format_time` function is _milliseconds_, (`ms`).
====
[source,cql]
----
cqlsh> select format_time(val) from ks.tb;
system.format_time(val)
-------------------------
1 m
20.57 m
142851.17 d
1 m
null
43 ms
2.06 m
----
We may specify what unit we want that value to be converted to, give the column's values are in millseconds:
[source,cql]
----
cqlsh> select format_time(val, 'm') from ks.tb;
system.format_time(val, 'm')
------------------------------
1 m
20.57 m
205705687.24 m
1 m
null
0 m
2.06 m
----
Lastly, we can specify both source and target values:
[source,cql]
----
cqlsh> select format_time(val, 's', 'h') from ks.tb;
system.format_time(val, 's', 'h')
-----------------------------------
16.67 h
342.84 h
3428428120.62 h
16.67 h
null
0.01 h
34.28 h
----
[[user-defined-scalar-functions]]
=== User-defined functions

View File

@ -480,25 +480,21 @@ public abstract class DataStorageSpec
{
BYTES("B")
{
public long toBytes(long d)
{
return d;
}
public long toBytes(long d) { return d; }
public long toKibibytes(long d)
{
return (d / 1024L);
}
public long toKibibytes(long d) { return (d / 1024L); }
public long toMebibytes(long d)
{
return (d / (1024L * 1024));
}
public long toMebibytes(long d) { return (d / (1024L * 1024)); }
public long toGibibytes(long d)
{
return (d / (1024L * 1024 * 1024));
}
public long toGibibytes(long d) { return (d / (1024L * 1024 * 1024)); }
public double toBytesDouble(long d) { return (double) d; }
public double toKibibytesDouble(long d) { return d / 1024.0; }
public double toMebibytesDouble(long d) { return d / (1024.0 * 1024); }
public double toGibibytesDouble(long d) { return d / (1024.0 * 1024 * 1024); }
public long convert(long source, DataStorageUnit sourceUnit)
{
@ -527,6 +523,14 @@ public abstract class DataStorageSpec
return (d / (1024L * 1024));
}
public double toBytesDouble(long d) { return (double) toBytes(d); }
public double toKibibytesDouble(long d) { return (double) d; }
public double toMebibytesDouble(long d) { return d / 1024.0; }
public double toGibibytesDouble(long d) { return d / (1024.0 * 1024); }
public long convert(long source, DataStorageUnit sourceUnit)
{
return sourceUnit.toKibibytes(source);
@ -544,16 +548,21 @@ public abstract class DataStorageSpec
return x(d, 1024L, (MAX / 1024L));
}
public long toMebibytes(long d)
{
return d;
}
public long toMebibytes(long d) { return d; }
public long toGibibytes(long d)
{
return (d / 1024L);
}
public double toBytesDouble(long d) { return (double) toBytes(d); }
public double toKibibytesDouble(long d) { return (double) toKibibytes(d); }
public double toMebibytesDouble(long d) { return (double) d; }
public double toGibibytesDouble(long d) { return d / 1024.0; }
public long convert(long source, DataStorageUnit sourceUnit)
{
return sourceUnit.toMebibytes(source);
@ -576,10 +585,15 @@ public abstract class DataStorageSpec
return x(d, 1024L, (MAX / 1024L));
}
public long toGibibytes(long d)
{
return d;
}
public long toGibibytes(long d) { return d; }
public double toBytesDouble(long d) { return (double) toBytes(d); }
public double toKibibytesDouble(long d) { return (double) toKibibytes(d); }
public double toMebibytesDouble(long d) { return (double) toMebibytes(d); }
public double toGibibytesDouble(long d) { return (double) d; }
public long convert(long source, DataStorageUnit sourceUnit)
{
@ -628,26 +642,51 @@ public abstract class DataStorageSpec
this.symbol = symbol;
}
public String getSymbol()
{
return symbol;
}
public long toBytes(long d)
{
throw new AbstractMethodError();
}
public double toBytesDouble(long d)
{
throw new AbstractMethodError();
}
public long toKibibytes(long d)
{
throw new AbstractMethodError();
}
public double toKibibytesDouble(long d)
{
throw new AbstractMethodError();
}
public long toMebibytes(long d)
{
throw new AbstractMethodError();
}
public double toMebibytesDouble(long d)
{
throw new AbstractMethodError();
}
public long toGibibytes(long d)
{
throw new AbstractMethodError();
}
public double toGibibytesDouble(long d)
{
throw new AbstractMethodError();
}
public long convert(long source, DataStorageUnit sourceUnit)
{
throw new AbstractMethodError();

View File

@ -141,7 +141,7 @@ public abstract class DurationSpec
* @param symbol the time unit symbol
* @return the time unit associated to the specified symbol
*/
static TimeUnit fromSymbol(String symbol)
public static TimeUnit fromSymbol(String symbol)
{
switch (toLowerCaseLocalized(symbol))
{

View File

@ -0,0 +1,447 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.functions;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.config.DataStorageSpec.DataStorageUnit;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.Pair;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.BYTES;
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.GIBIBYTES;
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.KIBIBYTES;
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.MEBIBYTES;
import static org.apache.cassandra.cql3.CQL3Type.Native.ASCII;
import static org.apache.cassandra.cql3.CQL3Type.Native.BIGINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.INT;
import static org.apache.cassandra.cql3.CQL3Type.Native.SMALLINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.TEXT;
import static org.apache.cassandra.cql3.CQL3Type.Native.TINYINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.VARINT;
import static org.apache.cassandra.cql3.functions.FunctionParameter.fixed;
import static org.apache.cassandra.cql3.functions.FunctionParameter.optional;
public class FormatFcts
{
private static final DecimalFormat decimalFormat;
static
{
decimalFormat = new DecimalFormat("#.##");
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
}
/**
* Formats a double value to a string with two decimal places.
* <p>
* Supported column types on which this function is possible to be applied:
* <pre>DOUBLE</pre>
*/
public static String format(double value)
{
return decimalFormat.format(value);
}
public static void addFunctionsTo(NativeFunctions functions)
{
functions.add(FormatBytesFct.factory());
functions.add(FormatTimeFct.factory());
}
private static long validateAndGetValue(Arguments arguments)
{
if (arguments.containsNulls())
throw new InvalidRequestException("none of the arguments may be null");
long value = getValue(arguments);
if (value < 0)
throw new InvalidRequestException("value must be non-negative");
return value;
}
private static long getValue(Arguments arguments)
{
Optional<String> maybeString = getAsString(arguments, 0);
if (maybeString.isPresent())
{
try
{
return Long.parseLong(maybeString.get());
}
catch (Exception ex)
{
throw new InvalidRequestException("unable to convert string '" + maybeString.get() + "' to a value of type long");
}
}
else
{
return arguments.getAsLong(0);
}
}
private static Optional<String> getAsString(Arguments arguments, int i)
{
try
{
return Optional.ofNullable(arguments.get(i));
}
catch (Exception ex)
{
return Optional.empty();
}
}
private FormatFcts()
{
}
/**
* Converts numeric value in a column to a value of specified unit.
* <p>
* If the function call contains just one argument - value to convert - then it will be
* looked at as the value is of unit 'ms' and it will be converted to a value of a unit which is closest to it.
* The result will be rounded to two decimal places.
* E.g. If a value is (20 * 1000 + 250) then the unit will be in seconds and converted value will be 20.25.
* <p>
* If the function call contains two arguments - value to convert and a unit - then it will be looked at
* as the unit of such value is 'ms' and it will be converted into the value of the second (unit) argument.
* <p>
* If the function call contains three arguments - value to covert and source and target unit - then the value
* will be considered of a unit of the second argument, and it will be converted
* into a value of the third (unit) argument.
* <p>
* Examples:
* <pre>
* format_time(val)
* format_time(val, 'm') = format_time(val, 'ms', 'm')
* format_time(val, 's', 'm')
* format_time(val, 's', 'h')
* format_time(val, 's', 'd')
* format_time(val, 's') = format_time(val, 'ms', 's')
* format_time(val, 'h') = format_time(val, 'ms', 'h')
* </pre>
* <p>
* It is possible to convert values of a bigger unit to values of a smaller unit, e.g. this is possible:
*
* <pre>
* format_time(val, 'm', 's')
* </pre>
* <p>
* Values can be max of Double.MAX_VALUE, If the conversion produces overflown value, Double.MAX_VALUE will be returned.
* <p>
* Supported units are: d, h, m, s, ms, us, µs, ns
* <p>
* Supported column types on which this function is possible to be applied:
* <pre>INT, TINYINT, SMALLINT, BIGINT, VARINT, ASCII, TEXT</pre>
* For ASCII and TEXT types, text of such column has to be a non-negative number.
* <p>
* The conversion of negative values is not supported.
*/
public static class FormatTimeFct extends NativeScalarFunction
{
private static final String FUNCTION_NAME = "format_time";
private static final String[] UNITS = { "d", "h", "m", "s" };
private static final long[] CONVERSION_FACTORS = { 86400000, 3600000, 60000, 1000 }; // Milliseconds in a day, hour, minute, second
private FormatTimeFct(AbstractType<?>... argsTypes)
{
super(FUNCTION_NAME, UTF8Type.instance, argsTypes);
}
@Override
public ByteBuffer execute(Arguments arguments) throws InvalidRequestException
{
if (arguments.get(0) == null)
return null;
long value = validateAndGetValue(arguments);
if (arguments.size() == 1)
{
Pair<Double, String> convertedValue = convertValue(value);
return UTF8Type.instance.fromString(format(convertedValue.left) + ' ' + convertedValue.right);
}
TimeUnit sourceUnit;
TimeUnit targetUnit;
String targetUnitAsString;
if (arguments.size() == 2)
{
sourceUnit = MILLISECONDS;
targetUnitAsString = arguments.get(1);
}
else
{
sourceUnit = validateUnit(arguments.get(1));
targetUnitAsString = arguments.get(2);
}
targetUnit = validateUnit(targetUnitAsString);
double convertedValue = convertValue(value, sourceUnit, targetUnit);
return UTF8Type.instance.fromString(format(convertedValue) + ' ' + targetUnitAsString);
}
private TimeUnit validateUnit(String unitAsString)
{
try
{
return DurationSpec.fromSymbol(unitAsString);
}
catch (Exception ex)
{
throw new InvalidRequestException(ex.getMessage());
}
}
private Pair<Double, String> convertValue(long valueToConvert)
{
for (int i = 0; i < CONVERSION_FACTORS.length; i++)
{
if (valueToConvert >= CONVERSION_FACTORS[i])
{
double convertedValue = (double) valueToConvert / CONVERSION_FACTORS[i];
return Pair.create(convertedValue, UNITS[i]);
}
}
return Pair.create((double) valueToConvert, "ms");
}
private Double convertValue(long valueToConvert, TimeUnit sourceUnit, TimeUnit targetUnit)
{
try
{
double conversionFactor = getConversionFactor(sourceUnit, targetUnit);
return valueToConvert * conversionFactor;
}
catch (ArithmeticException ex)
{
return Double.MAX_VALUE;
}
}
private static double getConversionFactor(TimeUnit sourceUnit, TimeUnit targetUnit)
{
// Define conversion factors between units
double nanosPerSourceUnit = getNanosPerUnit(sourceUnit);
double nanosPerTargetUnit = getNanosPerUnit(targetUnit);
// Calculate the conversion factor
return nanosPerSourceUnit / nanosPerTargetUnit;
}
private static double getNanosPerUnit(TimeUnit unit)
{
switch (unit)
{
case NANOSECONDS:
return 1.0;
case MICROSECONDS:
return 1_000.0;
case MILLISECONDS:
return 1_000_000.0;
case SECONDS:
return 1_000_000_000.0;
case MINUTES:
return 60.0 * 1_000_000_000.0;
case HOURS:
return 3600.0 * 1_000_000_000.0;
case DAYS:
return 86400.0 * 1_000_000_000.0;
default:
throw new IllegalArgumentException("Unsupported time unit: " + unit);
}
}
public static FunctionFactory factory()
{
return new FunctionFactory(FUNCTION_NAME,
fixed(INT, TINYINT, SMALLINT, BIGINT, VARINT, ASCII, TEXT),
optional(fixed(ASCII)),
optional(fixed(ASCII)))
{
@Override
protected NativeFunction doGetOrCreateFunction(List<AbstractType<?>> argTypes, AbstractType<?> receiverType)
{
if (argTypes.isEmpty() || argTypes.size() > 3)
throw invalidNumberOfArgumentsException();
return new FormatTimeFct(argTypes.toArray(new AbstractType<?>[0]));
}
};
}
}
/**
* Converts numeric value in a column to a size value of specified unit.
* <p>
* If the function call contains just one argument - value to convert - then it will be
* looked at as the value is of unit 'B' and it will be converted to a value of a unit which is closest to it.
* The result will be rounded to two decimal places.
* E.g. If a value is (100 * 1024 + 150) then the unit will be in KiB and converted value will be 100.15.
* <p>
* If the function call contains two arguments - value to convert and a unit - then it will be looked at
* as the unit of such value is 'B' and it will be converted into the value of the second (unit) argument.
* <p>
* If the function call contains three arguments - value to covert and source and target unit - then the value
* will be considered of a unit of the second argument, and it will be converted
* into a value of the third (unit) argument.
* <p>
* Examples:
* <pre>
* format_bytes(val) = format_bytes(val, 'B', 'MiB')
* format_bytes(val, 'B', 'MiB')
* format_bytes(val, 'B', 'GiB')
* format_bytes(val, 'KiB', 'GiB')
* format_bytes(val, 'MiB') = format_bytes(val, 'B', 'MiB')
* format_bytes(val, 'GiB') = format_bytes(val, 'B', 'GiB')
* </pre>
* <p>
* It is possible to convert values of a bigger unit to values of a smaller unit, e.g. this is possible:
*
* <pre>
* format_bytes(val, 'GiB', 'B')
* </pre>
* <p>
* Values can be max of Long.MAX_VALUE, If the conversion produces overflown value, Long.MAX_VALUE will be returned.
* Note that the actual return value will be 9223372036854776000 due to the limitations of double precision.
* <p>
* Supported units are: B, KiB, MiB, GiB
* <p>
* Supported column types on which this function is possible to be applied:
* <pre>INT, TINYINT, SMALLINT, BIGINT, VARINT, ASCII, TEXT</pre>
* For ASCII and TEXT types, text of such column has to be a non-negative number.
* <p>
* <p>
* The conversion of negative values is not supported.
*/
public static class FormatBytesFct extends NativeScalarFunction
{
private static final String FUNCTION_NAME = "format_bytes";
private FormatBytesFct(AbstractType<?>... argsTypes)
{
super(FUNCTION_NAME, UTF8Type.instance, argsTypes);
}
@Override
public ByteBuffer execute(Arguments arguments) throws InvalidRequestException
{
if (arguments.get(0) == null)
return null;
long value = validateAndGetValue(arguments);
DataStorageUnit sourceUnit;
DataStorageUnit targetUnit;
if (arguments.size() == 1)
{
sourceUnit = BYTES;
if (value > FileUtils.ONE_GIB)
targetUnit = GIBIBYTES;
else if (value > FileUtils.ONE_MIB)
targetUnit = MEBIBYTES;
else if (value > FileUtils.ONE_KIB)
targetUnit = KIBIBYTES;
else
targetUnit = BYTES;
}
else if (arguments.size() == 2)
{
sourceUnit = BYTES;
targetUnit = validateUnit(arguments.get(1));
}
else
{
sourceUnit = validateUnit(arguments.get(1));
targetUnit = validateUnit(arguments.get(2));
}
double convertedValue = convertValue(value, sourceUnit, targetUnit);
String convertedValueAsString = format(convertedValue);
return UTF8Type.instance.fromString(convertedValueAsString + ' ' + targetUnit.getSymbol());
}
private double convertValue(long valueToConvert, DataStorageUnit sourceUnit, DataStorageUnit targetUnit)
{
switch (targetUnit)
{
case BYTES:
return sourceUnit.toBytesDouble(valueToConvert);
case KIBIBYTES:
return sourceUnit.toKibibytesDouble(valueToConvert);
case MEBIBYTES:
return sourceUnit.toMebibytesDouble(valueToConvert);
case GIBIBYTES:
return sourceUnit.toGibibytesDouble(valueToConvert);
default:
throw new InvalidRequestException("unsupported target unit " + targetUnit);
}
}
private DataStorageUnit validateUnit(String unitAsString)
{
try
{
return DataStorageUnit.fromSymbol(unitAsString);
}
catch (Exception ex)
{
throw new InvalidRequestException(ex.getMessage());
}
}
public static FunctionFactory factory()
{
return new FunctionFactory(FUNCTION_NAME,
fixed(INT, TINYINT, SMALLINT, BIGINT, VARINT, ASCII, TEXT),
optional(fixed(ASCII)),
optional(fixed(ASCII)))
{
@Override
protected NativeFunction doGetOrCreateFunction(List<AbstractType<?>> argTypes, AbstractType<?> receiverType)
{
if (argTypes.isEmpty() || argTypes.size() > 3)
throw invalidNumberOfArgumentsException();
return new FormatBytesFct(argTypes.toArray(new AbstractType<?>[0]));
}
};
}
}
}

View File

@ -49,6 +49,7 @@ public class NativeFunctions
MaskingFcts.addFunctionsTo(this);
VectorFcts.addFunctionsTo(this);
ClusterMetadataFcts.addFunctionsTo(this);
FormatFcts.addFunctionsTo(this);
}
};

View File

@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.functions;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Ignore;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.CQLTester;
@Ignore
public abstract class AbstractFormatFctTest extends CQLTester
{
protected void createTable(List<CQL3Type.Native> columnTypes, Object[][] rows)
{
String[][] columns = new String[columnTypes.size() + 1][2];
columns[0][0] = "pk";
columns[0][1] = "int";
for (int i = 1; i <= columnTypes.size(); i++)
{
columns[i][0] = "col" + i;
columns[i][1] = columnTypes.get(i - 1).name().toLowerCase();
}
createTable(columns, rows);
}
protected void createDefaultTable(Object[][] rows)
{
createTable(new String[][]{ { "pk", "int" }, { "col1", "int" }, { "col2", "int" } }, rows);
}
protected void createTable(String[][] columns, Object[][] rows)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++)
{
sb.append(columns[i][0]);
sb.append(' ');
sb.append(columns[i][1]);
if (i == 0)
sb.append(" primary key");
if (i + 1 != columns.length)
sb.append(", ");
}
String columnsDefinition = sb.toString();
createTable(KEYSPACE, "CREATE TABLE %s (" + columnsDefinition + ')');
String cols = Arrays.stream(columns).map(s -> s[0]).collect(Collectors.joining(", "));
for (Object[] row : rows)
{
String vals = Arrays.stream(row).map(v -> {
if (v == null)
return "null";
return v.toString();
}).collect(Collectors.joining(", "));
execute("INSERT INTO %s (" + cols + ") values (" + vals + ')');
}
}
}

View File

@ -0,0 +1,307 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.functions;
import org.junit.Test;
import org.apache.cassandra.exceptions.InvalidRequestException;
import static java.util.List.of;
import static org.apache.cassandra.cql3.CQL3Type.Native.ASCII;
import static org.apache.cassandra.cql3.CQL3Type.Native.BIGINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.INT;
import static org.apache.cassandra.cql3.CQL3Type.Native.SMALLINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.TEXT;
import static org.apache.cassandra.cql3.CQL3Type.Native.TINYINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.VARINT;
import static org.apache.cassandra.cql3.functions.FormatFcts.format;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.quicktheories.QuickTheory.qt;
import static org.quicktheories.generators.SourceDSL.integers;
public class FormatBytesFctTest extends AbstractFormatFctTest
{
@Test
public void testOneValueArgumentExact()
{
createTable(of(INT), new Object[][]{ { 1, 1073741825 },
{ 2, 1073741823 },
{ 3, 0 } }); // 0 B
assertRows(execute("select format_bytes(col1) from %s where pk = 1"), row("1 GiB"));
assertRows(execute("select format_bytes(col1) from %s where pk = 2"), row("1024 MiB"));
assertRows(execute("select format_bytes(col1) from %s where pk = 3"), row("0 B"));
}
@Test
public void testOneValueArgumentDecimalRoundup()
{
createTable(of(INT), new Object[][]{ { 1, 1563401650 },
{ 2, 1072441589 },
{ 3, 102775 },
{ 4, 102 } });
assertRows(execute("select format_bytes(col1) from %s where pk = 1"), row("1.46 GiB")); // 1.4560
assertRows(execute("select format_bytes(col1) from %s where pk = 2"), row("1022.76 MiB")); // 1022.7599
assertRows(execute("select format_bytes(col1) from %s where pk = 3"), row("100.37 KiB")); // 100.3662
assertRows(execute("select format_bytes(col1) from %s where pk = 4"), row("102 B"));
}
@Test
public void testOneValueArgumentDecimalRoundDown()
{
createTable(of(INT), new Object[][]{ { 1, 1557999386 },
{ 2, 1072433201 },
{ 3, 102769 },
{ 4, 102 } });
assertRows(execute("select format_bytes(col1) from %s where pk = 1"), row("1.45 GiB")); // 1.451
assertRows(execute("select format_bytes(col1) from %s where pk = 2"), row("1022.75 MiB")); // 1022.752
assertRows(execute("select format_bytes(col1) from %s where pk = 3"), row("100.36 KiB")); // 100.3613
assertRows(execute("select format_bytes(col1) from %s where pk = 4"), row("102 B"));
}
@Test
public void testValueAndUnitArgumentsExact()
{
createTable(of(INT), new Object[][]{ { 1, 1073741825 },
{ 2, 0 } });
assertRows(execute("select format_bytes(col1, 'B') from %s where pk = 1"), row("1073741825 B"));
assertRows(execute("select format_bytes(col1, 'KiB') from %s where pk = 1"), row("1048576 KiB"));
assertRows(execute("select format_bytes(col1, 'MiB') from %s where pk = 1"), row("1024 MiB"));
assertRows(execute("select format_bytes(col1, 'GiB') from %s where pk = 1"), row("1 GiB"));
assertRows(execute("select format_bytes(col1, 'B') from %s where pk = 2"), row("0 B"));
assertRows(execute("select format_bytes(col1, 'KiB') from %s where pk = 2"), row("0 KiB"));
assertRows(execute("select format_bytes(col1, 'MiB') from %s where pk = 2"), row("0 MiB"));
assertRows(execute("select format_bytes(col1, 'GiB') from %s where pk = 2"), row("0 GiB"));
}
@Test
public void testValueAndUnitArgumentsDecimal()
{
createTable(of(INT), new Object[][]{ { 1, 1563401650 },
{ 2, 1557999336 } });
assertRows(execute("select format_bytes(col1, 'B') from %s where pk = 1"), row("1563401650 B"));
assertRows(execute("select format_bytes(col1, 'KiB') from %s where pk = 1"), row("1526759.42 KiB"));
assertRows(execute("select format_bytes(col1, 'MiB') from %s where pk = 1"), row("1490.98 MiB"));
assertRows(execute("select format_bytes(col1, 'GiB') from %s where pk = 1"), row("1.46 GiB"));
assertRows(execute("select format_bytes(col1, 'B') from %s where pk = 2"), row("1557999336 B"));
assertRows(execute("select format_bytes(col1, 'KiB') from %s where pk = 2"), row("1521483.73 KiB"));
assertRows(execute("select format_bytes(col1, 'MiB') from %s where pk = 2"), row("1485.82 MiB"));
assertRows(execute("select format_bytes(col1, 'GiB') from %s where pk = 2"), row("1.45 GiB"));
}
@Test
public void testValueWithSourceAndTargetArgumentExact()
{
createTable(of(INT), new Object[][]{ { 1, 1073741825 },
{ 2, 1 },
{ 3, 0 } });
assertRows(execute("select format_bytes(col1, 'B', 'B') from %s where pk = 1"), row("1073741825 B"));
assertRows(execute("select format_bytes(col1, 'B', 'KiB') from %s where pk = 1"), row("1048576 KiB"));
assertRows(execute("select format_bytes(col1, 'B', 'MiB') from %s where pk = 1"), row("1024 MiB"));
assertRows(execute("select format_bytes(col1, 'B', 'GiB') from %s where pk = 1"), row("1 GiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'GiB') from %s where pk = 2"), row("1 GiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'MiB') from %s where pk = 2"), row("1024 MiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'KiB') from %s where pk = 2"), row("1048576 KiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'B') from %s where pk = 2"), row("1073741824 B"));
assertRows(execute("select format_bytes(col1, 'GiB', 'GiB') from %s where pk = 3"), row("0 GiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'MiB') from %s where pk = 3"), row("0 MiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'KiB') from %s where pk = 3"), row("0 KiB"));
assertRows(execute("select format_bytes(col1, 'GiB', 'B') from %s where pk = 3"), row("0 B"));
}
@Test
public void testValueWithSourceAndTargetArgumentDecimal()
{
createTable(of(INT), new Object[][]{ { 1, 1563401650 },
{ 2, 1557999336 },});
assertRows(execute("select format_bytes(col1, 'B', 'B') from %s where pk = 1"), row("1563401650 B"));
assertRows(execute("select format_bytes(col1, 'B', 'KiB') from %s where pk = 1"), row("1526759.42 KiB"));
assertRows(execute("select format_bytes(col1, 'B', 'MiB') from %s where pk = 1"), row("1490.98 MiB"));
assertRows(execute("select format_bytes(col1, 'B', 'GiB') from %s where pk = 1"), row("1.46 GiB"));
assertRows(execute("select format_bytes(col1, 'B', 'B') from %s where pk = 2"), row("1557999336 B"));
assertRows(execute("select format_bytes(col1, 'B', 'KiB') from %s where pk = 2"), row("1521483.73 KiB"));
assertRows(execute("select format_bytes(col1, 'B', 'MiB') from %s where pk = 2"), row("1485.82 MiB"));
assertRows(execute("select format_bytes(col1, 'B', 'GiB') from %s where pk = 2"), row("1.45 GiB"));
}
@Test
public void testFuzzNumberGenerators()
{
createTable("CREATE TABLE %s (pk int primary key, col1 int)");
qt().withExamples(1024).forAll(integers().allPositive()).checkAssert(
(randInt) -> {
execute("INSERT INTO %s (pk, col1) VALUES (?, ?)", 1, randInt);
assertRows(execute("select format_bytes(col1, 'MiB') from %s where pk = 1"), row(format(randInt / 1024.0 / 1024.0) + " MiB"));
assertRows(execute("select format_bytes(col1, 'KiB', 'GiB') from %s where pk = 1"), row(format(randInt / 1024.0 / 1024.0) + " GiB"));
assertRows(execute("select format_bytes(col1, 'B', 'GiB') from %s where pk = 1"), row(format(randInt / 1024.0 / 1024.0 / 1024.0 ) + " GiB"));
});
}
@Test
public void testOverflow()
{
createTable(of(BIGINT, INT, SMALLINT, TINYINT),
new Object[][]{ { 1,
1073741825L * 1024 + 1,
Integer.MAX_VALUE - 1,
Short.MAX_VALUE - 1,
Byte.MAX_VALUE - 1 },
{ 2,
1073741825L * 1024 + 1,
Integer.MAX_VALUE,
Short.MAX_VALUE,
Byte.MAX_VALUE } });
// this will stop at Long.MAX_VALUE
assertRows(execute("select format_bytes(col1, 'GiB', 'B') from %s where pk = 1"), row("9223372036854776000 B"));
assertRows(execute("select format_bytes(col2, 'GiB', 'B') from %s where pk = 1"), row("2305843007066210300 B"));
assertRows(execute("select format_bytes(col3, 'GiB', 'B') from %s where pk = 1"), row("35182224605184 B"));
assertRows(execute("select format_bytes(col4, 'GiB', 'B') from %s where pk = 1"), row("135291469824 B"));
assertRows(execute("select format_bytes(col2, 'GiB', 'B') from %s where pk = 2"), row("2305843008139952130 B"));
assertRows(execute("select format_bytes(col3, 'GiB', 'B') from %s where pk = 2"), row("35183298347008 B"));
assertRows(execute("select format_bytes(col4, 'GiB', 'B') from %s where pk = 2"), row("136365211648 B"));
}
@Test
public void testAllSupportedColumnTypes()
{
createTable(of(INT, TINYINT, SMALLINT, BIGINT, VARINT, ASCII, TEXT),
new Object[][]{ { 1,
Integer.MAX_VALUE,
Byte.MAX_VALUE,
Short.MAX_VALUE,
Long.MAX_VALUE,
Integer.MAX_VALUE,
'\'' + Integer.valueOf(Integer.MAX_VALUE).toString() + '\'',
'\'' + Integer.valueOf(Integer.MAX_VALUE).toString() + '\'',
} });
assertRows(execute("select format_bytes(col1) from %s where pk = 1"), row("2 GiB"));
assertRows(execute("select format_bytes(col2) from %s where pk = 1"), row("127 B"));
assertRows(execute("select format_bytes(col3) from %s where pk = 1"), row("32 KiB"));
assertRows(execute("select format_bytes(col4) from %s where pk = 1"), row("8589934592 GiB"));
assertRows(execute("select format_bytes(col5) from %s where pk = 1"), row("2 GiB"));
assertRows(execute("select format_bytes(col6) from %s where pk = 1"), row("2 GiB"));
assertRows(execute("select format_bytes(col7) from %s where pk = 1"), row("2 GiB"));
}
@Test
public void testNegativeValueIsInvalid()
{
createDefaultTable(new Object[][]{ { "1", "-1", "-2" } });
assertThatThrownBy(() -> execute("select format_bytes(col1) from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("value must be non-negative");
}
@Test
public void testUnparsableTextIsInvalid()
{
createTable(of(TEXT), new Object[][]{ { 1, "'abc'" }, { 2, "'-1'" } });
assertThatThrownBy(() -> execute("select format_bytes(col1) from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("unable to convert string 'abc' to a value of type long");
assertThatThrownBy(() -> execute("select format_bytes(col1) from %s where pk = 2"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("value must be non-negative");
}
@Test
public void testInvalidUnits()
{
createDefaultTable(new Object[][]{ { "1", "1", "2" } });
for (String functionCall : new String[] {
"format_bytes(col1, 'abc')",
"format_bytes(col1, 'B', 'abc')",
"format_bytes(col1, 'abc', 'B')",
"format_bytes(col1, 'abc', 'abc')"
})
{
assertThatThrownBy(() -> execute("select " + functionCall + " from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("Unsupported data storage unit: abc. Supported units are: B, KiB, MiB, GiB");
}
}
@Test
public void testInvalidArgumentsSize()
{
createDefaultTable(new Object[][]{ { "1", "1", "2" } });
// Test arguemnt size = 0
assertThatThrownBy(() -> execute("select format_bytes() from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("Invalid number of arguments for function system.format_bytes([int|tinyint|smallint|bigint|varint|ascii|text], [ascii], [ascii])");
// Test argument size > 3
assertThatThrownBy(() -> execute("select format_bytes(col1, 'B', 'KiB', 'GiB') from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("Invalid number of arguments for function system.format_bytes([int|tinyint|smallint|bigint|varint|ascii|text], [ascii], [ascii])");
}
@Test
public void testHandlingNullValues()
{
createTable(of(TEXT, ASCII, INT),
new Object[][]{ { 1, null, null, null } });
assertRows(execute("select format_bytes(col1), format_bytes(col2), format_bytes(col3) from %s where pk = 1"),
row(null, null, null));
assertRows(execute("select format_bytes(col1, 'B') from %s where pk = 1"), row((Object) null));
assertRows(execute("select format_bytes(col1, 'B', 'KiB') from %s where pk = 1"), row((Object) null));
}
@Test
public void testHandlingNullArguments()
{
createTable(of(TEXT, ASCII, INT),
new Object[][]{ { 1, null, null, null },
{ 2, "'1'", "'2'", 3 } });
assertRows(execute("select format_bytes(col1, null) from %s where pk = 1"), row((Object) null));
for (String functionCall : new String[] {
"format_bytes(col3, null)",
"format_bytes(col3, null, null)",
"format_bytes(col3, null, 'KiB')",
"format_bytes(col3, 'KiB', null)"
})
{
assertThatThrownBy(() -> execute("select " + functionCall + " from %s where pk = 2"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("none of the arguments may be null");
}
}
@Test
public void testSizeSmallerThan1KibiByte()
{
createDefaultTable(new Object[][]{ { "1", "900", "2000" } });
assertRows(execute("select format_bytes(col1) from %s where pk = 1"), row("900 B"));
}
}

View File

@ -0,0 +1,268 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.functions;
import org.junit.Test;
import org.apache.cassandra.exceptions.InvalidRequestException;
import static java.util.List.of;
import static org.apache.cassandra.cql3.CQL3Type.Native.ASCII;
import static org.apache.cassandra.cql3.CQL3Type.Native.BIGINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.INT;
import static org.apache.cassandra.cql3.CQL3Type.Native.SMALLINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.TEXT;
import static org.apache.cassandra.cql3.CQL3Type.Native.TINYINT;
import static org.apache.cassandra.cql3.CQL3Type.Native.VARINT;
import static org.apache.cassandra.cql3.functions.FormatFcts.format;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.quicktheories.WithQuickTheories;
public class FormatTimeFctTest extends AbstractFormatFctTest implements WithQuickTheories
{
@Test
public void testOneValueArgument()
{
createTable(of(INT), new Object[][]{ { 1, 7200001 }, // 2h + 1ms
{ 2, 7199999 }, // 2h - 1ms
{ 3, 0 } }); // 0 B
assertRows(execute("select format_time(col1) from %s where pk = 1"), row("2 h"));
assertRows(execute("select format_time(col1) from %s where pk = 2"), row("2 h"));
assertRows(execute("select format_time(col1) from %s where pk = 3"), row("0 ms"));
}
@Test
public void testOneValueArgumentDecimal()
{
createTable(of(INT), new Object[][]{ { 1, 9000000 }, // 2.5h
{ 2, 7704000 }, // 2.14h
{ 3, 7848000 } }); // 2.18h
assertRows(execute("select format_time(col1) from %s where pk = 1"), row("2.5 h"));
assertRows(execute("select format_time(col1) from %s where pk = 2"), row("2.14 h"));
assertRows(execute("select format_time(col1) from %s where pk = 3"), row("2.18 h"));
}
@Test
public void testValueAndUnitArguments()
{
createTable(of(INT), new Object[][]{ { 1, 1073741826 },
{ 2, 0 }});
assertRows(execute("select format_time(col1, 's') from %s where pk = 1"), row("1073741.83 s"));
assertRows(execute("select format_time(col1, 'm') from %s where pk = 1"), row("17895.7 m"));
assertRows(execute("select format_time(col1, 'h') from %s where pk = 1"), row("298.26 h"));
assertRows(execute("select format_time(col1, 'd') from %s where pk = 1"), row("12.43 d"));
assertRows(execute("select format_time(col1, 's') from %s where pk = 2"), row("0 s"));
assertRows(execute("select format_time(col1, 'm') from %s where pk = 2"), row("0 m"));
assertRows(execute("select format_time(col1, 'h') from %s where pk = 2"), row("0 h"));
assertRows(execute("select format_time(col1, 'd') from %s where pk = 2"), row("0 d"));
}
@Test
public void testValueWithSourceAndTargetArgument()
{
createTable(of(INT), new Object[][]{ { 1, 1073741826 },
{ 2, 1 },
{ 3, 0 } });
assertRows(execute("select format_time(col1, 'ns', 'us') from %s where pk = 1"), row("1073741.83 us"));
assertRows(execute("select format_time(col1, 'ns', 'ms') from %s where pk = 1"), row("1073.74 ms"));
assertRows(execute("select format_time(col1, 'ns', 's') from %s where pk = 1"), row("1.07 s"));
assertRows(execute("select format_time(col1, 'ns', 'm') from %s where pk = 1"), row("0.02 m"));
assertRows(execute("select format_time(col1, 'us', 'ns') from %s where pk = 1"), row("1073741826000 ns"));
assertRows(execute("select format_time(col1, 'us', 'ms') from %s where pk = 1"), row("1073741.83 ms"));
assertRows(execute("select format_time(col1, 'us', 's') from %s where pk = 1"), row("1073.74 s"));
assertRows(execute("select format_time(col1, 'us', 'm') from %s where pk = 1"), row("17.9 m"));
assertRows(execute("select format_time(col1, 'us', 'h') from %s where pk = 1"), row("0.3 h"));
assertRows(execute("select format_time(col1, 'us', 'd') from %s where pk = 1"), row("0.01 d"));
assertRows(execute("select format_time(col1, 'ms', 'ms') from %s where pk = 1"), row("1073741826 ms"));
assertRows(execute("select format_time(col1, 'ms', 's') from %s where pk = 1"), row("1073741.83 s"));
assertRows(execute("select format_time(col1, 'ms', 'm') from %s where pk = 1"), row("17895.7 m"));
assertRows(execute("select format_time(col1, 'ms', 'h') from %s where pk = 1"), row("298.26 h"));
assertRows(execute("select format_time(col1, 'ms', 'd') from %s where pk = 1"), row("12.43 d"));
assertRows(execute("select format_time(col1, 'd', 'd') from %s where pk = 2"), row("1 d"));
assertRows(execute("select format_time(col1, 'd', 'h') from %s where pk = 2"), row("24 h"));
assertRows(execute("select format_time(col1, 'd', 'm') from %s where pk = 2"), row("1440 m"));
assertRows(execute("select format_time(col1, 'd', 's') from %s where pk = 2"), row("86400 s"));
assertRows(execute("select format_time(col1, 'd', 'd') from %s where pk = 3"), row("0 d"));
assertRows(execute("select format_time(col1, 'd', 'h') from %s where pk = 3"), row("0 h"));
assertRows(execute("select format_time(col1, 'd', 'm') from %s where pk = 3"), row("0 m"));
assertRows(execute("select format_time(col1, 'd', 's') from %s where pk = 3"), row("0 s"));
assertRows(execute("select format_time(col1, 'd', 'ms') from %s where pk = 3"), row("0 ms"));
assertRows(execute("select format_time(col1, 'd', 'us') from %s where pk = 3"), row("0 us"));
}
@Test
public void testNoOverflow()
{
createTable(of(BIGINT, INT, SMALLINT, TINYINT),
new Object[][]{ { 1,
Long.MAX_VALUE - 1,
Integer.MAX_VALUE - 1,
Short.MAX_VALUE - 1,
Byte.MAX_VALUE - 1 },
{ 2,
Long.MAX_VALUE,
Integer.MAX_VALUE,
Short.MAX_VALUE,
Byte.MAX_VALUE } });
// Won't overlfow because the value is one less than the Double.MAX_VALUE
assertRows(execute("select format_time(col1, 'd', 'ns') from %s where pk = 1"), row("796899343984252600000000000000000 ns"));
assertRows(execute("select format_time(col2, 'd', 'ns') from %s where pk = 1"), row("185542587014400000000000 ns"));
assertRows(execute("select format_time(col3, 'd', 'ns') from %s where pk = 1"), row("2830982400000000000 ns"));
assertRows(execute("select format_time(col4, 'd', 'ns') from %s where pk = 1"), row("10886400000000000 ns"));
assertRows(execute("select format_time(col1, 'd', 'ns') from %s where pk = 2"), row("796899343984252600000000000000000 ns"));
assertRows(execute("select format_time(col2, 'd', 'ns') from %s where pk = 2"), row("185542587100800000000000 ns"));
assertRows(execute("select format_time(col3, 'd', 'ns') from %s where pk = 2"), row("2831068800000000000 ns"));
assertRows(execute("select format_time(col4, 'd', 'ns') from %s where pk = 2"), row("10972800000000000 ns"));
}
@Test
public void testAllSupportedColumnTypes()
{
createTable(of(INT, TINYINT, SMALLINT, BIGINT, VARINT, ASCII, TEXT),
new Object[][]{ { 1,
Integer.MAX_VALUE,
Byte.MAX_VALUE,
Short.MAX_VALUE,
Long.MAX_VALUE,
Integer.MAX_VALUE,
'\'' + Integer.valueOf(Integer.MAX_VALUE).toString() + '\'',
'\'' + Integer.valueOf(Integer.MAX_VALUE).toString() + '\'',
} });
assertRows(execute("select format_time(col1) from %s where pk = 1"), row("24.86 d"));
assertRows(execute("select format_time(col2) from %s where pk = 1"), row("127 ms"));
assertRows(execute("select format_time(col3) from %s where pk = 1"), row("32.77 s"));
assertRows(execute("select format_time(col4) from %s where pk = 1"), row("106751991167.3 d"));
assertRows(execute("select format_time(col5) from %s where pk = 1"), row("24.86 d"));
assertRows(execute("select format_time(col6) from %s where pk = 1"), row("24.86 d"));
assertRows(execute("select format_time(col7) from %s where pk = 1"), row("24.86 d"));
}
@Test
public void testNegativeValueIsInvalid()
{
createDefaultTable(new Object[][]{ { "1", "-1", "-2" } });
assertThatThrownBy(() -> execute("select format_time(col1) from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("value must be non-negative");
}
@Test
public void testUnparsableTextIsInvalid()
{
createTable(of(TEXT), new Object[][]{ { 1, "'abc'" }, { 2, "'-1'" } });
assertThatThrownBy(() -> execute("select format_time(col1) from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("unable to convert string 'abc' to a value of type long");
assertThatThrownBy(() -> execute("select format_time(col1) from %s where pk = 2"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("value must be non-negative");
}
@Test
public void testInvalidUnits()
{
createDefaultTable(new Object[][]{ { "1", "1", "2" } });
for (String functionCall : new String[] {
"format_time(col1, 'abc')",
"format_time(col1, 'd', 'abc')",
"format_time(col1, 'abc', 'd')",
"format_time(col1, 'abc', 'abc')"
})
{
assertThatThrownBy(() -> execute("select " + functionCall + " from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("Unsupported time unit: abc. Supported units are: ns, us, ms, s, m, h, d");
}
}
@Test
public void testInvalidArgumentsSize()
{
createDefaultTable(new Object[][]{ { "1", "1", "2" } });
// test arguemnt size = 0
assertThatThrownBy(() -> execute("select format_time() from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("Invalid number of arguments for function system.format_time([int|tinyint|smallint|bigint|varint|ascii|text], [ascii], [ascii])");
// Test argument size > 3
assertThatThrownBy(() -> execute("select format_time(col1, 'ms', 's', 'h') from %s where pk = 1"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("Invalid number of arguments for function system.format_time([int|tinyint|smallint|bigint|varint|ascii|text], [ascii], [ascii])");
}
@Test
public void testHandlingNullValues()
{
createTable(of(TEXT, ASCII, INT),
new Object[][]{ { 1, null, null, null } });
assertRows(execute("select format_time(col1), format_time(col2), format_time(col3) from %s where pk = 1"),
row(null, null, null));
assertRows(execute("select format_time(col1, 's') from %s where pk = 1"), row((Object) null));
assertRows(execute("select format_time(col1, 's', 'd') from %s where pk = 1"), row((Object) null));
}
@Test
public void testHandlingNullArguments()
{
createTable(of(TEXT, ASCII, INT),
new Object[][]{ { 1, null, null, null },
{ 2, "'1'", "'2'", 3 } });
assertRows(execute("select format_time(col1, null) from %s where pk = 1"), row((Object) null));
for (String functionCall : new String[] {
"format_time(col3, null)",
"format_time(col3, null, null)",
"format_time(col3, null, 'd')",
"format_time(col3, 'd', null)"
})
{
assertThatThrownBy(() -> execute("select " + functionCall + " from %s where pk = 2"))
.isInstanceOf(InvalidRequestException.class)
.hasMessageContaining("none of the arguments may be null");
}
}
@Test
public void testFuzzRandomGenerators()
{
createTable("CREATE TABLE %s (pk int primary key, col1 int)");
qt().withExamples(1024).forAll(integers().allPositive()).checkAssert(
(randInt) -> {
execute("INSERT INTO %s (pk, col1) VALUES (?, ?)", 1, randInt);
assertRows(execute("select format_time(col1, 's', 'm') from %s where pk = 1"), row(format((double) randInt * (1 / 60.0)) + " m"));
assertRows(execute("select format_time(col1, 's', 'h') from %s where pk = 1"), row(format((double) randInt * (1 / 3600.0)) + " h"));
assertRows(execute("select format_time(col1, 's', 'd') from %s where pk = 1"), row(format((double) randInt * (1 / 86400.0)) + " d"));
assertRows(execute("select format_time(col1, 'ms', 'm') from %s where pk = 1"), row(format((double) randInt * (1 / (60 * 1000.0))) + " m"));
assertRows(execute("select format_time(col1, 'ms', 'h') from %s where pk = 1"), row(format((double) randInt * (1 / (3600 * 1000.0))) + " h"));
assertRows(execute("select format_time(col1, 'ms', 'd') from %s where pk = 1"), row(format((double) randInt * (1 / (86400 * 1000.0))) + " d"));
});
}
}