Skip to main content

rupg_func/
cast.rs

1//! The functions of the casts of `pg_cast` and of the typmod casts, such as `int48`, `numeric_int4`, `text_name` and `varchar`.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_types::{
5    self as types, DATE_INFINITY, DATE_NEGATIVE_INFINITY, Numeric, NumericSign,
6    POSTGRES_EPOCH_JDATE, TIMESTAMP_INFINITY, TIMESTAMP_NEGATIVE_INFINITY, UNIX_TO_POSTGRES_USECS,
7    USECS_PER_DAY, USECS_PER_SEC, Value, oid,
8};
9
10use crate::compare::float;
11use crate::io::base_type;
12use crate::math::{float_value, integer};
13use crate::{Call, Kernel, Session, bad_value, type_error};
14
15/// `TIMESTAMP_END_JULIAN`: the first Julian day after the last timestamp.
16pub(crate) const TIMESTAMP_END_JULIAN: i32 = 109_203_528;
17/// `MIN_TIMESTAMP`.
18const MIN_TIMESTAMP: i64 = -211_813_488_000_000_000;
19/// `END_TIMESTAMP`.
20const END_TIMESTAMP: i64 = 9_223_371_331_200_000_000;
21/// The seconds from 1970-01-01 to 2000-01-01.
22const UNIX_TO_POSTGRES_SECS: i64 = 946_684_800;
23
24/// The first argument.
25fn first(args: &[Value]) -> Result<&Value> {
26    args.first().ok_or_else(bad_value)
27}
28
29/// The string of the first argument.
30fn string(args: &[Value]) -> Result<&str> {
31    first(args)?.as_str().ok_or_else(bad_value)
32}
33
34/// The typmod argument of a typmod cast.
35fn typmod(args: &[Value]) -> Result<i32> {
36    match args.get(1) {
37        Some(Value::Int4(v)) => Ok(*v),
38        _ => Err(bad_value()),
39    }
40}
41
42/// The flag of an explicit cast of a typmod cast.
43fn explicit(args: &[Value]) -> Result<bool> {
44    args.get(2).and_then(Value::as_bool).ok_or_else(bad_value)
45}
46
47/// `int48` and the other casts between the integer types.
48fn int_to_int(call: &Call<'_>, args: &[Value]) -> Result<Value> {
49    let v = first(args)?.as_i64().ok_or_else(bad_value)?;
50    integer(call.ret, i128::from(v))
51}
52
53/// `i4tod` and the other casts from an integer to a float. The conversion rounds once.
54#[allow(clippy::cast_precision_loss)]
55fn int_to_float(call: &Call<'_>, args: &[Value]) -> Result<Value> {
56    let v = first(args)?.as_i64().ok_or_else(bad_value)?;
57    Ok(if base_type(call.ret) == oid::FLOAT4 {
58        Value::Float4(v as f32)
59    } else {
60        Value::Float8(v as f64)
61    })
62}
63
64/// `dtoi4` and the other casts from a float to an integer: `rint`, then the range check.
65#[allow(clippy::cast_possible_truncation)]
66fn float_to_int(call: &Call<'_>, args: &[Value]) -> Result<Value> {
67    let x = float(first(args)?).ok_or_else(bad_value)?.round_ties_even();
68    if x.is_nan() {
69        return integer(call.ret, i128::MAX);
70    }
71    integer(call.ret, x as i128)
72}
73
74/// `ftod`.
75fn float4_to_float8(_: &Call<'_>, args: &[Value]) -> Result<Value> {
76    Ok(Value::Float8(float(first(args)?).ok_or_else(bad_value)?))
77}
78
79/// `dtof`, with the checks of overflow and underflow.
80#[allow(clippy::cast_possible_truncation)]
81fn float8_to_float4(_: &Call<'_>, args: &[Value]) -> Result<Value> {
82    let x = float(first(args)?).ok_or_else(bad_value)?;
83    let r = x as f32;
84    if r.is_infinite() && !x.is_infinite() {
85        return Err(Error::new(
86            SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
87            "value out of range: overflow",
88        ));
89    }
90    if r == 0.0 && x != 0.0 {
91        return Err(Error::new(
92            SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
93            "value out of range: underflow",
94        ));
95    }
96    Ok(Value::Float4(r))
97}
98
99/// `int4_numeric` and the others.
100fn int_to_numeric(_: &Call<'_>, args: &[Value]) -> Result<Value> {
101    let v = first(args)?.as_i64().ok_or_else(bad_value)?;
102    Ok(Value::Numeric(Numeric::from_integer(i128::from(v))))
103}
104
105/// `numeric_int4` and the others: round half away from zero, then the range check.
106pub(crate) fn numeric_to_int(call: &Call<'_>, args: &[Value]) -> Result<Value> {
107    let Value::Numeric(n) = first(args)? else { return Err(bad_value()) };
108    let name = match base_type(call.ret) {
109        oid::INT2 => "smallint",
110        oid::INT4 => "integer",
111        _ => "bigint",
112    };
113    let v = n.to_integer(name).map_err(type_error)?;
114    integer(call.ret, v)
115}
116
117/// `numeric_float8` and `numeric_float4`: the input function of the float type on the text of the number.
118pub(crate) fn numeric_to_float(call: &Call<'_>, args: &[Value]) -> Result<Value> {
119    let Value::Numeric(n) = first(args)? else { return Err(bad_value()) };
120    let special = match n.sign() {
121        NumericSign::NaN => Some(f64::NAN),
122        NumericSign::Infinity => Some(f64::INFINITY),
123        NumericSign::NegativeInfinity => Some(f64::NEG_INFINITY),
124        _ => None,
125    };
126    if let Some(v) = special {
127        return Ok(float_value(call.ret, v));
128    }
129    let mut text = Vec::new();
130    types::numeric_out(n, &mut text);
131    let text = String::from_utf8(text).map_err(|_| bad_value())?;
132    if base_type(call.ret) == oid::FLOAT4 {
133        Ok(Value::Float4(types::float4_in(&text).map_err(type_error)?))
134    } else {
135        Ok(Value::Float8(types::float8_in(&text).map_err(type_error)?))
136    }
137}
138
139/// `float8_numeric`.
140fn float8_to_numeric(_: &Call<'_>, args: &[Value]) -> Result<Value> {
141    Ok(Value::Numeric(Numeric::from_f64(float(first(args)?).ok_or_else(bad_value)?)))
142}
143
144/// `float4_numeric`: the value printed with 6 significant digits, as `%.*g` with `FLT_DIG` gives it.
145pub(crate) fn float4_to_numeric(_: &Call<'_>, args: &[Value]) -> Result<Value> {
146    let v = float(first(args)?).ok_or_else(bad_value)?;
147    if !v.is_finite() {
148        return Ok(Value::Numeric(Numeric::from_f64(v)));
149    }
150    let text = format!("{v:.5e}");
151    let (mantissa, exponent) = text.split_once('e').unwrap_or((&text, "0"));
152    let mantissa = mantissa.trim_end_matches('0').trim_end_matches('.');
153    types::numeric_in(&format!("{mantissa}e{exponent}"), -1).map(Value::Numeric).map_err(type_error)
154}
155
156/// `int4_bool`.
157fn int_to_bool(_: &Call<'_>, args: &[Value]) -> Result<Value> {
158    Ok(Value::Bool(first(args)?.as_i64().ok_or_else(bad_value)? != 0))
159}
160
161/// `bool_int4`.
162fn bool_to_int(_: &Call<'_>, args: &[Value]) -> Result<Value> {
163    Ok(Value::Int4(i32::from(first(args)?.as_bool().ok_or_else(bad_value)?)))
164}
165
166/// `booltext`.
167fn bool_to_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
168    let v = first(args)?.as_bool().ok_or_else(bad_value)?;
169    Ok(Value::text(if v { "true" } else { "false" }))
170}
171
172/// `text_name`: the string cut to 63 bytes.
173fn to_name(_: &Call<'_>, args: &[Value]) -> Result<Value> {
174    Ok(Value::text(types::name_in(string(args)?)))
175}
176
177/// `name_text`, `name_bpchar` and the other casts that keep the string.
178fn same_string(_: &Call<'_>, args: &[Value]) -> Result<Value> {
179    Ok(Value::text(string(args)?))
180}
181
182/// `bpchar_name`: the string cut to 63 bytes, then without its trailing spaces.
183fn bpchar_to_name(_: &Call<'_>, args: &[Value]) -> Result<Value> {
184    Ok(Value::text(types::name_in(string(args)?).trim_end_matches(' ')))
185}
186
187/// `char_text`.
188fn char_to_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
189    let Value::Char(c) = first(args)? else { return Err(bad_value()) };
190    let mut out = Vec::new();
191    types::char_out(*c, &mut out);
192    Ok(Value::Text(String::from_utf8(out).map_err(|_| bad_value())?))
193}
194
195/// `text_char`.
196fn text_to_char(_: &Call<'_>, args: &[Value]) -> Result<Value> {
197    Ok(Value::Char(types::char_in(string(args)?)))
198}
199
200/// `i4tochar`.
201#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
202fn int_to_char(_: &Call<'_>, args: &[Value]) -> Result<Value> {
203    let v = first(args)?.as_i64().ok_or_else(bad_value)?;
204    if !(-128..=127).contains(&v) {
205        return Err(Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "\"char\" out of range"));
206    }
207    Ok(Value::Char(v as i8 as u8))
208}
209
210/// `chartoi4`: the byte as a signed number.
211#[allow(clippy::cast_possible_wrap)]
212fn char_to_int(_: &Call<'_>, args: &[Value]) -> Result<Value> {
213    let Value::Char(c) = first(args)? else { return Err(bad_value()) };
214    Ok(Value::Int4(i32::from(*c as i8)))
215}
216
217/// `i8tooid`.
218fn int8_to_oid(_: &Call<'_>, args: &[Value]) -> Result<Value> {
219    let v = first(args)?.as_i64().ok_or_else(bad_value)?;
220    u32::try_from(v)
221        .map(Value::Oid)
222        .map_err(|_| Error::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, "OID out of range"))
223}
224
225/// `oidtoi8`.
226fn oid_to_int8(_: &Call<'_>, args: &[Value]) -> Result<Value> {
227    Ok(Value::Int8(i64::from(first(args)?.as_oid().ok_or_else(bad_value)?)))
228}
229
230/// `oidtooid8`.
231fn oid_to_oid8(_: &Call<'_>, args: &[Value]) -> Result<Value> {
232    Ok(Value::Int8(i64::from(first(args)?.as_oid().ok_or_else(bad_value)?)))
233}
234
235/// `xid8toxid`: the low 32 bits.
236fn xid8_to_xid(_: &Call<'_>, args: &[Value]) -> Result<Value> {
237    let Value::Int8(v) = first(args)? else { return Err(bad_value()) };
238    Ok(Value::Oid(u32::try_from(v.cast_unsigned() & u64::from(u32::MAX)).map_err(|_| bad_value())?))
239}
240
241/// `varchar(varchar, int4, bool)`: the length of the typmod.
242fn varchar_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
243    let s =
244        types::varchar_coerce(string(args)?, typmod(args)?, explicit(args)?).map_err(type_error)?;
245    Ok(Value::text(s))
246}
247
248/// `bpchar(bpchar, int4, bool)`: the length of the typmod, with spaces added.
249fn bpchar_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
250    let s =
251        types::bpchar_coerce(string(args)?, typmod(args)?, explicit(args)?).map_err(type_error)?;
252    Ok(Value::text(s))
253}
254
255/// `numeric(numeric, int4)`: the precision and the scale of the typmod.
256fn numeric_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
257    let Value::Numeric(n) = first(args)? else { return Err(bad_value()) };
258    n.with_typmod(typmod(args)?).map(Value::Numeric).map_err(type_error)
259}
260
261/// `timestamp_scale` and `timestamptz_scale`.
262fn timestamp_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
263    let m = typmod(args)?;
264    match first(args)? {
265        Value::Timestamp(t) => {
266            Ok(Value::Timestamp(types::adjust_timestamp(*t, m).map_err(type_error)?))
267        }
268        Value::TimestampTz(t) => {
269            Ok(Value::TimestampTz(types::adjust_timestamp(*t, m).map_err(type_error)?))
270        }
271        _ => Err(bad_value()),
272    }
273}
274
275/// `time_scale` and `timetz_scale`.
276fn time_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
277    let m = typmod(args)?;
278    match first(args)? {
279        Value::Time(t) => Ok(Value::Time(types::adjust_time(*t, m))),
280        Value::TimeTz(t, z) => Ok(Value::TimeTz(types::adjust_time(*t, m), *z)),
281        _ => Err(bad_value()),
282    }
283}
284
285/// `interval_scale`.
286fn interval_typmod(_: &Call<'_>, args: &[Value]) -> Result<Value> {
287    let Value::Interval(period) = first(args)? else { return Err(bad_value()) };
288    types::adjust_interval(*period, typmod(args)?).map(Value::Interval).map_err(type_error)
289}
290
291/// The offset east of UTC in seconds of the zone of the session at an instant in microseconds since 2000-01-01 UTC.
292fn offset_at(session: &dyn Session, utc: i64) -> Result<i64> {
293    let unix = utc.div_euclid(USECS_PER_SEC) + UNIX_TO_POSTGRES_SECS;
294    Ok(i64::from(session.zone()?.at(unix).0))
295}
296
297/// The local time of the session for an instant.
298pub(crate) fn local(session: &dyn Session, utc: i64) -> Result<i64> {
299    let offset = offset_at(session, utc)?;
300    utc.checked_add(offset * USECS_PER_SEC)
301        .ok_or_else(|| Error::new(SqlState::DATETIME_VALUE_OUT_OF_RANGE, "timestamp out of range"))
302}
303
304/// The local time of the session for an instant in microseconds since 2000-01-01 UTC, and the offset east of UTC in seconds of the zone at that instant.
305///
306/// # Errors
307///
308/// The error of [`Session::zone`], and `22008` when the local time is out of range.
309#[allow(clippy::cast_possible_truncation)]
310pub fn local_time(session: &dyn Session, utc: i64) -> Result<(i64, i32)> {
311    Ok((local(session, utc)?, offset_at(session, utc)? as i32))
312}
313
314/// The instant of a local time of the session, with the offset that `DetermineTimeZoneOffset` gives for the time.
315pub(crate) fn instant(session: &dyn Session, local: i64) -> Result<i64> {
316    let zone = session.zone()?;
317    let seconds = local.div_euclid(USECS_PER_SEC) - UNIX_TO_POSTGRES_USECS / USECS_PER_SEC;
318    let (west, _) = types::local_offset(seconds, zone.as_ref());
319    local.checked_add(i64::from(west) * USECS_PER_SEC).ok_or_else(timestamp_range)
320}
321
322/// `IS_VALID_TIMESTAMP`.
323pub(crate) fn valid_timestamp(t: i64) -> bool {
324    (MIN_TIMESTAMP..END_TIMESTAMP).contains(&t)
325}
326
327pub(crate) fn timestamp_range() -> Error {
328    Error::new(SqlState::DATETIME_VALUE_OUT_OF_RANGE, "timestamp out of range")
329}
330
331/// `date2timestamp`: midnight of the date.
332pub(crate) fn midnight(date: i32) -> Result<i64> {
333    match date {
334        DATE_NEGATIVE_INFINITY => Ok(TIMESTAMP_NEGATIVE_INFINITY),
335        DATE_INFINITY => Ok(TIMESTAMP_INFINITY),
336        d if d >= TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE => Err(Error::new(
337            SqlState::DATETIME_VALUE_OUT_OF_RANGE,
338            "date out of range for timestamp",
339        )),
340        d => Ok(i64::from(d) * USECS_PER_DAY),
341    }
342}
343
344/// The date of a local time.
345#[allow(clippy::cast_possible_truncation)]
346fn date_of(local: i64) -> i32 {
347    match local {
348        TIMESTAMP_NEGATIVE_INFINITY => DATE_NEGATIVE_INFINITY,
349        TIMESTAMP_INFINITY => DATE_INFINITY,
350        t => t.div_euclid(USECS_PER_DAY) as i32,
351    }
352}
353
354pub(crate) fn infinite(t: i64) -> bool {
355    t == TIMESTAMP_NEGATIVE_INFINITY || t == TIMESTAMP_INFINITY
356}
357
358fn date_timestamp(_: &Call<'_>, args: &[Value]) -> Result<Value> {
359    let Value::Date(d) = first(args)? else { return Err(bad_value()) };
360    Ok(Value::Timestamp(midnight(*d)?))
361}
362
363fn date_timestamptz(call: &Call<'_>, args: &[Value]) -> Result<Value> {
364    let Value::Date(d) = first(args)? else { return Err(bad_value()) };
365    let t = midnight(*d)?;
366    if infinite(t) {
367        return Ok(Value::TimestampTz(t));
368    }
369    let utc = instant(call.session, t)?;
370    if !valid_timestamp(utc) {
371        return Err(Error::new(
372            SqlState::DATETIME_VALUE_OUT_OF_RANGE,
373            "date out of range for timestamp",
374        ));
375    }
376    Ok(Value::TimestampTz(utc))
377}
378
379fn timestamp_date(_: &Call<'_>, args: &[Value]) -> Result<Value> {
380    let Value::Timestamp(t) = first(args)? else { return Err(bad_value()) };
381    Ok(Value::Date(date_of(*t)))
382}
383
384fn timestamptz_date(call: &Call<'_>, args: &[Value]) -> Result<Value> {
385    let Value::TimestampTz(t) = first(args)? else { return Err(bad_value()) };
386    if infinite(*t) {
387        return Ok(Value::Date(date_of(*t)));
388    }
389    Ok(Value::Date(date_of(local(call.session, *t)?)))
390}
391
392fn timestamp_timestamptz(call: &Call<'_>, args: &[Value]) -> Result<Value> {
393    let Value::Timestamp(t) = first(args)? else { return Err(bad_value()) };
394    if infinite(*t) {
395        return Ok(Value::TimestampTz(*t));
396    }
397    let utc = instant(call.session, *t)?;
398    if !valid_timestamp(utc) {
399        return Err(timestamp_range());
400    }
401    Ok(Value::TimestampTz(utc))
402}
403
404fn timestamptz_timestamp(call: &Call<'_>, args: &[Value]) -> Result<Value> {
405    let Value::TimestampTz(t) = first(args)? else { return Err(bad_value()) };
406    if infinite(*t) {
407        return Ok(Value::Timestamp(*t));
408    }
409    let local = local(call.session, *t)?;
410    if !valid_timestamp(local) {
411        return Err(timestamp_range());
412    }
413    Ok(Value::Timestamp(local))
414}
415
416/// `timestamp_time`: null for an infinite timestamp.
417fn timestamp_time(_: &Call<'_>, args: &[Value]) -> Result<Value> {
418    let Value::Timestamp(t) = first(args)? else { return Err(bad_value()) };
419    if infinite(*t) {
420        return Ok(Value::Null);
421    }
422    Ok(Value::Time(t.rem_euclid(USECS_PER_DAY)))
423}
424
425/// `timestamptz_time`: null for an infinite timestamp.
426fn timestamptz_time(call: &Call<'_>, args: &[Value]) -> Result<Value> {
427    let Value::TimestampTz(t) = first(args)? else { return Err(bad_value()) };
428    if infinite(*t) {
429        return Ok(Value::Null);
430    }
431    Ok(Value::Time(local(call.session, *t)?.rem_euclid(USECS_PER_DAY)))
432}
433
434/// `timestamptz_timetz`: null for an infinite timestamp.
435#[allow(clippy::cast_possible_truncation)]
436fn timestamptz_timetz(call: &Call<'_>, args: &[Value]) -> Result<Value> {
437    let Value::TimestampTz(t) = first(args)? else { return Err(bad_value()) };
438    if infinite(*t) {
439        return Ok(Value::Null);
440    }
441    let offset = offset_at(call.session, *t)?;
442    let local = local(call.session, *t)?;
443    Ok(Value::TimeTz(local.rem_euclid(USECS_PER_DAY), -offset as i32))
444}
445
446/// `timetz_time`.
447fn timetz_time(_: &Call<'_>, args: &[Value]) -> Result<Value> {
448    let Value::TimeTz(t, _) = first(args)? else { return Err(bad_value()) };
449    Ok(Value::Time(*t))
450}
451
452/// The kernel of a cast function by its `prosrc`.
453pub(crate) fn by_src(src: &str) -> Option<Kernel> {
454    Some(match src {
455        "int48" | "int84" | "i2toi4" | "i4toi2" | "int28" | "int82" => int_to_int,
456        "i2tod" | "i4tod" | "i8tod" | "i2tof" | "i4tof" | "i8tof" => int_to_float,
457        "dtoi2" | "dtoi4" | "dtoi8" | "ftoi2" | "ftoi4" | "ftoi8" => float_to_int,
458        "ftod" => float4_to_float8,
459        "dtof" => float8_to_float4,
460        "int2_numeric" | "int4_numeric" | "int8_numeric" => int_to_numeric,
461        "numeric_int2" | "numeric_int4" | "numeric_int8" => numeric_to_int,
462        "numeric_float4" | "numeric_float8" => numeric_to_float,
463        "float8_numeric" => float8_to_numeric,
464        "float4_numeric" => float4_to_numeric,
465        "int4_bool" => int_to_bool,
466        "bool_int4" => bool_to_int,
467        "booltext" => bool_to_text,
468        "text_name" => to_name,
469        "name_text" | "name_bpchar" => same_string,
470        "bpchar_name" => bpchar_to_name,
471        "char_text" | "char_bpchar" => char_to_text,
472        "text_char" => text_to_char,
473        "i4tochar" => int_to_char,
474        "chartoi4" => char_to_int,
475        "i8tooid" => int8_to_oid,
476        "oidtoi8" => oid_to_int8,
477        "oidtooid8" => oid_to_oid8,
478        "xid8toxid" => xid8_to_xid,
479        "varchar" => varchar_typmod,
480        "bpchar" => bpchar_typmod,
481        "numeric" => numeric_typmod,
482        "timestamp_scale" | "timestamptz_scale" => timestamp_typmod,
483        "time_scale" | "timetz_scale" => time_typmod,
484        "interval_scale" => interval_typmod,
485        "date_timestamp" => date_timestamp,
486        "date_timestamptz" => date_timestamptz,
487        "timestamp_date" => timestamp_date,
488        "timestamptz_date" => timestamptz_date,
489        "timestamp_timestamptz" | "timestamp_at_local" => timestamp_timestamptz,
490        "timestamptz_timestamp" | "timestamptz_at_local" => timestamptz_timestamp,
491        "timestamp_time" => timestamp_time,
492        "timestamptz_time" => timestamptz_time,
493        "timestamptz_timetz" => timestamptz_timetz,
494        "timetz_time" => timetz_time,
495        _ => return None,
496    })
497}