Casts
Casting is the process of converting a value from its current data type to another type using an explicit expression having the form
<expr> :: <type>
where <expr> is any expression and <type> is any
type that is
compatible with <expr>. When <expr> and <type> are incompatible,
structured errors result as described below.
The SQL syntax
CAST(<expr> AS <type>)
is also supported.
To cast to the value form of a type, i.e., a type value, the cast function may be used.
When a cast is successful, the return value of cast always has the target type.
If errors are encountered, then some or all of the resulting value will be embedded with structured errors and the result does not have the target type.
The target type cannot contain an error type. The error function
should instead be used to create error values.
Primitive Values
Some primitive values can be cast to other primitive types, but not all possibilities are permitted and instead result in structured errors. Except for union and named types, primitive values cannot be cast to complex types.
The casting rules for primitives are as follows:
- A number may be cast to
- another number type as long as the numeric value is not outside the scope of the target type, which results in a structured error,
- type
string, - type
boolwhere zero isfalseand non-zero istrue, - type
durationwhere the number is presumed to be nanoseconds, - type
timewhere the number is presumed to be nanoseconds since epoch, or - a union or named type.
- A string may be cast to any other primitive type as long as
the string corresponds to a valid SuperSQL primitive literal. Time strings
in particular may represent typical timestamp formats. When cast to the
bytestype, the result is the byte encoding of the UTF-8 string. A string may also be cast to a union or named type. To parse a literal string that is in the SUP or JSON format without having to specify the target type, use theparse_supfunction. - A bool may be cast to
- a number type where
falseis zero andtrueis1, - type
string, or - a union or named type.
- a number type where
- A time value may be cast to
- a number type where the numeric value is nanoseconds since epoch
- type
string, or - a union or named type.
A null value of type null may be cast to any type.
Note
A future version of this documentation will provide detailed documentation for acceptable date/time strings.
Complex Values
When a complex value has multiple levels of nesting, casting is applied recursively into the value hierarchy. For example, cast is recursively applied to each element in an array of records, then recursively applied to each of those records.
If there is a mismatch between the type of the input value and target type then structured errors appear within the portion of a nested value that is not castable.
The casting rules for complex values are as follows:
- A record may be cast to
- a record type where any fields not present in the target type are omitted, any fields not present in the input value while present in the target type are set to null, and the value of each input field present in both the input and target are recursively cast to the target’s type of that field,
- a string type where the string is the input value serialized in the SUP format, or
- a union or named type.
- An array may be cast to
- an array type where the elements of the input value are recursively cast to the element type of the target array type,
- a set type where the elements of the input value are recursively cast to the element type of the target set type and any duplicate values are automatically removed, or
- a string type where the string is the input value serialized in the SUP format, or
- a union or named type.
- A set may be cast to
- a set type where the elements of the input value are recursively cast to the element type of the target set type,
- an array type where the elements of the input value are recursively cast to the element type of the target array type, or
- a string type where the string is the input value serialized in the SUP format, or
- a union or named type.
- A map may be cast to
- a map type where the keys and values of the input value are recursively cast to the key and value type of the target map type, or
- a string type where the string is the input value serialized in the SUP format, or
- a union or named type.
- An enum may be cast to
- an enum type where the target type includes the symbol of the value being cast, or
- a string type where the string is the input value serialized in the SUP format, or
- a union or named type.
Union Types
When casting a value to a union type, the member type of the union is selected to find a best fit of the available types. If no fit exists, a structured error is returned.
If the input type is present in the member types, then the best fit is that type.
Otherwise, the best fit is determined from the input type as follows:
Note
A future version of this documentation will provide detailed documentation for best-fit selection algorithm.
Named Types
When casting to a named type, the cast is carried out using its underlying type then the named type is reattached to the result.
Errors
Casts attempted between a value and a type that are not defined result in a structured error of the form of:
{message:"cannot cast to <target>", on:<val>}
When errors appear within a complex value, the returned value may not be wrapped in a structured error and the problematic portions of the cast can be debugged by inspecting the result for precisely where the errors arose.
For example, this function call
cast({a:"1",b:2}, <{a:int64,b:ip}>)
returns
{a:1,b:error({message:"cannot cast to ip",on:2})}
That is the value for a was successfully cast from string "1“ to integer 1 but
the value for b could not be cast to an IP address so a structured error is
instead embedded as the value for b.
Examples
Cast various primitives to type ip
# spq
values this::ip
# input
"10.0.0.1"
1
"foo"
# expected output
10.0.0.1
error({message:"cannot cast to ip",on:1})
error({message:"cannot cast to ip",on:"foo"})
Cast array of strings to array of IPs
# spq
values this::[ip]
# input
["10.0.0.1","10.0.0.2"]
# expected output
[10.0.0.1,10.0.0.2]
Cast a record to a different record type
# spq
values this::{b:string}
# input
{a:1,b:2}
{a:3}
{b:4}
# expected output
{b:"2"}
{b:null::string}
{b:"4"}
Multiple syntax options for casting
# spq
values
80::(port=uint16),
CAST(80 AS (port=uint16)),
cast(80::uint16, 'port'),
cast(cast(80, <uint16>), 'port')
# input
# expected output
80::(port=uint16)
80::(port=uint16)
80::(port=uint16)
80::(port=uint16)
Casting time strings is fairly flexible
# spq
values this::time
# input
"May 8, 2009 5:57:51 PM"
"oct 7, 1970"
# expected output
2009-05-08T17:57:51Z
1970-10-07T00:00:00Z
Cast to a declared type
# spq
type port = uint16
values this::port
# input
80
8080
# expected output
80::(port=uint16)
8080::(port=uint16)
Cast nested records
# spq
values this::{ts:time,r:{x:float64,y:float64}}
# input
{ts:"1/1/2022",r:{x:"1",y:"2"}}
{ts:"1/2/2022",r:{x:3,y:4}}
# expected output
{ts:2022-01-01T00:00:00Z,r:{x:1.,y:2.}}
{ts:2022-01-02T00:00:00Z,r:{x:3.,y:4.}}