1use 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
15pub(crate) const TIMESTAMP_END_JULIAN: i32 = 109_203_528;
17const MIN_TIMESTAMP: i64 = -211_813_488_000_000_000;
19const END_TIMESTAMP: i64 = 9_223_371_331_200_000_000;
21const UNIX_TO_POSTGRES_SECS: i64 = 946_684_800;
23
24fn first(args: &[Value]) -> Result<&Value> {
26 args.first().ok_or_else(bad_value)
27}
28
29fn string(args: &[Value]) -> Result<&str> {
31 first(args)?.as_str().ok_or_else(bad_value)
32}
33
34fn typmod(args: &[Value]) -> Result<i32> {
36 match args.get(1) {
37 Some(Value::Int4(v)) => Ok(*v),
38 _ => Err(bad_value()),
39 }
40}
41
42fn explicit(args: &[Value]) -> Result<bool> {
44 args.get(2).and_then(Value::as_bool).ok_or_else(bad_value)
45}
46
47fn 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#[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#[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
74fn float4_to_float8(_: &Call<'_>, args: &[Value]) -> Result<Value> {
76 Ok(Value::Float8(float(first(args)?).ok_or_else(bad_value)?))
77}
78
79#[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
99fn 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
105pub(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
117pub(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
139fn 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
144pub(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
156fn int_to_bool(_: &Call<'_>, args: &[Value]) -> Result<Value> {
158 Ok(Value::Bool(first(args)?.as_i64().ok_or_else(bad_value)? != 0))
159}
160
161fn 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
166fn 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
172fn to_name(_: &Call<'_>, args: &[Value]) -> Result<Value> {
174 Ok(Value::text(types::name_in(string(args)?)))
175}
176
177fn same_string(_: &Call<'_>, args: &[Value]) -> Result<Value> {
179 Ok(Value::text(string(args)?))
180}
181
182fn bpchar_to_name(_: &Call<'_>, args: &[Value]) -> Result<Value> {
184 Ok(Value::text(types::name_in(string(args)?).trim_end_matches(' ')))
185}
186
187fn 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
195fn text_to_char(_: &Call<'_>, args: &[Value]) -> Result<Value> {
197 Ok(Value::Char(types::char_in(string(args)?)))
198}
199
200#[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#[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
217fn 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
225fn 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
230fn 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
235fn 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
241fn 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
248fn 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
255fn 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
261fn 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
275fn 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
285fn 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
291fn 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
297pub(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#[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
314pub(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
322pub(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
331pub(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#[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
416fn 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
425fn 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#[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
446fn 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
452pub(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}