Skip to main content

entdb_server/server/
type_map.rs

1/*
2 * Copyright 2026 EntDB Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use entdb::error::{EntDbError, Result};
18use entdb::types::{DataType, Value};
19use pgwire::api::results::{DataRowEncoder, FieldFormat};
20use pgwire::api::Type;
21use pgwire::error::PgWireResult;
22
23pub fn ent_to_pg_type(dt: &DataType) -> Type {
24    match dt {
25        DataType::Boolean => Type::BOOL,
26        DataType::Int16 => Type::INT2,
27        DataType::Int32 => Type::INT4,
28        DataType::Int64 => Type::INT8,
29        DataType::Float32 => Type::FLOAT4,
30        DataType::Float64 => Type::FLOAT8,
31        DataType::Text => Type::TEXT,
32        DataType::Varchar(_) => Type::VARCHAR,
33        DataType::Timestamp => Type::TIMESTAMP,
34        DataType::Null => Type::UNKNOWN,
35    }
36}
37
38pub fn value_to_text(val: &Value) -> Option<String> {
39    match val {
40        Value::Null => None,
41        Value::Boolean(v) => Some(if *v { "t".to_string() } else { "f".to_string() }),
42        Value::Int16(v) => Some(v.to_string()),
43        Value::Int32(v) => Some(v.to_string()),
44        Value::Int64(v) => Some(v.to_string()),
45        Value::Float32(v) => Some(v.to_string()),
46        Value::Float64(v) => Some(v.to_string()),
47        Value::Text(v) => Some(v.clone()),
48        Value::Timestamp(v) => Some(v.to_string()),
49    }
50}
51
52pub fn value_to_binary(val: &Value) -> Option<Vec<u8>> {
53    match val {
54        Value::Null => None,
55        Value::Boolean(v) => Some(vec![u8::from(*v)]),
56        Value::Int16(v) => Some(v.to_be_bytes().to_vec()),
57        Value::Int32(v) => Some(v.to_be_bytes().to_vec()),
58        Value::Int64(v) => Some(v.to_be_bytes().to_vec()),
59        Value::Float32(v) => Some(v.to_be_bytes().to_vec()),
60        Value::Float64(v) => Some(v.to_be_bytes().to_vec()),
61        Value::Text(v) => Some(v.as_bytes().to_vec()),
62        Value::Timestamp(v) => Some(v.to_be_bytes().to_vec()),
63    }
64}
65
66pub fn text_to_value(text: &str, dt: &DataType) -> Result<Value> {
67    match dt {
68        DataType::Boolean => text
69            .parse::<bool>()
70            .map(Value::Boolean)
71            .map_err(|e| EntDbError::Query(format!("invalid BOOL literal: {e}"))),
72        DataType::Int16 => text
73            .parse::<i16>()
74            .map(Value::Int16)
75            .map_err(|e| EntDbError::Query(format!("invalid INT2 literal: {e}"))),
76        DataType::Int32 => text
77            .parse::<i32>()
78            .map(Value::Int32)
79            .map_err(|e| EntDbError::Query(format!("invalid INT4 literal: {e}"))),
80        DataType::Int64 => text
81            .parse::<i64>()
82            .map(Value::Int64)
83            .map_err(|e| EntDbError::Query(format!("invalid INT8 literal: {e}"))),
84        DataType::Float32 => text
85            .parse::<f32>()
86            .map(Value::Float32)
87            .map_err(|e| EntDbError::Query(format!("invalid FLOAT4 literal: {e}"))),
88        DataType::Float64 => text
89            .parse::<f64>()
90            .map(Value::Float64)
91            .map_err(|e| EntDbError::Query(format!("invalid FLOAT8 literal: {e}"))),
92        DataType::Text | DataType::Varchar(_) => Ok(Value::Text(text.to_string())),
93        DataType::Timestamp => text
94            .parse::<i64>()
95            .map(Value::Timestamp)
96            .map_err(|e| EntDbError::Query(format!("invalid TIMESTAMP literal: {e}"))),
97        DataType::Null => Ok(Value::Null),
98    }
99}
100
101pub fn encode_value(
102    encoder: &mut DataRowEncoder,
103    value: &Value,
104    data_type: &Type,
105    format: FieldFormat,
106) -> PgWireResult<()> {
107    match value {
108        Value::Null => encoder.encode_field_with_type_and_format(&None::<i32>, data_type, format),
109        Value::Boolean(v) => {
110            encoder.encode_field_with_type_and_format(&Some(*v), data_type, format)
111        }
112        Value::Int16(v) => encoder.encode_field_with_type_and_format(&Some(*v), data_type, format),
113        Value::Int32(v) => encoder.encode_field_with_type_and_format(&Some(*v), data_type, format),
114        Value::Int64(v) => encoder.encode_field_with_type_and_format(&Some(*v), data_type, format),
115        Value::Float32(v) => {
116            encoder.encode_field_with_type_and_format(&Some(*v), data_type, format)
117        }
118        Value::Float64(v) => {
119            encoder.encode_field_with_type_and_format(&Some(*v), data_type, format)
120        }
121        Value::Text(v) => {
122            encoder.encode_field_with_type_and_format(&Some(v.as_str()), data_type, format)
123        }
124        Value::Timestamp(v) => {
125            let text = v.to_string();
126            encoder.encode_field_with_type_and_format(&Some(text), data_type, format)
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::{ent_to_pg_type, text_to_value, value_to_binary, value_to_text};
134    use entdb::types::{DataType, Value};
135    use pgwire::api::Type;
136
137    #[test]
138    fn data_type_mapping_covers_supported_set() {
139        assert_eq!(ent_to_pg_type(&DataType::Boolean), Type::BOOL);
140        assert_eq!(ent_to_pg_type(&DataType::Int16), Type::INT2);
141        assert_eq!(ent_to_pg_type(&DataType::Int32), Type::INT4);
142        assert_eq!(ent_to_pg_type(&DataType::Int64), Type::INT8);
143        assert_eq!(ent_to_pg_type(&DataType::Float32), Type::FLOAT4);
144        assert_eq!(ent_to_pg_type(&DataType::Float64), Type::FLOAT8);
145        assert_eq!(ent_to_pg_type(&DataType::Text), Type::TEXT);
146        assert_eq!(ent_to_pg_type(&DataType::Varchar(64)), Type::VARCHAR);
147        assert_eq!(ent_to_pg_type(&DataType::Timestamp), Type::TIMESTAMP);
148    }
149
150    #[test]
151    fn text_value_round_trip_for_core_types() {
152        let cases = vec![
153            ("true", DataType::Boolean, Value::Boolean(true)),
154            ("12", DataType::Int16, Value::Int16(12)),
155            ("123", DataType::Int32, Value::Int32(123)),
156            ("1234", DataType::Int64, Value::Int64(1234)),
157            ("1.5", DataType::Float64, Value::Float64(1.5)),
158            ("ent", DataType::Text, Value::Text("ent".to_string())),
159            ("99", DataType::Timestamp, Value::Timestamp(99)),
160        ];
161
162        for (input, dt, expected) in cases {
163            let parsed = text_to_value(input, &dt).expect("parse value");
164            assert_eq!(parsed, expected);
165            assert_eq!(value_to_text(&parsed), value_to_text(&expected));
166        }
167    }
168
169    #[test]
170    fn binary_encoding_returns_none_for_null() {
171        assert!(value_to_binary(&Value::Null).is_none());
172        assert_eq!(value_to_binary(&Value::Int32(7)).expect("bytes").len(), 4);
173    }
174}