// CAST to INTERVAL (spec M4d2 ยง5.4).
///|
/// The interval `v` in the fields of `q` (SQL92 6.10 GR 12): cut toward
/// zero to whole units of the last field (or of the fractional precision for
/// SECOND); a leading field with more digits than its precision (default
/// 2) is an interval field overflow.
fn interval_to_interval(
v : @value.Value,
q : @ast.IntervalQualifier,
) -> @value.Value raise OpFailure {
let leading = q.start_precision.unwrap_or(2)
let last = q.end.unwrap_or(q.start)
let overflow = DataFailure(
"an interval leading field of more than \{leading} digits",
)
match (v, q.start) {
(IntervalYM(i), Year | Month) => {
let m = big64(i.months)
let kept = if last is Year { m - m % 12N } else { m }
let lead = if q.start is Year { kept / 12N } else { kept }
if !within_digits(abs_int(lead), leading) {
raise overflow
}
ym_value(kept)
}
(IntervalDT(i), Day | Hour | Minute | Second) => {
let n = dt_nanos(i)
let step = if last is Second {
pow10(9 - q.fractional_precision.unwrap_or(6))
} else {
field_nanos(last)
}
let kept = n - n % step
if !within_digits(abs_int(kept) / field_nanos(q.start), leading) {
raise overflow
}
dt_value(kept)
}
_ => raise TypeFailure("CAST of an interval of the other class")
}
}
///|
/// The units of the field of a one-field qualifier, in months (year-month)
/// or nanoseconds (day-time).
fn field_units(f : @ast.DatetimeField) -> @bigint.BigInt {
match f {
Year => 12N
Month => 1N
_ => field_nanos(f)
}
}
///|
/// An exact number as `n` units of the one field of `q` (SQL92 6.10 GR 12a).
/// A fraction is cut toward zero by the interval rule.
fn number_to_interval(
d : @ion_core.Decimal,
q : @ast.IntervalQualifier,
) -> @value.Value raise OpFailure {
if q.end is Some(end) && end != q.start {
raise TypeFailure("CAST of a number to an interval of more than one field")
}
let c = d.coefficient() * field_units(q.start)
let e = d.exponent().to_int64()
let total = if e >= 0L {
if digit_count(c).to_int64() + e > 40L {
raise DataFailure("a number out of the range of the interval")
}
c * pow10(e.to_int())
} else if -e > (digit_count(c) + 1).to_int64() {
0N
} else {
c / pow10((-e).to_int())
}
let v = if q.start is (Year | Month) {
ym_value(total)
} else {
dt_value(total)
}
interval_to_interval(v, q)
}
///|
fn to_interval(
x : @value.Value,
q : @ast.IntervalQualifier,
) -> @value.Value raise OpFailure {
match x {
IntervalYM(_) | IntervalDT(_) => interval_to_interval(x, q)
// Read the literal with room for any digits, then cast it, so that a
// leading field that is too long is an overflow (SQL 22015), not a
// text that is not valid.
String(s) if keyword_literal(trimmed(s)) is Some(literal) =>
match literal {
Interval(body, negative~, qualifier~) =>
interval_to_interval(parse_interval(body, negative~, qualifier~), q)
_ => raise TypeFailure("'\{s}' is not an INTERVAL literal")
}
String(s) => {
let wide : @ast.IntervalQualifier = {
..q,
start_precision: Some(18),
fractional_precision: Some(9),
}
interval_to_interval(
parse_interval(trimmed(s), negative=false, qualifier=wide),
q,
)
}
Int(n) => number_to_interval(@ion_core.Decimal::Decimal(big64(n), 0), q)
BigInt(n) => number_to_interval(@ion_core.Decimal::Decimal(n, 0), q)
Decimal(d) => number_to_interval(d, q)
_ => raise TypeFailure("CAST of \{kind_name(x)} to INTERVAL")
}
}