// EXTRACT (SQL-92 6.6; spec M4d3 §4.2).
///|
/// The SQL name of a datetime field.
fn field_name(f : @ast.DatetimeField) -> String {
match f {
Year => "YEAR"
Month => "MONTH"
Day => "DAY"
Hour => "HOUR"
Minute => "MINUTE"
Second => "SECOND"
TimezoneHour => "TIMEZONE_HOUR"
TimezoneMinute => "TIMEZONE_MINUTE"
}
}
///|
/// Seconds and nanoseconds (magnitudes) as a decimal of `scale` fraction
/// digits; the fraction is cut to `scale` digits.
fn second_decimal(
negative : Bool,
seconds : Int64,
nanos : Int32,
scale : Int32,
) -> @ion_core.Decimal {
let units = big64(seconds) * pow10(scale) +
big64(nanos.to_int64()) / pow10(9 - scale)
@ion_core.Decimal::Decimal(if negative { -units } else { units }, -scale)
}
///|
fn no_field(f : @ast.DatetimeField, x : @value.Value) -> OpFailure {
TypeFailure("EXTRACT \{field_name(f)} from \{kind_name(x)}")
}
///|
/// A field of a time of day: the local value, the second with the value's
/// precision as scale, the offset's hours or minutes with its sign.
fn time_field(
f : @ast.DatetimeField,
t : @value.Time,
x : @value.Value,
) -> @value.Value raise OpFailure {
match f {
Hour => @value.Value::from_int(t.hour.to_int64())
Minute => @value.Value::from_int(t.minute.to_int64())
Second =>
@value.Value::from_decimal(
second_decimal(false, t.second.to_int64(), t.nanos, t.precision.min(9)),
)
TimezoneHour | TimezoneMinute =>
match t.offset {
Some(o) =>
@value.Value::from_int(
(if f is TimezoneHour { o / 60 } else { o % 60 }).to_int64(),
)
None => raise no_field(f, x)
}
_ => raise no_field(f, x)
}
}
///|
fn extract_field(
f : @ast.DatetimeField,
x : @value.Value,
) -> @value.Value raise OpFailure {
match x {
Date(d) =>
match f {
Year => @value.Value::from_int(d.year.to_int64())
Month => @value.Value::from_int(d.month.to_int64())
Day => @value.Value::from_int(d.day.to_int64())
_ => raise no_field(f, x)
}
Time(t) => time_field(f, t, x)
Timestamp(ts) =>
match f {
Year => @value.Value::from_int(ts.date.year.to_int64())
Month => @value.Value::from_int(ts.date.month.to_int64())
Day => @value.Value::from_int(ts.date.day.to_int64())
_ => time_field(f, ts.time, x)
}
// The value keeps no qualifier, so the fields are the normalized ones
// (spec M4d3 §4.2, Q6).
IntervalYM(i) => {
let sign = if i.is_negative() { -1L } else { 1L }
match f {
Year => @value.Value::from_int(sign * i.years())
Month => @value.Value::from_int(sign * i.months_part())
_ => raise no_field(f, x)
}
}
IntervalDT(i) => {
let negative = i.is_negative()
let sign = if negative { -1L } else { 1L }
match f {
Day => @value.Value::from_int(sign * i.days())
Hour => @value.Value::from_int(sign * i.hours())
Minute => @value.Value::from_int(sign * i.minutes())
Second => {
let nanos = i.nanos_part().to_int()
let digits = if nanos == 0 { 0 } else { needed_precision(nanos) }
@value.Value::from_decimal(
second_decimal(negative, i.seconds_part(), nanos, digits.max(6)),
)
}
_ => raise no_field(f, x)
}
}
_ => raise no_field(f, x)
}
}
///|
/// `EXTRACT(f FROM x)`.
pub fn extract(
field : @ast.DatetimeField,
args : Array[@value.Value],
) -> @value.Value raise OpFailure {
apply("EXTRACT", [Temporal], args, xs => extract_field(field, xs[0]))
}