// The collection and map functions (spec M4d1 ยง5).
///|
/// The number of elements, fields or entries. Duplicate fields and NULL or
/// MISSING elements count (partiql-tests cardinality.ion:122, exists.ion:74).
fn element_count(name : String, v : @value.Value) -> Int32 raise OpFailure {
match v {
List(items) => items.length()
Bag(b) => b.length()
Tuple(t) => t.length()
Map(m) => m.length()
_ => raise TypeFailure("\{name} of a value that is not a collection")
}
}
///|
/// `SIZE(c)`.
pub fn size(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("SIZE", [Collection], args, xs => {
@value.Value::from_int(element_count("SIZE", xs[0]).to_int64())
})
}
///|
/// `CARDINALITY(c)`, the same count as SIZE.
pub fn cardinality(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("CARDINALITY", [Collection], args, xs => {
@value.Value::from_int(element_count("CARDINALITY", xs[0]).to_int64())
})
}
///|
/// `EXISTS(c)`: whether `c` has an element.
pub fn exists(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("EXISTS", [Collection], args, xs => {
@value.Value::from_bool(element_count("EXISTS", xs[0]) > 0)
})
}
///|
/// `TUPLEUNION(t1, ..., tn)`: every field of every argument, in argument
/// order, duplicates kept (spec select.adoc:585-593).
pub fn tuple_union(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("TUPLEUNION", Array::make(args.length(), Param::Tuple), args, xs => {
let fields : Array[(String, @value.Value)] = []
for x in xs {
match x {
Tuple(t) => t.fields().each(f => fields.push(f))
_ => raise TypeFailure("TUPLEUNION of a value that is not a tuple")
}
}
@value.Value::from_tuple(@value.Tuple(fields))
})
}
///|
/// The value for `key` in `m`, matched by value (`eqg`, so `1` finds `1.0`),
/// or None. A key outside the map's key type family (`MapType::admits`) is a
/// `TypeFailure` (RFC 0104: compatible types are cast, others are an error).
fn map_lookup(
m : @value.MapValue,
key : @value.Value,
) -> @value.Value? raise OpFailure {
if !m.key_type.admits(key) {
raise TypeFailure("a map key of an incompatible type")
}
for entry in m.entries() {
if @value.eqg(entry.0, key) {
return Some(entry.1)
}
}
None
}
///|
/// `MAP_GET(m, k)`: `m[k]`; an absent key is a `TypeFailure` (RFC 0104).
pub fn map_get(args : Array[@value.Value]) -> @value.Value raise OpFailure {
apply("MAP_GET", [Map, Any], args, xs => {
match xs {
[Map(m), key] =>
match map_lookup(m, key) {
Some(v) => v
None => raise TypeFailure("no such key in the map")
}
_ => raise TypeFailure("MAP_GET of a value that is not a map")
}
})
}
///|
/// `MAP_CONTAINS_KEY(m, k)`.
pub fn map_contains_key(
args : Array[@value.Value],
) -> @value.Value raise OpFailure {
apply("MAP_CONTAINS_KEY", [Map, Any], args, xs => {
match xs {
[Map(m), key] => @value.Value::from_bool(map_lookup(m, key) is Some(_))
_ => raise TypeFailure("MAP_CONTAINS_KEY of a value that is not a map")
}
})
}
///|
/// The entries of a map argument, for MAP_KEYS, MAP_VALUES and MAP_ENTRIES.
fn map_bag(
name : String,
args : Array[@value.Value],
item : ((@value.Value, @value.Value)) -> @value.Value,
) -> @value.Value raise OpFailure {
apply(name, [Map], args, xs => {
match xs {
[Map(m)] =>
@value.Value::from_bag(
@value.Bag(m.entries().iter().map(item).collect()),
)
_ => raise TypeFailure("\{name} of a value that is not a map")
}
})
}
///|
/// `MAP_KEYS(m)`: a bag of the keys in insertion order.
pub fn map_keys(args : Array[@value.Value]) -> @value.Value raise OpFailure {
map_bag("MAP_KEYS", args, e => e.0)
}
///|
/// `MAP_VALUES(m)`: a bag of the values in insertion order.
pub fn map_values(args : Array[@value.Value]) -> @value.Value raise OpFailure {
map_bag("MAP_VALUES", args, e => e.1)
}
///|
/// `MAP_ENTRIES(m)`: a bag of `{k, v}` tuples (partiql-tests map.ion:956).
pub fn map_entries(args : Array[@value.Value]) -> @value.Value raise OpFailure {
map_bag("MAP_ENTRIES", args, e => {
@value.Value::from_tuple(@value.Tuple([("k", e.0), ("v", e.1)]))
})
}