use crate::utf8::get_global_utf8_processor;
use core::fmt;
use core::mem::size_of;
extern crate alloc;
use alloc::string::String;
use alloc::string::ToString;
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Default)]
pub enum DistanceType {
#[default]
L2 = 0,
InnerProduct = 1,
Cosine = 2,
}
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Default)]
pub enum VectorIndexType {
#[default]
HNSW = 0,
HNSW_SQ = 1,
HNSW_BQ = 2,
IVF = 3,
IVF_PQ = 4,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Default)]
pub struct VectorMetadata {
pub dimension: u16,
pub distance_type: DistanceType,
pub index_type: VectorIndexType,
pub compression_enabled: bool,
pub compression_scheme: u8,
pub compression_level: u8,
pub hnsw_m: u8,
pub hnsw_ef_construction: u32,
pub hnsw_ef_search: u32,
pub ivf_nlist: u32,
pub ivf_nprobe: u32,
}
impl VectorMetadata {
pub const fn new(
dimension: u16,
distance_type: DistanceType,
index_type: VectorIndexType,
) -> Self {
Self {
dimension,
distance_type,
index_type,
compression_enabled: false,
compression_scheme: 0,
compression_level: 3,
hnsw_m: 16,
hnsw_ef_construction: 200,
hnsw_ef_search: 128,
ivf_nlist: 1024,
ivf_nprobe: 16,
}
}
pub const fn with_compression(
dimension: u16,
distance_type: DistanceType,
index_type: VectorIndexType,
compression_enabled: bool,
compression_scheme: u8,
compression_level: u8,
) -> Self {
Self {
dimension,
distance_type,
index_type,
compression_enabled,
compression_scheme,
compression_level,
hnsw_m: 16,
hnsw_ef_construction: 200,
hnsw_ef_search: 128,
ivf_nlist: 1024,
ivf_nprobe: 16,
}
}
pub const fn with_all_params(
dimension: u16,
distance_type: DistanceType,
index_type: VectorIndexType,
compression_enabled: bool,
compression_scheme: u8,
compression_level: u8,
hnsw_m: u8,
hnsw_ef_construction: u32,
hnsw_ef_search: u32,
ivf_nlist: u32,
ivf_nprobe: u32,
) -> Self {
Self {
dimension,
distance_type,
index_type,
compression_enabled,
compression_scheme,
compression_level,
hnsw_m,
hnsw_ef_construction,
hnsw_ef_search,
ivf_nlist,
ivf_nprobe,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Default)]
pub struct JsonMetadata {
pub path: String,
pub value_type: Option<DataType>,
pub virtual_column: bool,
pub virtual_column_name: Option<String>,
pub index_config: JsonIndexConfig,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct JsonIndexConfig {
pub index_type: IndexType,
pub path_index_enabled: bool,
pub max_depth: u8,
pub index_array_elements: bool,
pub index_object_keys: bool,
}
impl JsonMetadata {
pub fn new(path: String) -> Self {
Self {
path,
value_type: None,
virtual_column: false,
virtual_column_name: None,
index_config: JsonIndexConfig::default(),
}
}
pub fn with_virtual_column(path: String, column_name: String) -> Self {
Self {
path: path.clone(),
value_type: None,
virtual_column: true,
virtual_column_name: Some(column_name),
index_config: JsonIndexConfig::default(),
}
}
pub fn with_index_config(path: String, index_config: JsonIndexConfig) -> Self {
Self {
path,
value_type: None,
virtual_column: false,
virtual_column_name: None,
index_config,
}
}
}
impl Default for JsonIndexConfig {
fn default() -> Self {
Self {
index_type: IndexType::BTree,
path_index_enabled: true,
max_depth: 10,
index_array_elements: true,
index_object_keys: true,
}
}
}
impl From<(u16, DistanceType, VectorIndexType)> for VectorMetadata {
fn from((dimension, distance_type, index_type): (u16, DistanceType, VectorIndexType)) -> Self {
Self {
dimension,
distance_type,
index_type,
compression_enabled: false,
compression_scheme: 0,
compression_level: 3,
hnsw_m: 16,
hnsw_ef_construction: 200,
hnsw_ef_search: 128,
ivf_nlist: 1024,
ivf_nprobe: 16,
}
}
}
impl From<(u16, DistanceType, VectorIndexType, bool, u8, u8)> for VectorMetadata {
fn from(
(
dimension,
distance_type,
index_type,
compression_enabled,
compression_scheme,
compression_level,
): (u16, DistanceType, VectorIndexType, bool, u8, u8),
) -> Self {
Self {
dimension,
distance_type,
index_type,
compression_enabled,
compression_scheme,
compression_level,
hnsw_m: 16,
hnsw_ef_construction: 200,
hnsw_ef_search: 128,
ivf_nlist: 1024,
ivf_nprobe: 16,
}
}
}
impl
From<(
u16,
DistanceType,
VectorIndexType,
bool,
u8,
u8,
u8,
u32,
u32,
u32,
u32,
)> for VectorMetadata
{
fn from(
(
dimension,
distance_type,
index_type,
compression_enabled,
compression_scheme,
compression_level,
hnsw_m,
hnsw_ef_construction,
hnsw_ef_search,
ivf_nlist,
ivf_nprobe,
): (
u16,
DistanceType,
VectorIndexType,
bool,
u8,
u8,
u8,
u32,
u32,
u32,
u32,
),
) -> Self {
Self {
dimension,
distance_type,
index_type,
compression_enabled,
compression_scheme,
compression_level,
hnsw_m,
hnsw_ef_construction,
hnsw_ef_search,
ivf_nlist,
ivf_nprobe,
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum DataType {
UInt8 = 0,
UInt16 = 1,
UInt32 = 2,
UInt64 = 3,
Int8 = 4,
Int16 = 5,
Int32 = 6,
Int64 = 7,
Float32 = 8,
Float64 = 9,
Bool = 10,
Timestamp = 11,
TimestampTZ = 12,
VarChar = 13,
Char = 14,
Text = 15,
Interval = 16,
Vector = 17,
Json = 18,
}
impl From<u8> for DataType {
fn from(value: u8) -> Self {
match value {
0 => DataType::UInt8,
1 => DataType::UInt16,
2 => DataType::UInt32,
3 => DataType::UInt64,
4 => DataType::Int8,
5 => DataType::Int16,
6 => DataType::Int32,
7 => DataType::Int64,
8 => DataType::Float32,
9 => DataType::Float64,
10 => DataType::Bool,
11 => DataType::Timestamp,
12 => DataType::TimestampTZ,
13 => DataType::VarChar,
14 => DataType::Char,
15 => DataType::Text,
16 => DataType::Interval,
17 => DataType::Vector,
18 => DataType::Json,
_ => DataType::VarChar, }
}
}
impl DataType {
pub const fn size(&self) -> Option<usize> {
match self {
DataType::UInt8 => Some(1),
DataType::UInt16 => Some(2),
DataType::UInt32 => Some(4),
DataType::UInt64 => Some(8),
DataType::Int8 => Some(1),
DataType::Int16 => Some(2),
DataType::Int32 => Some(4),
DataType::Int64 => Some(8),
DataType::Float32 => Some(4),
DataType::Float64 => Some(8),
DataType::Bool => Some(1),
DataType::Timestamp => Some(core::mem::size_of::<db_timestamp>()), DataType::TimestampTZ => Some(core::mem::size_of::<db_timestamp>()), DataType::Interval => Some(core::mem::size_of::<db_interval>()), DataType::VarChar => None, DataType::Char => None, DataType::Text => None, DataType::Vector => None, DataType::Json => None, }
}
pub const fn size_unwrap(&self) -> usize {
match self.size() {
Some(s) => s,
None => panic!("size_unwrap called on variable-size type"),
}
}
pub fn to_sql_type(&self, size: usize) -> alloc::string::String {
match self {
DataType::UInt8 => "INTEGER".to_string(),
DataType::UInt16 => "INTEGER".to_string(),
DataType::UInt32 => "INTEGER".to_string(),
DataType::UInt64 => "INTEGER".to_string(),
DataType::Int8 => "INTEGER".to_string(),
DataType::Int16 => "INTEGER".to_string(),
DataType::Int32 => "INTEGER".to_string(),
DataType::Int64 => "INTEGER".to_string(),
DataType::Float32 => "REAL".to_string(),
DataType::Float64 => "REAL".to_string(),
DataType::Bool => "BOOL".to_string(),
DataType::Timestamp => "TIMESTAMP".to_string(),
DataType::TimestampTZ => "TIMESTAMPTZ".to_string(),
DataType::Interval => "INTERVAL".to_string(),
DataType::VarChar => alloc::format!("VARCHAR({})", size),
DataType::Char => alloc::format!("CHAR({})", size),
DataType::Text => "TEXT".to_string(),
DataType::Vector => "VECTOR".to_string(),
DataType::Json => "JSON".to_string(),
}
}
}
impl Default for DataType {
fn default() -> Self {
DataType::Int32
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct db_interval {
pub value: i64,
pub precision: u8,
pub flags: u8,
}
impl db_interval {
pub const fn new(value: i64, precision: u8, flags: u8) -> Self {
Self {
value,
precision,
flags,
}
}
pub const fn storage_size(precision: u8) -> usize {
match precision {
0..=2 => 4, 3..=5 => 6, 6..=8 => 8, 9 => 10, _ => 8, }
}
pub const fn size(&self) -> usize {
Self::storage_size(self.precision)
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct db_timestamp {
pub value: i64,
pub tz_offset: i16,
pub precision: u8,
pub flags: u8,
}
impl db_timestamp {
pub const fn new(value: i64, tz_offset: i16, precision: u8, flags: u8) -> Self {
Self {
value,
tz_offset,
precision,
flags,
}
}
pub const fn storage_size(precision: u8) -> usize {
match precision {
0..=2 => 4, 3..=5 => 6, 6..=8 => 8, 9 => 10, _ => 8, }
}
pub const fn size(&self) -> usize {
Self::storage_size(self.precision)
}
pub fn add(&self, interval: &db_interval) -> Self {
Self {
value: self.value + interval.value,
tz_offset: self.tz_offset,
precision: core::cmp::max(self.precision, interval.precision),
flags: self.flags,
}
}
pub fn sub(&self, interval: &db_interval) -> Self {
Self {
value: self.value - interval.value,
tz_offset: self.tz_offset,
precision: core::cmp::max(self.precision, interval.precision),
flags: self.flags,
}
}
pub fn diff(&self, other: &db_timestamp) -> db_interval {
let diff_value = self.value - other.value;
let precision = core::cmp::max(self.precision, other.precision);
db_interval::new(diff_value, precision, 0)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum JsonStorage {
Inline([u8; 256]),
External {
pool_id: u8,
offset: u32,
length: u32,
},
Null,
}
#[derive(Copy, Clone, Debug)]
pub struct TimeZone {
pub name: &'static str,
pub offset: i32,
pub uses_dst: bool,
}
pub const TIME_ZONES: &[TimeZone] = &[
TimeZone {
name: "UTC",
offset: 0,
uses_dst: false,
},
TimeZone {
name: "Asia/Shanghai",
offset: 8 * 3600,
uses_dst: false,
},
TimeZone {
name: "America/New_York",
offset: -5 * 3600,
uses_dst: true,
},
TimeZone {
name: "Europe/London",
offset: 0,
uses_dst: true,
},
TimeZone {
name: "Asia/Tokyo",
offset: 9 * 3600,
uses_dst: false,
},
];
pub fn find_timezone(name: &str) -> Option<TimeZone> {
TIME_ZONES
.iter()
.find(|tz| tz.name.eq_ignore_ascii_case(name))
.copied()
}
pub fn convert_timezone(timestamp: &db_timestamp, tz_offset: i16) -> db_timestamp {
db_timestamp {
value: timestamp.value,
tz_offset: tz_offset,
precision: timestamp.precision,
flags: timestamp.flags,
}
}
pub fn get_timezone_offset(timezone_name: &str) -> Option<i16> {
find_timezone(timezone_name).map(|tz| tz.offset as i16)
}
pub fn create_timezone_from_offset(offset: i16) -> TimeZone {
TimeZone {
name: "UTC",
offset: offset as i32,
uses_dst: false,
}
}
pub mod time_format {
pub fn to_iso8601(_timestamp: &super::db_timestamp) -> alloc::string::String {
alloc::format!("2023-01-01T12:00:00.000000+00:00")
}
pub fn to_char(timestamp: &super::db_timestamp, _format: &str) -> alloc::string::String {
alloc::format!("{}", timestamp.value)
}
pub fn to_epoch(timestamp: &super::db_timestamp) -> f64 {
timestamp.value as f64 / 1000000.0
}
}
pub mod time_utils {
pub const fn seconds_to_millis(seconds: u64) -> u64 {
seconds * 1000
}
pub const fn millis_to_seconds(millis: u64) -> u64 {
millis / 1000
}
pub const fn micros_to_millis(micros: u64) -> u64 {
micros / 1000
}
pub const fn millis_to_micros(millis: u64) -> u64 {
millis * 1000
}
pub const fn nanos_to_millis(nanos: u64) -> u64 {
nanos / 1000000
}
pub const fn millis_to_nanos(millis: u64) -> u64 {
millis * 1000000
}
pub fn time_diff(start: u64, end: u64) -> u64 {
if end > start {
end - start
} else {
start - end
}
}
pub fn is_in_time_range(timestamp: u64, start: u64, end: u64) -> bool {
timestamp >= start && timestamp <= end
}
#[cfg(feature = "std")]
pub fn now_millis() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_millis() as u64
}
#[cfg(feature = "std")]
pub fn now_micros() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO)
.as_micros() as u64
}
}
#[repr(C)]
pub union Value {
pub u8: u8,
pub u16: u16,
pub u32: u32,
pub u64: u64,
pub i8: i8,
pub i16: i16,
pub i32: i32,
pub i64: i64,
pub float32: f32,
pub float64: f64,
pub bool: bool,
pub timestamp: u64, pub time: db_timestamp, pub interval: db_interval, pub string: [u8; MAX_STRING_LEN],
pub vector: *const f32, pub vector_metadata: VectorMetadata, pub json_storage: JsonStorage, }
impl Clone for Value {
fn clone(&self) -> Self {
unsafe { core::mem::transmute_copy(self) }
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
unsafe { write!(f, "Value(0x{:x})", self.u64) }
}
}
unsafe impl Send for Value {}
unsafe impl Sync for Value {}
unsafe impl Send for FieldDef {}
unsafe impl Sync for FieldDef {}
unsafe impl Send for TableDef {}
unsafe impl Sync for TableDef {}
pub struct TypedValue {
pub value_type: DataType,
pub value: Value,
}
impl Clone for TypedValue {
fn clone(&self) -> Self {
let mut new_value = Value {
u64: 0,
};
unsafe {
match self.value_type {
DataType::UInt8 => new_value.u8 = self.value.u8,
DataType::UInt16 => new_value.u16 = self.value.u16,
DataType::UInt32 => new_value.u32 = self.value.u32,
DataType::UInt64 => new_value.u64 = self.value.u64,
DataType::Int8 => new_value.i8 = self.value.i8,
DataType::Int16 => new_value.i16 = self.value.i16,
DataType::Int32 => new_value.i32 = self.value.i32,
DataType::Int64 => new_value.i64 = self.value.i64,
DataType::Float32 => new_value.float32 = self.value.float32,
DataType::Float64 => new_value.float64 = self.value.float64,
DataType::Bool => new_value.bool = self.value.bool,
DataType::Timestamp => new_value.time = self.value.time,
DataType::TimestampTZ => new_value.time = self.value.time,
DataType::Interval => new_value.interval = self.value.interval,
DataType::VarChar | DataType::Char | DataType::Text => {
new_value.string = self.value.string
}
DataType::Vector => {
new_value.vector = self.value.vector;
}
DataType::Json => {
new_value.json_storage = self.value.json_storage;
}
}
}
TypedValue {
value_type: self.value_type,
value: new_value,
}
}
}
impl PartialEq for TypedValue {
fn eq(&self, other: &Self) -> bool {
if self.value_type != other.value_type {
return false;
}
unsafe {
match self.value_type {
DataType::UInt8 => self.value.u8 == other.value.u8,
DataType::UInt16 => self.value.u16 == other.value.u16,
DataType::UInt32 => self.value.u32 == other.value.u32,
DataType::UInt64 => self.value.u64 == other.value.u64,
DataType::Int8 => self.value.i8 == other.value.i8,
DataType::Int16 => self.value.i16 == other.value.i16,
DataType::Int32 => self.value.i32 == other.value.i32,
DataType::Int64 => self.value.i64 == other.value.i64,
DataType::Float32 => {
let a = self.value.float32;
let b = other.value.float32;
if a.is_nan() && b.is_nan() {
true } else {
a == b
}
}
DataType::Float64 => {
let a = self.value.float64;
let b = other.value.float64;
if a.is_nan() && b.is_nan() {
true
} else {
a == b
}
}
DataType::Bool => self.value.bool == other.value.bool,
DataType::Timestamp => self.value.time.value == other.value.time.value,
DataType::TimestampTZ => {
self.value.time.value == other.value.time.value
&& self.value.time.tz_offset == other.value.time.tz_offset
}
DataType::Interval => self.value.interval.value == other.value.interval.value,
DataType::VarChar | DataType::Char | DataType::Text => {
let a_str = self.value.string.as_ref();
let b_str = other.value.string.as_ref();
get_global_utf8_processor().compare(a_str, b_str) == core::cmp::Ordering::Equal
}
DataType::Vector => {
self.value.vector == other.value.vector
}
DataType::Json => {
self.value.json_storage == other.value.json_storage
}
}
}
}
}
impl Eq for TypedValue {}
use core::hash::{Hash, Hasher};
impl Hash for TypedValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value_type.hash(state);
unsafe {
match self.value_type {
DataType::UInt8 => self.value.u8.hash(state),
DataType::UInt16 => self.value.u16.hash(state),
DataType::UInt32 => self.value.u32.hash(state),
DataType::UInt64 => self.value.u64.hash(state),
DataType::Int8 => self.value.i8.hash(state),
DataType::Int16 => self.value.i16.hash(state),
DataType::Int32 => self.value.i32.hash(state),
DataType::Int64 => self.value.i64.hash(state),
DataType::Float32 => {
let a = self.value.float32;
if a.is_nan() {
state.write_u32(0x7FC00000);
} else {
a.to_bits().hash(state);
}
}
DataType::Float64 => {
let a = self.value.float64;
if a.is_nan() {
state.write_u64(0x7FF8000000000000);
} else {
a.to_bits().hash(state);
}
}
DataType::Bool => self.value.bool.hash(state),
DataType::Timestamp => self.value.time.value.hash(state),
DataType::TimestampTZ => {
self.value.time.value.hash(state);
self.value.time.tz_offset.hash(state);
}
DataType::Interval => self.value.interval.value.hash(state),
DataType::VarChar | DataType::Char | DataType::Text => {
if let Some(s) = get_global_utf8_processor().to_string(&self.value.string) {
s.trim_end_matches(char::from(0)).hash(state);
} else {
self.value.string.hash(state);
}
}
DataType::Vector => {
self.value.vector.hash(state);
}
DataType::Json => {
self.value.json_storage.hash(state);
}
}
}
}
}
impl PartialOrd for TypedValue {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match self.value_type.cmp(&other.value_type) {
core::cmp::Ordering::Equal => {
unsafe {
match self.value_type {
DataType::UInt8 => Some(self.value.u8.cmp(&other.value.u8)),
DataType::UInt16 => Some(self.value.u16.cmp(&other.value.u16)),
DataType::UInt32 => Some(self.value.u32.cmp(&other.value.u32)),
DataType::UInt64 => Some(self.value.u64.cmp(&other.value.u64)),
DataType::Int8 => Some(self.value.i8.cmp(&other.value.i8)),
DataType::Int16 => Some(self.value.i16.cmp(&other.value.i16)),
DataType::Int32 => Some(self.value.i32.cmp(&other.value.i32)),
DataType::Int64 => Some(self.value.i64.cmp(&other.value.i64)),
DataType::Float32 => {
let a = self.value.float32;
let b = other.value.float32;
if a.is_nan() || b.is_nan() {
None } else {
Some(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))
}
}
DataType::Float64 => {
let a = self.value.float64;
let b = other.value.float64;
if a.is_nan() || b.is_nan() {
None } else {
Some(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))
}
}
DataType::Bool => Some(self.value.bool.cmp(&other.value.bool)),
DataType::Timestamp => {
Some(self.value.time.value.cmp(&other.value.time.value))
}
DataType::TimestampTZ => {
match self.value.time.value.cmp(&other.value.time.value) {
core::cmp::Ordering::Equal => {
Some(self.value.time.tz_offset.cmp(&other.value.time.tz_offset))
}
ordering => Some(ordering),
}
}
DataType::Interval => {
Some(self.value.interval.value.cmp(&other.value.interval.value))
}
DataType::VarChar | DataType::Char | DataType::Text => {
let a_str = self.value.string.as_ref();
let b_str = other.value.string.as_ref();
Some(get_global_utf8_processor().compare(a_str, b_str))
}
DataType::Vector => {
Some(self.value.vector.cmp(&other.value.vector))
}
DataType::Json => {
Some(core::cmp::Ordering::Equal)
}
}
}
}
ordering => Some(ordering),
}
}
}
impl Ord for TypedValue {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.partial_cmp(other).unwrap_or_else(|| {
core::cmp::Ordering::Equal
})
}
}
impl fmt::Debug for TypedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
unsafe {
match self.value_type {
DataType::UInt8 => write!(
f,
"TypedValue(UInt8, {})
",
self.value.u8
),
DataType::UInt16 => write!(
f,
"TypedValue(UInt16, {})
",
self.value.u16
),
DataType::UInt32 => write!(
f,
"TypedValue(UInt32, {})
",
self.value.u32
),
DataType::UInt64 => write!(
f,
"TypedValue(UInt64, {})
",
self.value.u64
),
DataType::Int8 => write!(
f,
"TypedValue(Int8, {})
",
self.value.i8
),
DataType::Int16 => write!(
f,
"TypedValue(Int16, {})
",
self.value.i16
),
DataType::Int32 => write!(
f,
"TypedValue(Int32, {})
",
self.value.i32
),
DataType::Int64 => write!(
f,
"TypedValue(Int64, {})
",
self.value.i64
),
DataType::Float32 => write!(
f,
"TypedValue(Float32, {})
",
self.value.float32
),
DataType::Float64 => write!(
f,
"TypedValue(Float64, {})
",
self.value.float64
),
DataType::Bool => write!(
f,
"TypedValue(Bool, {})
",
self.value.bool
),
DataType::Timestamp => {
write!(
f,
"TypedValue(Timestamp, value: {}, precision: {})
",
self.value.time.value, self.value.time.precision
)
}
DataType::TimestampTZ => {
write!(
f,
"TypedValue(TimestampTZ, value: {}, tz_offset: {}s, precision: {})
",
self.value.time.value, self.value.time.tz_offset, self.value.time.precision
)
}
DataType::VarChar | DataType::Char | DataType::Text => {
let s = get_global_utf8_processor()
.to_string(&self.value.string)
.unwrap_or("")
.trim_end_matches(char::from(0));
write!(
f,
"TypedValue({}, \"{}\")\n",
self.value_type.to_sql_type(0).as_str(),
s
)
}
DataType::Interval => {
write!(
f,
"TypedValue(Interval, value: {}, precision: {})
",
self.value.interval.value, self.value.interval.precision
)
}
DataType::Vector => {
write!(f, "TypedValue(Vector, pointer: {:?})", self.value.vector)
}
DataType::Json => {
write!(
f,
"TypedValue(Json, storage: {:?})",
self.value.json_storage
)
}
}
}
}
}
pub const MAX_STRING_LEN: usize = 64;
pub const MAX_TEXT_LEN: usize = 10240;
pub const DEFAULT_TEXT_SIZE: usize = 512;
pub const DEFAULT_JSON_SIZE: usize = 512;
#[derive(Clone, Debug)]
pub struct FieldDef {
pub name: String,
pub data_type: DataType,
pub size: usize,
pub string_length: Option<usize>,
pub offset: usize,
pub primary_key: bool,
pub not_null: bool,
pub unique: bool,
pub auto_increment: bool,
pub default_value: Option<Value>,
pub vector_metadata: Option<VectorMetadata>,
pub json_metadata: Option<JsonMetadata>,
}
impl Default for FieldDef {
fn default() -> Self {
Self {
name: String::new(),
data_type: DataType::Int32,
size: 0,
string_length: None,
offset: 0,
primary_key: false,
not_null: false,
unique: false,
auto_increment: false,
default_value: None,
vector_metadata: None,
json_metadata: None,
}
}
}
impl FieldDef {
pub fn constraints_to_sql(&self) -> alloc::string::String {
let mut constraints = alloc::string::String::new();
if self.primary_key {
constraints.push_str(" PRIMARY KEY");
}
if self.auto_increment {
constraints.push_str(" AUTO_INCREMENT");
}
if self.not_null {
constraints.push_str(" NOT NULL");
}
if self.unique && !self.primary_key {
constraints.push_str(" UNIQUE");
}
if let Some(default) = &self.default_value {
constraints.push_str(" DEFAULT ");
unsafe {
match self.data_type {
DataType::VarChar | DataType::Char | DataType::Text => {
let s = get_global_utf8_processor()
.to_string(&default.string)
.unwrap_or("")
.trim_end_matches(char::from(0));
constraints.push_str(&alloc::format!("'{}'", s));
}
DataType::Bool => {
let b = default.bool;
constraints.push_str(if b { "TRUE" } else { "FALSE" });
}
DataType::UInt8 => constraints.push_str(&default.u8.to_string()),
DataType::UInt16 => constraints.push_str(&default.u16.to_string()),
DataType::UInt32 => constraints.push_str(&default.u32.to_string()),
DataType::UInt64 => constraints.push_str(&default.u64.to_string()),
DataType::Int8 => constraints.push_str(&default.i8.to_string()),
DataType::Int16 => constraints.push_str(&default.i16.to_string()),
DataType::Int32 => constraints.push_str(&default.i32.to_string()),
DataType::Int64 => constraints.push_str(&default.i64.to_string()),
DataType::Float32 => constraints.push_str(&default.float32.to_string()),
DataType::Float64 => constraints.push_str(&default.float64.to_string()),
DataType::Timestamp => constraints.push_str(&default.timestamp.to_string()),
DataType::TimestampTZ => constraints.push_str(&default.timestamp.to_string()),
DataType::Interval => constraints.push_str(&default.interval.value.to_string()),
DataType::Vector => {
constraints.push_str("NULL"); }
DataType::Json => {
constraints.push_str("NULL"); }
}
}
}
if self.data_type == DataType::Vector {
if let Some(meta) = self.vector_metadata {
let distance_str = match meta.distance_type {
DistanceType::L2 => "L2",
DistanceType::InnerProduct => "INNER_PRODUCT",
DistanceType::Cosine => "COSINE",
};
constraints.push_str(&alloc::format!(" WITH DISTANCE={}", distance_str));
}
}
constraints
}
}
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
pub enum IndexType {
Hash = 0,
SortedArray = 1,
BTree = 2,
TTree = 3,
Vector = 4,
Json = 5,
}
impl From<u8> for IndexType {
fn from(value: u8) -> Self {
match value {
0 => IndexType::Hash,
1 => IndexType::SortedArray,
2 => IndexType::BTree,
3 => IndexType::TTree,
4 => IndexType::Vector,
5 => IndexType::Json,
_ => IndexType::SortedArray, }
}
}
#[derive(Clone, Debug)]
pub struct TableDef {
pub id: u8,
pub name: String,
pub fields: Vec<FieldDef>,
pub primary_key: Vec<usize>,
pub secondary_index: Option<Vec<usize>>,
pub secondary_index_type: IndexType,
pub record_size: usize,
pub max_records: usize,
pub version: u32,
pub created_at: u64,
pub updated_at: u64,
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum RecordStatus {
Free = 0,
Used = 1,
Deleted = 2,
}
#[repr(u8)]
#[derive(Copy, Clone)]
pub enum LockType {
None = 0,
Shared = 1,
Exclusive = 2,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct RecordHeader {
pub status: RecordStatus,
pub version: u16,
pub lock_type: LockType,
pub lock_owner: u32,
pub lock_count: u8,
pub create_tx_id: u32,
pub delete_tx_id: u32,
pub next_version_ptr: usize,
}
impl RecordHeader {
pub const SIZE: usize = size_of::<Self>();
}
const _: () = {
assert!(size_of::<RecordStatus>() == 1);
assert!(size_of::<DataType>() == 1);
};
#[derive(Debug, PartialEq, Eq)]
pub enum RemDbError {
OutOfMemory,
RecordNotFound,
DuplicateKey,
FieldNotFound,
TypeMismatch,
NotNullViolation,
TransactionError,
ConfigError,
InvalidConfig(String),
CompressionError,
NotAllowed,
TwoMoreIndexNotSupported,
UnsupportedOperation,
FileIoError,
DatabaseNotFound,
DatabaseExists,
DatabaseClosed,
MaxDatabasesReached,
SnapshotFormatError,
Crc32Error,
LogFormatError,
LogRecordNotFound,
LogChecksumError,
LockConflict,
LockTimeout,
TableNotFound,
InvalidRecordSize,
InvalidSqlQuery,
NoRecordsToOverwrite,
InvalidArgument,
InvalidState,
InternalError,
PlatformNotInitialized,
LockError,
InvalidPointer,
InvalidData(&'static str),
VariableSizeType,
ProtocolError(String),
UnexpectedNone(&'static str),
}
impl fmt::Display for RemDbError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RemDbError::OutOfMemory => write!(f, "Out of memory"),
RemDbError::RecordNotFound => write!(f, "Record not found"),
RemDbError::DuplicateKey => write!(f, "Duplicate key"),
RemDbError::FieldNotFound => write!(f, "Field not found"),
RemDbError::TypeMismatch => write!(f, "Type mismatch"),
RemDbError::NotNullViolation => write!(f, "NOT NULL constraint violation"),
RemDbError::TransactionError => write!(f, "Transaction error"),
RemDbError::ConfigError => write!(f, "Config error"),
RemDbError::InvalidConfig(msg) => write!(f, "Invalid config: {}", msg),
RemDbError::CompressionError => write!(f, "Compression error"),
RemDbError::NotAllowed => write!(f, "Operation not allowed"),
RemDbError::TwoMoreIndexNotSupported => write!(f, "Two more index not supported"),
RemDbError::UnsupportedOperation => write!(f, "Unsupported operation"),
RemDbError::FileIoError => write!(f, "File I/O error"),
RemDbError::SnapshotFormatError => write!(f, "Snapshot format error"),
RemDbError::Crc32Error => write!(f, "CRC32 checksum error"),
RemDbError::LogFormatError => write!(f, "Log format error"),
RemDbError::LogRecordNotFound => write!(f, "Log record not found"),
RemDbError::LogChecksumError => write!(f, "Log checksum error"),
RemDbError::LockConflict => write!(f, "Lock conflict"),
RemDbError::LockTimeout => write!(f, "Lock timeout"),
RemDbError::TableNotFound => write!(f, "Table not found"),
RemDbError::InvalidRecordSize => write!(f, "Invalid record size"),
RemDbError::InvalidSqlQuery => write!(f, "Invalid SQL query"),
RemDbError::NoRecordsToOverwrite => write!(f, "No records to overwrite"),
RemDbError::InvalidArgument => write!(f, "Invalid argument"),
RemDbError::InvalidState => write!(f, "Invalid state"),
RemDbError::DatabaseNotFound => write!(f, "Database not found"),
RemDbError::DatabaseExists => write!(f, "Database exists"),
RemDbError::DatabaseClosed => write!(f, "Database closed"),
RemDbError::MaxDatabasesReached => write!(f, "Maximum databases reached"),
RemDbError::InternalError => write!(f, "Internal error"),
RemDbError::PlatformNotInitialized => write!(f, "Platform not initialized"),
RemDbError::LockError => write!(f, "Lock error"),
RemDbError::InvalidPointer => write!(f, "Invalid pointer"),
RemDbError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
RemDbError::VariableSizeType => write!(f, "Variable size type"),
RemDbError::ProtocolError(msg) => write!(f, "Protocol error: {}", msg),
RemDbError::UnexpectedNone(msg) => write!(f, "Unexpected None: {}", msg),
}
}
}
pub type Result<T> = core::result::Result<T, RemDbError>;