///|
fn annotate(x : @ion_core.IonValue, name : String) -> @ion_core.IonValue {
x.with_annotations(
[@ion_core.SymbolToken::SymbolToken(name), ..x.annotations()],
)
}
///|
fn int64(n : Int64) -> @ion_core.IonValue {
@ion_core.IonValue::big_int(@bigint.BigInt::from_int64(n))
}
///|
fn small(n : Int32) -> @ion_core.IonValue {
@ion_core.IonValue::int(n)
}
///|
fn seconds_decimal(t : @value.Time) -> @ion_core.IonValue {
let unit = 10N.pow(@bigint.BigInt::from_int(t.precision))
let fraction = @bigint.BigInt::from_int(t.nanos) /
10N.pow(@bigint.BigInt::from_int(9 - t.precision))
let coefficient = @bigint.BigInt::from_int(t.second) * unit + fraction
@ion_core.IonValue::decimal(
@ion_core.Decimal::Decimal(coefficient, -t.precision),
)
}
///|
fn offset_value(offset : Int32?) -> @ion_core.IonValue {
match offset {
Some(minutes) => small(minutes)
None => @ion_core.IonValue::untyped_null()
}
}
///|
fn struct_of(
fields : Array[(String, @ion_core.IonValue)],
) -> @ion_core.IonValue {
@ion_core.IonValue::from_fields(
fields.map(pair => @ion_core.Field::Field(pair.0, pair.1)),
)
}
///|
fn sign_value(negative : Bool) -> @ion_core.IonValue {
@ion_core.IonValue::string(if negative { "-" } else { "+" })
}
///|
/// Encodes a value in PartiQL-encoded Ion. A boxed Ion value is written as
/// `$ion::x`.
pub fn to_ion(v : @value.Value) -> @ion_core.IonValue {
encode(v, false)
}
///|
/// Encodes a value for Ion data (the Ion-preserving mode): a boxed Ion
/// value, at any depth, is written exactly as it was read; other values use
/// the PartiQL-encoded forms. Read the output with `from_ion_data`, not
/// `from_ion`: a boxed value whose own annotation is, for example, `$bag`
/// is written as is, so `from_ion` would read it as a PartiQL bag.
pub fn to_ion_data(v : @value.Value) -> @ion_core.IonValue {
encode(v, true)
}
///|
/// Decodes Ion data in the Ion-preserving mode: the value is boxed, so every
/// Ion detail is kept until a semantic lowers it (ADR 0005).
pub fn from_ion_data(x : @ion_core.IonValue) -> @value.Value {
@value.Value::from_ion(x)
}
///|
/// The shared encoder. `preserve` writes boxed Ion values exactly.
fn encode(v : @value.Value, preserve : Bool) -> @ion_core.IonValue {
v.fold((v, children) => {
match v {
Null => @ion_core.IonValue::untyped_null()
Missing => annotate(@ion_core.IonValue::untyped_null(), "$missing")
Bool(b) => @ion_core.IonValue::bool(b)
Int(n) => int64(n)
BigInt(n) => @ion_core.IonValue::big_int(n)
Float(d) => @ion_core.IonValue::float(d)
Decimal(d) => @ion_core.IonValue::decimal(d)
String(s) => @ion_core.IonValue::string(s)
Clob(bytes) => @ion_core.IonValue::clob(bytes)
Blob(bytes) => @ion_core.IonValue::blob(bytes)
Date(d) =>
annotate(
struct_of([
("year", small(d.year)),
("month", small(d.month)),
("day", small(d.day)),
]),
"$date",
)
Time(t) =>
annotate(
struct_of([
("hour", small(t.hour)),
("minute", small(t.minute)),
("second", seconds_decimal(t)),
("offset", offset_value(t.offset)),
]),
"$time",
)
Timestamp(ts) =>
annotate(
struct_of([
("year", small(ts.date.year)),
("month", small(ts.date.month)),
("day", small(ts.date.day)),
("hour", small(ts.time.hour)),
("minute", small(ts.time.minute)),
("second", seconds_decimal(ts.time)),
("offset", offset_value(ts.time.offset)),
]),
"$timestamp",
)
IntervalYM(i) =>
annotate(
struct_of([
("sign", sign_value(i.is_negative())),
("years", int64(i.years())),
("months", int64(i.months_part())),
]),
"$interval_ym",
)
IntervalDT(i) =>
annotate(
struct_of([
("sign", sign_value(i.is_negative())),
("days", int64(i.days())),
("hours", int64(i.hours())),
("minutes", int64(i.minutes())),
("seconds", int64(i.seconds_part())),
("nanos", int64(i.nanos_part())),
]),
"$interval_dt",
)
List(_) => @ion_core.IonValue::list(children)
Bag(_) => annotate(@ion_core.IonValue::list(children), "$bag")
Tuple(t) => struct_of(t.fields().mapi((i, p) => (p.0, children[i])))
Map(m) => {
let pairs = m
.entries()
.mapi((i, _) => {
@ion_core.IonValue::list([children[2 * i], children[2 * i + 1]])
})
@ion_core.IonValue::list(pairs).with_annotations([
@ion_core.SymbolToken::SymbolToken("$map"),
@ion_core.SymbolToken::SymbolToken(m.key_type.name()),
@ion_core.SymbolToken::SymbolToken(m.value_type.name()),
])
}
Graph(g) => encode_graph(g, children)
Ion(x) => if preserve { x } else { annotate(x, "$ion") }
}
})
}
///|
fn symbol(text : String) -> @ion_core.IonValue {
@ion_core.IonValue::symbol(text)
}
///|
/// The fields shared by nodes and edges: labels when there are any, and the
/// payload when there is one.
fn element_fields(
labels : Array[String],
payload : @ion_core.IonValue?,
) -> Array[(String, @ion_core.IonValue)] {
let out : Array[(String, @ion_core.IonValue)] = []
if !labels.is_empty() {
out.push(
(
"labels",
@ion_core.IonValue::list(labels.map(@ion_core.IonValue::string)),
),
)
}
if payload is Some(p) {
out.push(("payload", p))
}
out
}
///|
/// `$graph::{nodes: [...], edges: [...]}` (the external graph format).
fn encode_graph(
g : @value.GraphValue,
children : Array[@ion_core.IonValue],
) -> @ion_core.IonValue {
let mut at = 0
let payload = (p : @value.Value?) => {
match p {
None => None
Some(_) => {
let x = children[at]
at += 1
Some(x)
}
}
}
let nodes = g
.nodes()
.iter()
.map(n => {
struct_of(
[
("id", symbol(n.id)),
..element_fields(n.labels().to_owned(), payload(n.payload)),
],
)
})
.collect()
let edges = g
.edges()
.iter()
.map(e => {
let ends = match e.ends {
Directed(from~, to~) =>
@ion_core.IonValue::sexp([symbol(from), symbol("->"), symbol(to)])
Undirected(a, b) =>
@ion_core.IonValue::sexp([symbol(a), symbol("--"), symbol(b)])
}
struct_of(
[
("id", symbol(e.id)),
..element_fields(e.labels().to_owned(), payload(e.payload)),
("ends", ends),
],
)
})
.collect()
annotate(
struct_of([
("nodes", @ion_core.IonValue::list(nodes)),
("edges", @ion_core.IonValue::list(edges)),
]),
"$graph",
)
}