1use rudb_common::{Field, LogicalType};
51
52pub type Signature = &'static [(&'static str, &'static str)];
54
55#[derive(Debug, Clone, Copy)]
57pub struct TypeEntry {
58 pub name: &'static str,
60 pub logical_type: &'static str,
62 pub oid: Option<i64>,
64 pub varargs: Option<&'static str>,
66 pub signatures: &'static [Signature],
68}
69
70const BARE: &[Signature] = &[&[]];
72
73const STRING: &[Signature] = &[&[], &[("length", "BIGINT")], &[("collation", "VARCHAR")]];
75
76const LENGTH: &[Signature] = &[&[], &[("length", "BIGINT")]];
78
79const WIDTH_SCALE: &[Signature] = &[&[], &[("width", "UTINYINT"), ("scale", "UTINYINT")]];
81
82const PRECISION: &[Signature] = &[&[], &[("precision", "UTINYINT")]];
84
85pub static TYPE_NAMES: &[TypeEntry] = &[
90 entry("bigint", "BIGINT", Some(14)),
91 entry("binary", "BLOB", Some(26)),
92 TypeEntry { signatures: LENGTH, oid: Some(36), ..entry("bit", "BIT", None) },
93 TypeEntry { signatures: LENGTH, ..entry("bitstring", "BIT", None) },
94 entry("blob", "BLOB", None),
95 entry("bool", "BOOLEAN", Some(10)),
96 entry("boolean", "BOOLEAN", None),
97 TypeEntry { signatures: STRING, oid: Some(25), ..entry("bpchar", "VARCHAR", None) },
98 entry("bytea", "BLOB", None),
99 TypeEntry { signatures: STRING, ..entry("char", "VARCHAR", None) },
100 entry("date", "DATE", Some(15)),
101 TypeEntry { signatures: PRECISION, oid: Some(19), ..entry("datetime", "TIMESTAMP", None) },
102 TypeEntry { signatures: WIDTH_SCALE, oid: Some(21), ..entry("dec", "DECIMAL", None) },
103 TypeEntry { signatures: WIDTH_SCALE, ..entry("decimal", "DECIMAL", None) },
104 entry("double", "DOUBLE", Some(23)),
105 entry("float", "FLOAT", Some(22)),
106 entry("float4", "FLOAT", None),
107 entry("float8", "DOUBLE", None),
108 entry("guid", "UUID", Some(54)),
109 entry("hugeint", "HUGEINT", Some(50)),
110 entry("int", "INTEGER", Some(13)),
111 entry("int1", "TINYINT", Some(11)),
112 entry("int128", "HUGEINT", None),
113 entry("int16", "SMALLINT", Some(12)),
114 entry("int2", "SMALLINT", None),
115 entry("int32", "INTEGER", None),
116 entry("int4", "INTEGER", None),
117 entry("int64", "BIGINT", None),
118 entry("int8", "BIGINT", None),
119 entry("integer", "INTEGER", None),
120 entry("integral", "INTEGER", None),
121 TypeEntry { signatures: PRECISION, oid: Some(27), ..entry("interval", "INTERVAL", None) },
122 TypeEntry {
123 signatures: &[&[("child", "TYPE")]],
124 oid: Some(101),
125 ..entry("list", "LIST", None)
126 },
127 entry("logical", "BOOLEAN", None),
128 entry("long", "BIGINT", None),
129 TypeEntry {
130 signatures: &[&[("key", "TYPE"), ("value", "TYPE")]],
131 oid: Some(102),
132 ..entry("map", "MAP", None)
133 },
134 entry("null", "NULL", Some(1)),
135 TypeEntry { signatures: WIDTH_SCALE, ..entry("numeric", "DECIMAL", None) },
136 TypeEntry { signatures: STRING, ..entry("nvarchar", "VARCHAR", None) },
137 entry("oid", "BIGINT", None),
138 entry("real", "FLOAT", None),
139 TypeEntry { varargs: Some("TYPE"), oid: Some(100), ..entry("row", "STRUCT", None) },
140 entry("short", "SMALLINT", None),
141 entry("signed", "INTEGER", None),
142 entry("smallint", "SMALLINT", None),
143 TypeEntry { signatures: STRING, ..entry("string", "VARCHAR", None) },
144 TypeEntry { varargs: Some("TYPE"), ..entry("struct", "STRUCT", None) },
145 TypeEntry { signatures: STRING, ..entry("text", "VARCHAR", None) },
146 entry("time", "TIME", Some(16)),
147 entry("time with time zone", "TIME WITH TIME ZONE", Some(34)),
148 TypeEntry { signatures: PRECISION, ..entry("timestamp", "TIMESTAMP", None) },
149 entry("timestamp with time zone", "TIMESTAMP WITH TIME ZONE", Some(32)),
150 entry("timestamptz", "TIMESTAMP WITH TIME ZONE", None),
151 entry("timestamp_ms", "TIMESTAMP_MS", Some(18)),
152 entry("timestamp_ns", "TIMESTAMP_NS", Some(20)),
153 entry("timestamp_s", "TIMESTAMP_S", Some(17)),
154 entry("timestamp_us", "TIMESTAMP", None),
155 entry("timetz", "TIME WITH TIME ZONE", None),
156 entry("tinyint", "TINYINT", None),
157 entry("ubigint", "UBIGINT", Some(31)),
158 entry("uhugeint", "UHUGEINT", Some(49)),
159 entry("uint128", "UHUGEINT", None),
160 entry("uint16", "USMALLINT", Some(29)),
161 entry("uint32", "UINTEGER", Some(30)),
162 entry("uint64", "UBIGINT", None),
163 entry("uint8", "UTINYINT", Some(28)),
164 entry("uinteger", "UINTEGER", None),
165 TypeEntry { varargs: Some("TYPE"), oid: Some(107), ..entry("union", "UNION", None) },
166 entry("usmallint", "USMALLINT", None),
167 entry("utinyint", "UTINYINT", None),
168 entry("uuid", "UUID", None),
169 entry("varbinary", "BLOB", None),
170 TypeEntry { signatures: STRING, ..entry("varchar", "VARCHAR", None) },
171];
172
173const fn entry(name: &'static str, logical_type: &'static str, oid: Option<i64>) -> TypeEntry {
176 TypeEntry { name, logical_type, oid, varargs: None, signatures: BARE }
177}
178
179#[must_use]
181pub fn type_fields() -> Vec<Field> {
182 vec![
183 Field::new("database_name", LogicalType::Varchar),
184 Field::new("database_oid", LogicalType::BigInt),
185 Field::new("schema_name", LogicalType::Varchar),
186 Field::new("schema_oid", LogicalType::BigInt),
187 Field::new("type_oid", LogicalType::BigInt),
188 Field::new("type_name", LogicalType::Varchar),
189 Field::new("type_size", LogicalType::BigInt),
190 Field::new("logical_type", LogicalType::Varchar),
191 Field::new("type_category", LogicalType::Varchar),
192 Field::new("comment", LogicalType::Varchar),
193 Field::new("tags", LogicalType::map(LogicalType::Varchar, LogicalType::Varchar)),
194 Field::new("internal", LogicalType::Boolean),
195 Field::new("extension_name", LogicalType::Varchar),
196 Field::new("labels", LogicalType::list(LogicalType::Varchar)),
197 Field::new("parameters", LogicalType::list(LogicalType::Varchar)),
198 Field::new("parameter_types", LogicalType::list(LogicalType::Varchar)),
199 Field::new("varargs", LogicalType::Varchar),
200 ]
201}
202
203#[must_use]
211pub fn representative(logical_type: &str) -> Option<LogicalType> {
212 Some(match logical_type {
213 "NULL" => LogicalType::Null,
214 "BOOLEAN" => LogicalType::Boolean,
215 "TINYINT" => LogicalType::TinyInt,
216 "SMALLINT" => LogicalType::SmallInt,
217 "INTEGER" => LogicalType::Integer,
218 "BIGINT" => LogicalType::BigInt,
219 "HUGEINT" => LogicalType::HugeInt,
220 "UTINYINT" => LogicalType::UTinyInt,
221 "USMALLINT" => LogicalType::USmallInt,
222 "UINTEGER" => LogicalType::UInteger,
223 "UBIGINT" => LogicalType::UBigInt,
224 "UHUGEINT" => LogicalType::UHugeInt,
225 "FLOAT" => LogicalType::Float,
226 "DOUBLE" => LogicalType::Double,
227 "DECIMAL" => LogicalType::Decimal { width: 18, scale: 3 },
228 "VARCHAR" => LogicalType::Varchar,
229 "BLOB" => LogicalType::Blob,
230 "BIT" => LogicalType::Bit,
231 "UUID" => LogicalType::Uuid,
232 "DATE" => LogicalType::Date,
233 "TIME" => LogicalType::Time,
234 "TIME WITH TIME ZONE" => LogicalType::TimeTz,
235 "TIMESTAMP" => LogicalType::Timestamp,
236 "TIMESTAMP_S" => LogicalType::TimestampS,
237 "TIMESTAMP_MS" => LogicalType::TimestampMs,
238 "TIMESTAMP_NS" => LogicalType::TimestampNs,
239 "TIMESTAMP WITH TIME ZONE" => LogicalType::TimestampTz,
240 "INTERVAL" => LogicalType::Interval,
241 "LIST" => LogicalType::list(LogicalType::Integer),
242 "MAP" => LogicalType::map(LogicalType::Varchar, LogicalType::Varchar),
243 "STRUCT" => LogicalType::Struct(Vec::new()),
244 "UNION" => LogicalType::Union(Vec::new()),
245 _ => return None,
246 })
247}
248
249#[must_use]
257pub fn type_oid(logical_type: &str) -> Option<i64> {
258 TYPE_NAMES
259 .iter()
260 .find(|entry| entry.logical_type == logical_type && entry.oid.is_some())
261 .and_then(|entry| entry.oid)
262}
263
264#[must_use]
269pub fn type_size(logical_type: &str) -> Option<i64> {
270 if logical_type == "DECIMAL" {
271 return None;
272 }
273 let ty = representative(logical_type)?;
274 i64::try_from(ty.physical().size()).ok()
275}
276
277#[must_use]
283pub fn type_category(logical_type: &str) -> Option<&'static str> {
284 let ty = representative(logical_type)?;
285 Some(match ty {
286 LogicalType::Boolean => "BOOLEAN",
287 LogicalType::Varchar => "STRING",
288 LogicalType::TinyInt
289 | LogicalType::SmallInt
290 | LogicalType::Integer
291 | LogicalType::BigInt
292 | LogicalType::HugeInt
293 | LogicalType::UTinyInt
294 | LogicalType::USmallInt
295 | LogicalType::UInteger
296 | LogicalType::UBigInt
297 | LogicalType::UHugeInt
298 | LogicalType::Float
299 | LogicalType::Double
300 | LogicalType::Decimal { .. } => "NUMERIC",
301 LogicalType::Date
302 | LogicalType::Time
303 | LogicalType::TimeTz
304 | LogicalType::Timestamp
305 | LogicalType::TimestampS
306 | LogicalType::TimestampMs
307 | LogicalType::TimestampNs
308 | LogicalType::TimestampTz
309 | LogicalType::Interval => "DATETIME",
310 LogicalType::List(_)
311 | LogicalType::Array(_, _)
312 | LogicalType::Map(_, _)
313 | LogicalType::Struct(_)
314 | LogicalType::Union(_) => "COMPOSITE",
315 _ => return None,
316 })
317}
318
319#[must_use]
325pub fn sort_key(name: &str) -> String {
326 name.to_ascii_lowercase().replace('_', "{")
327}
328
329#[cfg(test)]
330mod tests {
331 use rudb_common::LogicalType;
332
333 use super::{TYPE_NAMES, sort_key, type_category, type_fields, type_size};
334
335 #[test]
336 fn the_table_is_the_shape_the_pin_returns() {
337 let rows: usize = TYPE_NAMES.iter().map(|entry| entry.signatures.len()).sum();
338 assert_eq!(TYPE_NAMES.len(), 73, "names");
339 assert_eq!(rows, 93, "rows, which is the pin's 104 less the eleven for types we lack");
340 assert_eq!(type_fields().len(), 17);
341 }
342
343 #[test]
346 fn the_oid_sits_on_the_first_name_of_its_type() {
347 for entry in TYPE_NAMES {
348 let first = TYPE_NAMES
349 .iter()
350 .filter(|other| other.logical_type == entry.logical_type)
351 .min_by_key(|other| sort_key(other.name))
352 .expect("at least itself");
353 let expected = entry.name == first.name;
354 assert_eq!(
355 entry.oid.is_some(),
356 expected,
357 "{} carries an oid and {} is the first name of {}",
358 entry.name,
359 first.name,
360 entry.logical_type
361 );
362 }
363 let mut oids: Vec<i64> = TYPE_NAMES.iter().filter_map(|entry| entry.oid).collect();
365 oids.sort_unstable();
366 let total = oids.len();
367 oids.dedup();
368 assert_eq!(oids.len(), total, "two names claim the same oid");
369 assert_eq!(total, 32, "one oid per type this engine has");
370 }
371
372 #[test]
373 fn the_names_are_in_the_order_the_pin_returns_them() {
374 let mut sorted: Vec<&str> = TYPE_NAMES.iter().map(|entry| entry.name).collect();
375 sorted.sort_by_key(|name| sort_key(name));
376 let listed: Vec<&str> = TYPE_NAMES.iter().map(|entry| entry.name).collect();
377 assert_eq!(listed, sorted);
378 assert!(sort_key("timestamptz_ns") < sort_key("timestamp_ms"));
380 assert!(sort_key("timetz") < sort_key("time_ns"));
381 assert!(sort_key("time with time zone") < sort_key("timestamp"));
382 }
383
384 #[test]
387 fn every_name_here_is_a_name_a_cast_can_spell() {
388 for entry in TYPE_NAMES {
389 if entry.name == "list" {
390 assert!(LogicalType::parse("list").is_err(), "the pin refuses this too");
391 continue;
392 }
393 let spelled = match entry.logical_type {
394 "DECIMAL" => format!("{}(9, 2)", entry.name),
395 "MAP" => format!("{}(VARCHAR, VARCHAR)", entry.name),
396 "STRUCT" | "UNION" => format!("{}(a INTEGER)", entry.name),
397 _ => entry.name.to_string(),
398 };
399 let parsed = LogicalType::parse(&spelled)
400 .unwrap_or_else(|e| panic!("{} does not parse: {e}", entry.name));
401 let canonical = entry.logical_type.to_string();
402 let got = parsed.to_string().replace('"', "");
406 assert!(
407 got == canonical || got.starts_with(&canonical),
408 "{} parses to {got} and the table says {canonical}",
409 entry.name
410 );
411 }
412 }
413
414 #[test]
417 fn the_sizes_are_this_engines_and_three_of_them_differ_from_the_pin() {
418 assert_eq!(type_size("INTEGER"), Some(4));
419 assert_eq!(type_size("VARCHAR"), Some(16));
420 assert_eq!(type_size("HUGEINT"), Some(16));
421 assert_eq!(type_size("DECIMAL"), None, "the width decides, so there is no one answer");
422 assert_eq!(type_size("STRUCT"), Some(0), "the parent holds nothing of its own");
423 assert_eq!(type_size("LIST"), Some(8), "the pin says 16");
425 assert_eq!(type_size("MAP"), Some(8), "the pin says 16");
426 assert_eq!(type_size("NULL"), Some(0), "the pin says 4");
428 }
429
430 #[test]
431 fn four_types_are_in_no_category_and_that_is_the_pins_answer() {
432 assert_eq!(type_category("BIGINT"), Some("NUMERIC"));
433 assert_eq!(type_category("DECIMAL"), Some("NUMERIC"));
434 assert_eq!(type_category("VARCHAR"), Some("STRING"));
435 assert_eq!(type_category("BOOLEAN"), Some("BOOLEAN"));
436 assert_eq!(type_category("INTERVAL"), Some("DATETIME"));
437 assert_eq!(type_category("MAP"), Some("COMPOSITE"));
438 for uncategorised in ["NULL", "BIT", "BLOB", "UUID"] {
439 assert_eq!(type_category(uncategorised), None, "{uncategorised}");
440 }
441 }
442
443 #[test]
446 fn two_names_for_one_type_can_take_different_modifiers() {
447 let of = |name: &str| {
448 TYPE_NAMES.iter().find(|entry| entry.name == name).expect("a name in the table")
449 };
450 assert_eq!(of("timestamp").signatures.len(), 2, "timestamp takes a precision");
451 assert_eq!(of("timestamp_us").signatures.len(), 1, "the same type, and it does not");
452 assert_eq!(of("varchar").signatures.len(), 3);
453 assert_eq!(of("blob").signatures.len(), 1);
454 assert_eq!(of("bpchar").signatures[1], [("length", "BIGINT")]);
456 assert_eq!(of("bpchar").signatures[2], [("collation", "VARCHAR")]);
457 }
458}