1use super::Numeric;
2
3#[doc(hidden)]
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
7pub struct ArithmeticOp;
8
9#[doc(hidden)]
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
12pub struct AddOp;
13
14#[doc(hidden)]
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
17pub struct SubOp;
18
19#[doc(hidden)]
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
22pub struct MulOp;
23
24#[doc(hidden)]
26#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
27pub struct DivOp;
28
29#[doc(hidden)]
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
32pub struct RemOp;
33
34#[doc(hidden)]
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
37pub struct PropagateNullability;
38
39#[doc(hidden)]
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
42pub struct AlwaysNullable;
43
44#[doc(hidden)]
46pub trait ArithmeticNullability: super::private::Sealed + Copy + 'static {}
47
48impl super::private::Sealed for PropagateNullability {}
49impl super::private::Sealed for AlwaysNullable {}
50impl ArithmeticNullability for PropagateNullability {}
51impl ArithmeticNullability for AlwaysNullable {}
52
53#[diagnostic::on_unimplemented(
60 message = "arithmetic between `{Self}` and `{Rhs}` is not supported",
61 label = "both operands must be Numeric (Int, BigInt, Float, Double, etc.)"
62)]
63pub trait ArithmeticOutput<Rhs: Numeric = Self, Op = ArithmeticOp>: Numeric {
64 type Output: Numeric;
66
67 type Nullability: ArithmeticNullability;
69}
70
71#[diagnostic::on_unimplemented(
73 message = "unary negation of `{Self}` is not supported",
74 label = "the dialect has no numeric result mapping for this operand"
75)]
76pub trait NegOutput: Numeric {
77 type Output: Numeric;
79}
80
81macro_rules! neg_output {
82 ($input:ty => $out:ty) => {
83 impl NegOutput for $input {
84 type Output = $out;
85 }
86 };
87}
88
89macro_rules! arithmetic_output {
92 ($lhs:ty, $rhs:ty => $out:ty) => {
93 arithmetic_output!($lhs, $rhs => $out; zero_divisor: PropagateNullability);
94 };
95 ($lhs:ty, $rhs:ty => $out:ty; zero_divisor: $zero_divisor:ty) => {
96 impl ArithmeticOutput<$rhs> for $lhs {
97 type Output = $out;
98 type Nullability = PropagateNullability;
99 }
100
101 impl ArithmeticOutput<$rhs, AddOp> for $lhs {
102 type Output = $out;
103 type Nullability = PropagateNullability;
104 }
105
106 impl ArithmeticOutput<$rhs, SubOp> for $lhs {
107 type Output = $out;
108 type Nullability = PropagateNullability;
109 }
110
111 impl ArithmeticOutput<$rhs, MulOp> for $lhs {
112 type Output = $out;
113 type Nullability = PropagateNullability;
114 }
115
116 impl ArithmeticOutput<$rhs, DivOp> for $lhs {
117 type Output = $out;
118 type Nullability = $zero_divisor;
119 }
120
121 impl ArithmeticOutput<$rhs, RemOp> for $lhs {
122 type Output = $out;
123 type Nullability = $zero_divisor;
124 }
125 };
126}
127
128use crate::sqlite::types::{Integer, Numeric as SqliteNumeric, Real};
136
137arithmetic_output!(Integer, Integer => Integer; zero_divisor: AlwaysNullable);
139arithmetic_output!(Integer, Real => Real; zero_divisor: AlwaysNullable);
141arithmetic_output!(Integer, SqliteNumeric => SqliteNumeric; zero_divisor: AlwaysNullable);
143
144arithmetic_output!(Real, Integer => Real; zero_divisor: AlwaysNullable);
146arithmetic_output!(Real, Real => Real; zero_divisor: AlwaysNullable);
148arithmetic_output!(Real, SqliteNumeric => Real; zero_divisor: AlwaysNullable);
150
151arithmetic_output!(SqliteNumeric, Integer => SqliteNumeric; zero_divisor: AlwaysNullable);
153arithmetic_output!(SqliteNumeric, Real => Real; zero_divisor: AlwaysNullable);
155arithmetic_output!(SqliteNumeric, SqliteNumeric => SqliteNumeric; zero_divisor: AlwaysNullable);
157
158use crate::sqlite::types::Any as SqliteAny;
160
161arithmetic_output!(SqliteAny, SqliteAny => SqliteAny);
162arithmetic_output!(SqliteAny, Integer => SqliteAny);
163arithmetic_output!(SqliteAny, Real => SqliteAny);
164arithmetic_output!(SqliteAny, SqliteNumeric => SqliteAny);
165arithmetic_output!(Integer, SqliteAny => SqliteAny);
166arithmetic_output!(Real, SqliteAny => SqliteAny);
167arithmetic_output!(SqliteNumeric, SqliteAny => SqliteAny);
168
169neg_output!(Integer => Integer);
170neg_output!(Real => Real);
171neg_output!(SqliteNumeric => SqliteNumeric);
172neg_output!(SqliteAny => SqliteAny);
173
174use crate::postgres::types::{Float4, Float8, Int2, Int4, Int8, Numeric as PgNumeric};
186
187arithmetic_output!(Int2, Int2 => Int2);
189arithmetic_output!(Int2, Int4 => Int4); arithmetic_output!(Int2, Int8 => Int8); arithmetic_output!(Int2, Float4 => Float4); arithmetic_output!(Int2, Float8 => Float8); arithmetic_output!(Int2, PgNumeric => PgNumeric);
194
195arithmetic_output!(Int4, Int2 => Int4); arithmetic_output!(Int4, Int4 => Int4);
198arithmetic_output!(Int4, Int8 => Int8); arithmetic_output!(Int4, Float4 => Float8); arithmetic_output!(Int4, Float8 => Float8); arithmetic_output!(Int4, PgNumeric => PgNumeric);
202
203arithmetic_output!(Int8, Int2 => Int8); arithmetic_output!(Int8, Int4 => Int8); arithmetic_output!(Int8, Int8 => Int8);
207arithmetic_output!(Int8, Float4 => Float8); arithmetic_output!(Int8, Float8 => Float8); arithmetic_output!(Int8, PgNumeric => PgNumeric);
210
211arithmetic_output!(Float4, Int2 => Float4); arithmetic_output!(Float4, Int4 => Float8); arithmetic_output!(Float4, Int8 => Float8); arithmetic_output!(Float4, Float4 => Float4);
216arithmetic_output!(Float4, Float8 => Float8); arithmetic_output!(Float4, PgNumeric => Float8);
218
219arithmetic_output!(Float8, Int2 => Float8);
221arithmetic_output!(Float8, Int4 => Float8);
222arithmetic_output!(Float8, Int8 => Float8);
223arithmetic_output!(Float8, Float4 => Float8); arithmetic_output!(Float8, Float8 => Float8);
225arithmetic_output!(Float8, PgNumeric => Float8);
226
227arithmetic_output!(PgNumeric, Int2 => PgNumeric);
229arithmetic_output!(PgNumeric, Int4 => PgNumeric);
230arithmetic_output!(PgNumeric, Int8 => PgNumeric);
231arithmetic_output!(PgNumeric, Float4 => Float8); arithmetic_output!(PgNumeric, Float8 => Float8);
233arithmetic_output!(PgNumeric, PgNumeric => PgNumeric);
234
235neg_output!(Int2 => Int2);
236neg_output!(Int4 => Int4);
237neg_output!(Int8 => Int8);
238neg_output!(Float4 => Float4);
239neg_output!(Float8 => Float8);
240neg_output!(PgNumeric => PgNumeric);
241
242use crate::mysql::types::{
252 BigInt as MyBigInt, BigIntUnsigned as MyBigIntUnsigned, Decimal as MyDecimal,
253 Double as MyDouble,
254};
255
256macro_rules! mysql_arithmetic {
257 (
258 signed: [$($signed:ty),+ $(,)?],
259 unsigned: [$($unsigned:ty),+ $(,)?],
260 decimal: $decimal:ty,
261 approximate: [$($approximate:ty),+ $(,)?],
262 ) => {
263 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
264 [$($signed),+], [$($signed),+] => MyBigInt);
265 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
266 [$($signed),+], [$($unsigned),+] => MyBigIntUnsigned);
267 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
268 [$($unsigned),+], [$($signed),+, $($unsigned),+] => MyBigIntUnsigned);
269
270 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
271 [$($signed),+, $($unsigned),+], [$decimal] => MyDecimal);
272 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
273 [$decimal], [$($signed),+, $($unsigned),+, $decimal] => MyDecimal);
274
275 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
276 [$($signed),+, $($unsigned),+, $decimal], [$($approximate),+] => MyDouble);
277 mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
278 [$($approximate),+],
279 [$($signed),+, $($unsigned),+, $decimal, $($approximate),+] => MyDouble);
280
281 mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
282 [$($signed),+], [$($signed),+, $($unsigned),+] => MyBigInt);
283 mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
284 [$($unsigned),+], [$($signed),+, $($unsigned),+] => MyBigIntUnsigned);
285 mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
286 [$($signed),+, $($unsigned),+], [$decimal] => MyDecimal);
287 mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
288 [$decimal], [$($signed),+, $($unsigned),+, $decimal] => MyDecimal);
289 mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
290 [$($signed),+, $($unsigned),+, $decimal], [$($approximate),+] => MyDouble);
291 mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
292 [$($approximate),+],
293 [$($signed),+, $($unsigned),+, $decimal, $($approximate),+] => MyDouble);
294
295 mysql_arithmetic!(@matrix [DivOp], AlwaysNullable;
296 [$($signed),+, $($unsigned),+, $decimal],
297 [$($signed),+, $($unsigned),+, $decimal] => MyDecimal);
298 mysql_arithmetic!(@matrix [DivOp], AlwaysNullable;
299 [$($signed),+, $($unsigned),+, $decimal], [$($approximate),+] => MyDouble);
300 mysql_arithmetic!(@matrix [DivOp], AlwaysNullable;
301 [$($approximate),+],
302 [$($signed),+, $($unsigned),+, $decimal, $($approximate),+] => MyDouble);
303
304 $(neg_output!($signed => MyBigInt);)+
305 $(neg_output!($unsigned => MyBigInt);)+
306 neg_output!($decimal => MyDecimal);
307 $(neg_output!($approximate => MyDouble);)+
308 };
309 (@matrix $ops:tt, $nullability:ty;
310 [$($lhs:ty),+], $rhs:tt => $out:ty
311 ) => {
312 $(mysql_arithmetic!(@row $ops, $nullability; $lhs, $rhs => $out);)+
313 };
314 (@row [$op:ty $(, $remaining:ty)*], $nullability:ty;
315 $lhs:ty, [$($rhs:ty),+] => $out:ty
316 ) => {
317 $(
318 impl ArithmeticOutput<$rhs, $op> for $lhs {
319 type Output = $out;
320 type Nullability = $nullability;
321 }
322 )+
323 mysql_arithmetic!(@row [$($remaining),*], $nullability;
324 $lhs, [$($rhs),+] => $out);
325 };
326 (@row [], $nullability:ty; $lhs:ty, $rhs:tt => $out:ty) => {};
327}
328
329mysql_arithmetic! {
330 signed: [
331 crate::mysql::types::TinyInt,
332 crate::mysql::types::SmallInt,
333 crate::mysql::types::MediumInt,
334 crate::mysql::types::Int,
335 crate::mysql::types::BigInt,
336 ],
337 unsigned: [
338 crate::mysql::types::TinyIntUnsigned,
339 crate::mysql::types::SmallIntUnsigned,
340 crate::mysql::types::MediumIntUnsigned,
341 crate::mysql::types::IntUnsigned,
342 crate::mysql::types::BigIntUnsigned,
343 crate::mysql::types::Year,
344 ],
345 decimal: crate::mysql::types::Decimal,
346 approximate: [crate::mysql::types::Float, crate::mysql::types::Double],
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::mysql::types as my;
353 use crate::postgres::types as pg;
354 use crate::sqlite::types as sqlite;
355
356 trait Same<T> {}
357 impl<T> Same<T> for T {}
358
359 fn assert_output<Lhs, Rhs, Op, Output, Nullability>()
360 where
361 Lhs: ArithmeticOutput<Rhs, Op, Output = Output>,
362 Rhs: Numeric,
363 Output: Numeric,
364 <Lhs as ArithmeticOutput<Rhs, Op>>::Nullability: Same<Nullability>,
365 {
366 }
367
368 fn assert_neg_output<Input, Output>()
369 where
370 Input: NegOutput<Output = Output>,
371 Output: Numeric,
372 {
373 }
374
375 #[test]
376 fn mysql_operator_result_types_follow_server_categories() {
377 assert_output::<my::Int, my::SmallInt, AddOp, my::BigInt, PropagateNullability>();
378 assert_output::<my::Int, my::IntUnsigned, SubOp, my::BigIntUnsigned, PropagateNullability>(
379 );
380 assert_output::<my::BigIntUnsigned, my::Int, MulOp, my::BigIntUnsigned, PropagateNullability>(
381 );
382 assert_output::<my::Int, my::Int, DivOp, my::Decimal, AlwaysNullable>();
383 assert_output::<my::Int, my::IntUnsigned, RemOp, my::BigInt, AlwaysNullable>();
384 assert_output::<my::IntUnsigned, my::Int, RemOp, my::BigIntUnsigned, AlwaysNullable>();
385 assert_output::<my::Decimal, my::Int, AddOp, my::Decimal, PropagateNullability>();
386 assert_output::<my::Float, my::Int, AddOp, my::Double, PropagateNullability>();
387 assert_output::<my::Int, my::Double, DivOp, my::Double, AlwaysNullable>();
388 }
389
390 #[test]
391 fn every_mysql_numeric_marker_has_operator_and_negation_policy() {
392 macro_rules! assert_numeric_policy {
393 ($($ty:ty),+ $(,)?) => {
394 $(
395 assert_output::<$ty, $ty, AddOp, _, PropagateNullability>();
396 assert_output::<$ty, $ty, DivOp, _, AlwaysNullable>();
397 assert_output::<$ty, $ty, RemOp, _, AlwaysNullable>();
398 assert_neg_output::<$ty, _>();
399 )+
400 };
401 }
402
403 assert_numeric_policy!(
404 my::TinyInt,
405 my::TinyIntUnsigned,
406 my::SmallInt,
407 my::SmallIntUnsigned,
408 my::MediumInt,
409 my::MediumIntUnsigned,
410 my::Int,
411 my::IntUnsigned,
412 my::BigInt,
413 my::BigIntUnsigned,
414 my::Year,
415 my::Decimal,
416 my::Float,
417 my::Double,
418 );
419 }
420
421 #[test]
422 fn legacy_operator_independent_projection_remains_available() {
423 fn assert_legacy<Lhs, Rhs, Output>()
424 where
425 Lhs: ArithmeticOutput<Rhs, Output = Output>,
426 Rhs: Numeric,
427 Output: Numeric,
428 {
429 }
430
431 assert_legacy::<sqlite::Integer, sqlite::Real, sqlite::Real>();
432 assert_legacy::<pg::Int4, pg::Float8, pg::Float8>();
433 }
434
435 #[test]
436 fn sqlite_zero_divisor_operators_are_nullable() {
437 assert_output::<sqlite::Integer, sqlite::Integer, DivOp, sqlite::Integer, AlwaysNullable>();
438 assert_output::<sqlite::Integer, sqlite::Integer, RemOp, sqlite::Integer, AlwaysNullable>();
439 assert_output::<sqlite::Real, sqlite::Integer, DivOp, sqlite::Real, AlwaysNullable>();
440 }
441
442 #[test]
443 fn mysql_unary_negation_widens_to_a_signed_result() {
444 assert_neg_output::<my::TinyInt, my::BigInt>();
445 assert_neg_output::<my::BigIntUnsigned, my::BigInt>();
446 assert_neg_output::<my::Decimal, my::Decimal>();
447 assert_neg_output::<my::Float, my::Double>();
448 }
449}