reifydb-type 0.4.8

Core type system and value representations for ReifyDB
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 ReifyDB

use std::cmp::min;

use Type::*;

use crate::value::r#type::Type;

impl Type {
	/// Promote two Types to a common supertype, similar to Postgres
	/// expression evaluation.
	pub fn promote(left: Type, right: Type) -> Type {
		if matches!(left, Option(_)) || matches!(right, Option(_)) {
			return left;
		}

		if left == Utf8 || right == Utf8 {
			return Utf8;
		}

		if left == Boolean || right == Boolean {
			return Boolean;
		}

		if left == Float8 || right == Float8 {
			return Float8;
		}

		if left == Float4 || right == Float4 {
			return Float8;
		}

		let signed_order = [Int1, Int2, Int4, Int8, Int16];
		let unsigned_order = [Uint1, Uint2, Uint4, Uint8, Uint16];

		let is_signed = |k: &Type| signed_order.contains(k);
		let is_unsigned = |k: &Type| unsigned_order.contains(k);

		let rank = |k: &Type| match k {
			Int1 | Uint1 => 0,
			Int2 | Uint2 => 1,
			Int4 | Uint4 => 2,
			Int8 | Uint8 => 3,
			Int16 | Uint16 => 4,
			_ => usize::MAX,
		};

		if is_signed(&left) && is_signed(&right) {
			return signed_order[min(rank(&left).max(rank(&right)), 3) + 1].clone();
		}

		if is_unsigned(&left) && is_unsigned(&right) {
			return unsigned_order[min(rank(&left).max(rank(&right)), 3) + 1].clone();
		}

		if (is_signed(&left) && is_unsigned(&right)) || (is_unsigned(&left) && is_signed(&right)) {
			return match rank(&left).max(rank(&right)) + 1 {
				0 => Int1,
				1 => Int2,
				2 => Int4,
				3 => Int8,
				4 => Int16,
				_ => Int16,
			};
		}

		left
	}
}

#[cfg(test)]
pub mod tests {
	use Type::*;

	use crate::value::r#type::Type;

	#[test]
	fn test_promote_bool() {
		let cases = [
			(Boolean, Boolean, Boolean),
			(Boolean, Float4, Boolean),
			(Boolean, Float8, Boolean),
			(Boolean, Int1, Boolean),
			(Boolean, Int2, Boolean),
			(Boolean, Int4, Boolean),
			(Boolean, Int8, Boolean),
			(Boolean, Int16, Boolean),
			(Boolean, Utf8, Utf8),
			(Boolean, Uint1, Boolean),
			(Boolean, Uint2, Boolean),
			(Boolean, Uint4, Boolean),
			(Boolean, Uint8, Boolean),
			(Boolean, Uint16, Boolean),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_float4() {
		let cases = [
			(Float4, Boolean, Boolean),
			(Float4, Float4, Float8),
			(Float4, Float8, Float8),
			(Float4, Int1, Float8),
			(Float4, Int2, Float8),
			(Float4, Int4, Float8),
			(Float4, Int8, Float8),
			(Float4, Int16, Float8),
			(Float4, Utf8, Utf8),
			(Float4, Uint1, Float8),
			(Float4, Uint2, Float8),
			(Float4, Uint4, Float8),
			(Float4, Uint8, Float8),
			(Float4, Uint16, Float8),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_float8() {
		let cases = [
			(Float8, Boolean, Boolean),
			(Float8, Float4, Float8),
			(Float8, Float8, Float8),
			(Float8, Int1, Float8),
			(Float8, Int2, Float8),
			(Float8, Int4, Float8),
			(Float8, Int8, Float8),
			(Float8, Int16, Float8),
			(Float8, Utf8, Utf8),
			(Float8, Uint1, Float8),
			(Float8, Uint2, Float8),
			(Float8, Uint4, Float8),
			(Float8, Uint8, Float8),
			(Float8, Uint16, Float8),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_int1() {
		let cases = [
			(Int1, Boolean, Boolean),
			(Int1, Float4, Float8),
			(Int1, Float8, Float8),
			(Int1, Int1, Int2),
			(Int1, Int2, Int4),
			(Int1, Int4, Int8),
			(Int1, Int8, Int16),
			(Int1, Int16, Int16),
			(Int1, Utf8, Utf8),
			(Int1, Uint1, Int2),
			(Int1, Uint2, Int4),
			(Int1, Uint4, Int8),
			(Int1, Uint8, Int16),
			(Int1, Uint16, Int16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_int2() {
		let cases = [
			(Int2, Boolean, Boolean),
			(Int2, Float4, Float8),
			(Int2, Float8, Float8),
			(Int2, Int1, Int4),
			(Int2, Int2, Int4),
			(Int2, Int4, Int8),
			(Int2, Int8, Int16),
			(Int2, Int16, Int16),
			(Int2, Utf8, Utf8),
			(Int2, Uint1, Int4),
			(Int2, Uint2, Int4),
			(Int2, Uint4, Int8),
			(Int2, Uint8, Int16),
			(Int2, Uint16, Int16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_int4() {
		let cases = [
			(Int4, Boolean, Boolean),
			(Int4, Float4, Float8),
			(Int4, Float8, Float8),
			(Int4, Int1, Int8),
			(Int4, Int2, Int8),
			(Int4, Int4, Int8),
			(Int4, Int8, Int16),
			(Int4, Int16, Int16),
			(Int4, Utf8, Utf8),
			(Int4, Uint1, Int8),
			(Int4, Uint2, Int8),
			(Int4, Uint4, Int8),
			(Int4, Uint8, Int16),
			(Int4, Uint16, Int16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_int8() {
		let cases = [
			(Int8, Boolean, Boolean),
			(Int8, Float4, Float8),
			(Int8, Float8, Float8),
			(Int8, Int1, Int16),
			(Int8, Int2, Int16),
			(Int8, Int4, Int16),
			(Int8, Int8, Int16),
			(Int8, Int16, Int16),
			(Int8, Utf8, Utf8),
			(Int8, Uint1, Int16),
			(Int8, Uint2, Int16),
			(Int8, Uint4, Int16),
			(Int8, Uint8, Int16),
			(Int8, Uint16, Int16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_int16() {
		let cases = [
			(Int16, Boolean, Boolean),
			(Int16, Float4, Float8),
			(Int16, Float8, Float8),
			(Int16, Int1, Int16),
			(Int16, Int2, Int16),
			(Int16, Int4, Int16),
			(Int16, Int8, Int16),
			(Int16, Int16, Int16),
			(Int16, Utf8, Utf8),
			(Int16, Uint1, Int16),
			(Int16, Uint2, Int16),
			(Int16, Uint4, Int16),
			(Int16, Uint8, Int16),
			(Int16, Uint16, Int16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_string() {
		let kinds = [
			Boolean, Float4, Float8, Int1, Int2, Int4, Int8, Int16, Utf8, Uint1, Uint2, Uint4, Uint8,
			Uint16,
		];
		for ty in kinds {
			assert_eq!(Type::promote(Utf8, ty), Utf8);
		}
	}

	#[test]
	fn test_promote_uint1() {
		let cases = [
			(Uint1, Boolean, Boolean),
			(Uint1, Float4, Float8),
			(Uint1, Float8, Float8),
			(Uint1, Int1, Int2),
			(Uint1, Int2, Int4),
			(Uint1, Int4, Int8),
			(Uint1, Int8, Int16),
			(Uint1, Int16, Int16),
			(Uint1, Utf8, Utf8),
			(Uint1, Uint1, Uint2),
			(Uint1, Uint2, Uint4),
			(Uint1, Uint4, Uint8),
			(Uint1, Uint8, Uint16),
			(Uint1, Uint16, Uint16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_uint2() {
		let cases = [
			(Uint2, Boolean, Boolean),
			(Uint2, Float4, Float8),
			(Uint2, Float8, Float8),
			(Uint2, Int1, Int4),
			(Uint2, Int2, Int4),
			(Uint2, Int4, Int8),
			(Uint2, Int8, Int16),
			(Uint2, Int16, Int16),
			(Uint2, Utf8, Utf8),
			(Uint2, Uint1, Uint4),
			(Uint2, Uint2, Uint4),
			(Uint2, Uint4, Uint8),
			(Uint2, Uint8, Uint16),
			(Uint2, Uint16, Uint16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_uint4() {
		let cases = [
			(Uint4, Boolean, Boolean),
			(Uint4, Float4, Float8),
			(Uint4, Float8, Float8),
			(Uint4, Int1, Int8),
			(Uint4, Int2, Int8),
			(Uint4, Int4, Int8),
			(Uint4, Int8, Int16),
			(Uint4, Int16, Int16),
			(Uint4, Utf8, Utf8),
			(Uint4, Uint1, Uint8),
			(Uint4, Uint2, Uint8),
			(Uint4, Uint4, Uint8),
			(Uint4, Uint8, Uint16),
			(Uint4, Uint16, Uint16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_uint8() {
		let cases = [
			(Uint8, Boolean, Boolean),
			(Uint8, Float4, Float8),
			(Uint8, Float8, Float8),
			(Uint8, Int1, Int16),
			(Uint8, Int2, Int16),
			(Uint8, Int4, Int16),
			(Uint8, Int8, Int16),
			(Uint8, Int16, Int16),
			(Uint8, Utf8, Utf8),
			(Uint8, Uint1, Uint16),
			(Uint8, Uint2, Uint16),
			(Uint8, Uint4, Uint16),
			(Uint8, Uint8, Uint16),
			(Uint8, Uint16, Uint16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}

	#[test]
	fn test_promote_uint16() {
		let cases = [
			(Uint16, Boolean, Boolean),
			(Uint16, Float4, Float8),
			(Uint16, Float8, Float8),
			(Uint16, Int1, Int16),
			(Uint16, Int2, Int16),
			(Uint16, Int4, Int16),
			(Uint16, Int8, Int16),
			(Uint16, Int16, Int16),
			(Uint16, Utf8, Utf8),
			(Uint16, Uint1, Uint16),
			(Uint16, Uint2, Uint16),
			(Uint16, Uint4, Uint16),
			(Uint16, Uint8, Uint16),
			(Uint16, Uint16, Uint16),
		];
		for (left, right, expected) in cases {
			assert_eq!(Type::promote(left, right), expected);
		}
	}
}