1use std::sync::Arc;
48
49use serde_json::{json, Map, Value};
50
51use crate::db::Db;
52
53const OWNER_OID: i64 = 10;
59
60const PUBLIC_NS_OID: i64 = 2200; const CATALOG_NS_OID: i64 = 11;
64const INFO_NS_OID: i64 = 13000;
65
66const COLUMN_SAMPLE: usize = 200;
73
74pub fn is_catalog(table: &str) -> bool {
81 matches!(
82 table,
83 "pg_class" | "pg_namespace" | "pg_attribute" | "pg_type" | "pg_database"
84 | "pg_am" | "pg_roles" | "pg_user" | "pg_settings" | "pg_index"
85 | "pg_description" | "pg_constraint" | "pg_tablespace"
86 | "information_schema.tables"
87 | "information_schema.columns"
88 | "information_schema.schemata"
89 | "information_schema.key_column_usage"
90 | "information_schema.table_constraints"
91 ) || EMPTY_CATALOG.contains(&table)
92}
93
94const EMPTY_CATALOG: &[&str] = &[
105 "pg_policy", "pg_attrdef", "pg_collation", "pg_inherits", "pg_publication",
106 "pg_publication_rel", "pg_publication_namespace", "pg_subscription",
107 "pg_subscription_rel", "pg_largeobject_metadata", "pg_statistic_ext",
108 "pg_statistic_ext_data", "pg_trigger", "pg_rewrite", "pg_event_trigger",
109 "pg_enum", "pg_range", "pg_proc", "pg_aggregate", "pg_language",
110 "pg_operator", "pg_opclass", "pg_opfamily", "pg_amop", "pg_amproc", "pg_cast",
111 "pg_conversion", "pg_extension", "pg_available_extensions",
112 "pg_available_extension_versions", "pg_foreign_data_wrapper",
113 "pg_foreign_server", "pg_foreign_table", "pg_user_mapping", "pg_user_mappings",
114 "pg_default_acl", "pg_partitioned_table", "pg_ts_config", "pg_ts_config_map",
115 "pg_ts_dict", "pg_ts_parser", "pg_ts_template", "pg_seclabel", "pg_shdescription",
116 "pg_auth_members", "pg_shseclabel", "pg_replication_origin", "pg_sequence",
117 "pg_stat_user_tables", "pg_stat_all_tables", "pg_stats", "pg_statistic",
118 "pg_depend", "pg_shdepend", "pg_init_privs", "pg_parameter_acl",
119 "pg_transform", "pg_group", "pg_shadow", "pg_locks", "pg_stat_activity",
120 "pg_prepared_statements", "pg_cursors", "pg_timezone_names", "pg_timezone_abbrevs",
121 "information_schema.views", "information_schema.routines",
122 "information_schema.sequences", "information_schema.referential_constraints",
123 "information_schema.constraint_column_usage", "information_schema.triggers",
124 "information_schema.domains", "information_schema.column_privileges",
125 "information_schema.table_privileges", "information_schema.check_constraints",
126];
127
128fn oid_for(name: &str) -> i64 {
137 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
138 for b in name.as_bytes() {
139 h ^= *b as u64;
140 h = h.wrapping_mul(0x100_0000_01b3);
141 }
142 16_384 + (h % 2_000_000_000) as i64
145}
146
147fn collections(db: Option<&Arc<Db>>) -> Vec<String> {
149 let mut out = match db {
150 Some(db) => db.id_index.collections(),
151 None => vec![],
152 };
153 out.retain(|c| !c.starts_with("__") && !c.is_empty());
156 out.sort();
157 out
158}
159
160fn columns_of(db: Option<&Arc<Db>>, coll: &str) -> Vec<(String, i32)> {
166 let db = match db {
167 Some(db) => db,
168 None => return vec![],
169 };
170 let rows = match crate::nql::query(db, &format!("FROM {} LIMIT {}", coll, COLUMN_SAMPLE)) {
171 Ok((rows, _)) => rows,
172 Err(_) => return vec![],
173 };
174 let mut names: Vec<String> = vec![];
175 for r in &rows {
176 if let Value::Object(m) = r {
177 for k in m.keys() {
178 if !names.iter().any(|n| n == k) {
179 names.push(k.clone());
180 }
181 }
182 }
183 }
184 names.sort();
185 names
186 .into_iter()
187 .map(|n| {
188 let oid = crate::pgwire::oid_for_column(&rows, &n);
189 (n, oid)
190 })
191 .collect()
192}
193
194pub fn type_name_pub(oid: i32) -> &'static str {
199 type_name(oid)
200}
201
202fn type_name(oid: i32) -> &'static str {
203 match oid {
204 16 => "boolean",
205 20 => "bigint",
206 21 => "smallint",
207 23 => "integer",
208 700 => "real",
209 701 => "double precision",
210 1043 => "character varying",
211 _ => "text",
212 }
213}
214
215fn row(pairs: Vec<(&str, Value)>) -> Value {
216 let mut m = Map::new();
217 for (k, v) in pairs {
218 m.insert(k.to_string(), v);
219 }
220 Value::Object(m)
221}
222
223pub fn rows(table: &str, db: Option<&Arc<Db>>) -> Option<Vec<Value>> {
229 let colls = collections(db);
230
231 Some(match table {
232 "pg_namespace" | "information_schema.schemata" => {
236 let is_info = table.starts_with("information_schema");
237 [("public", PUBLIC_NS_OID), ("pg_catalog", CATALOG_NS_OID),
238 ("information_schema", INFO_NS_OID)]
239 .iter()
240 .map(|(name, oid)| {
241 if is_info {
242 row(vec![
243 ("catalog_name", json!("nedb")),
244 ("schema_name", json!(name)),
245 ("schema_owner", json!("nedb")),
246 ("default_character_set_catalog", Value::Null),
247 ("default_character_set_schema", Value::Null),
248 ("default_character_set_name", Value::Null),
249 ("sql_path", Value::Null),
250 ])
251 } else {
252 row(vec![
253 ("oid", json!(oid)),
254 ("nspname", json!(name)),
255 ("nspowner", json!(OWNER_OID)),
256 ("nspacl", Value::Null),
257 ])
258 }
259 })
260 .collect()
261 }
262
263 "pg_class" => colls
265 .iter()
266 .map(|c| {
267 row(vec![
268 ("oid", json!(oid_for(c))),
269 ("relname", json!(c)),
270 ("relnamespace", json!(PUBLIC_NS_OID)),
271 ("relkind", json!("r")),
274 ("relowner", json!(OWNER_OID)),
275 ("relam", json!(2)), ("reltuples", json!(-1.0)), ("relpages", json!(0)),
278 ("relhasindex", json!(false)),
279 ("relpersistence", json!("p")),
280 ("reltablespace", json!(0)),
281 ("relispartition", json!(false)),
282 ("reltoastrelid", json!(0)),
283 ("relnatts", json!(columns_of(db, c).len() as i64)),
284 ("relacl", Value::Null),
289 ("relchecks", json!(0)),
290 ("relhasrules", json!(false)),
291 ("relhastriggers", json!(false)),
292 ("relhassubclass", json!(false)),
293 ("relrowsecurity", json!(false)),
294 ("relforcerowsecurity", json!(false)),
295 ("relispopulated", json!(true)),
296 ("relreplident", json!("d")),
297 ("reloftype", json!(0)),
298 ("relpartbound", Value::Null),
299 ("reloptions", Value::Null),
300 ("relfilenode", json!(oid_for(c))),
301 ("reltype", json!(0)),
302 ("relofoid", json!(0)),
303 ("tableoid", json!(1259)),
305 ])
306 })
307 .collect(),
308
309 "pg_attribute" => colls
311 .iter()
312 .flat_map(|c| {
313 let rel = oid_for(c);
314 columns_of(db, c)
315 .into_iter()
316 .enumerate()
317 .map(move |(i, (name, oid))| {
318 row(vec![
319 ("attrelid", json!(rel)),
320 ("attname", json!(name)),
321 ("attnum", json!(i as i64 + 1)),
323 ("atttypid", json!(oid as i64)),
324 ("attlen", json!(-1)),
325 ("atttypmod", json!(-1)),
326 ("attnotnull", json!(false)),
329 ("atthasdef", json!(false)),
330 ("attisdropped", json!(false)),
331 ("attidentity", json!("")),
332 ("attgenerated", json!("")),
333 ("attacl", Value::Null),
334 ("attcollation", json!(0)),
335 ("attstattarget", Value::Null),
336 ("attstorage", json!("x")),
337 ("attcompression", json!("")),
338 ("attfdwoptions", Value::Null),
339 ("attoptions", Value::Null),
340 ("attndims", json!(0)),
341 ("attbyval", json!(false)),
342 ("attalign", json!("i")),
343 ("atthasmissing", json!(false)),
344 ("attislocal", json!(true)),
345 ("attinhcount", json!(0)),
346 ])
347 })
348 .collect::<Vec<_>>()
349 })
350 .collect(),
351
352 "information_schema.tables" => colls
355 .iter()
356 .map(|c| {
357 row(vec![
358 ("table_catalog", json!("nedb")),
359 ("table_schema", json!("public")),
360 ("table_name", json!(c)),
361 ("table_type", json!("BASE TABLE")),
362 ("self_referencing_column_name", Value::Null),
363 ("reference_generation", Value::Null),
364 ("user_defined_type_catalog", Value::Null),
365 ("user_defined_type_schema", Value::Null),
366 ("user_defined_type_name", Value::Null),
367 ("is_insertable_into", json!("YES")),
368 ("is_typed", json!("NO")),
369 ("commit_action", Value::Null),
370 ])
371 })
372 .collect(),
373
374 "information_schema.columns" => colls
375 .iter()
376 .flat_map(|c| {
377 columns_of(db, c)
378 .into_iter()
379 .enumerate()
380 .map(move |(i, (name, oid))| {
381 row(vec![
382 ("table_catalog", json!("nedb")),
383 ("table_schema", json!("public")),
384 ("table_name", json!(c)),
385 ("column_name", json!(name)),
386 ("ordinal_position", json!(i as i64 + 1)),
387 ("column_default", Value::Null),
388 ("is_nullable", json!("YES")),
390 ("data_type", json!(type_name(oid))),
391 ("character_maximum_length", Value::Null),
392 ("numeric_precision", Value::Null),
393 ("numeric_scale", Value::Null),
394 ("datetime_precision", Value::Null),
395 ("udt_catalog", json!("nedb")),
396 ("udt_schema", json!("pg_catalog")),
397 ("udt_name", json!(type_name(oid))),
398 ("is_updatable", json!("YES")),
399 ])
400 })
401 .collect::<Vec<_>>()
402 })
403 .collect(),
404
405 "pg_type" => [
409 (16, "bool"), (20, "int8"), (21, "int2"), (23, "int4"),
410 (25, "text"), (700, "float4"), (701, "float8"), (1043, "varchar"),
411 ]
412 .iter()
413 .map(|(oid, name)| {
414 row(vec![
415 ("oid", json!(*oid as i64)),
416 ("typname", json!(name)),
417 ("typnamespace", json!(CATALOG_NS_OID)),
418 ("typowner", json!(OWNER_OID)),
419 ("typlen", json!(-1)),
420 ("typtype", json!("b")),
421 ("typcategory", json!("S")),
422 ("typelem", json!(0)),
423 ("typrelid", json!(0)),
424 ("typarray", json!(0)),
428 ("typbasetype", json!(0)),
429 ("typtypmod", json!(-1)),
430 ("typcollation", json!(0)),
431 ("typnotnull", json!(false)),
432 ("typdefault", Value::Null),
433 ("typacl", Value::Null),
434 ("typndims", json!(0)),
435 ("typbyval", json!(false)),
436 ("typalign", json!("i")),
437 ("typstorage", json!("x")),
438 ("typinput", json!(0)),
439 ("typoutput", json!(0)),
440 ("tableoid", json!(1247)),
441 ])
442 })
443 .collect(),
444
445 "pg_database" => vec![row(vec![
447 ("oid", json!(oid_for("nedb"))),
448 ("datname", json!("nedb")),
449 ("datdba", json!(OWNER_OID)),
450 ("encoding", json!(6)), ("datcollate", json!("C")),
452 ("datctype", json!("C")),
453 ("datlocprovider", json!("c")),
454 ("daticulocale", Value::Null),
455 ("daticurules", Value::Null),
456 ("datistemplate", json!(false)),
457 ("datallowconn", json!(true)),
458 ("datconnlimit", json!(-1)),
459 ("datacl", Value::Null),
460 ])],
461
462 "pg_am" => vec![row(vec![
463 ("oid", json!(2)),
464 ("amname", json!("heap")),
465 ("amhandler", json!(0)),
466 ("amtype", json!("t")),
467 ])],
468
469 "pg_roles" | "pg_user" => vec![row(vec![
470 ("oid", json!(OWNER_OID)),
471 ("rolname", json!("nedb")),
472 ("usename", json!("nedb")),
473 ("rolsuper", json!(true)),
474 ("usesuper", json!(true)),
475 ("rolcanlogin", json!(true)),
476 ("rolcreatedb", json!(true)),
477 ("rolvaliduntil", Value::Null),
478 ])],
479
480 "pg_index" | "pg_description" | "pg_constraint" | "pg_tablespace"
486 | "pg_settings" | "information_schema.key_column_usage"
487 | "information_schema.table_constraints" => vec![],
488
489 t if EMPTY_CATALOG.contains(&t) => vec![],
490
491 _ => return None,
492 })
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use tempfile::tempdir;
499
500 fn db_with(colls: &[(&str, &str, Value)]) -> (tempfile::TempDir, Arc<Db>) {
501 let dir = tempdir().unwrap();
502 let db = Arc::new(Db::open(dir.path(), None).unwrap());
503 for (coll, id, doc) in colls {
504 db.put(coll, id, doc.clone(), vec![], None, None).unwrap();
505 }
506 (dir, db)
507 }
508
509 fn names(rows: &[Value], field: &str) -> Vec<String> {
510 let mut v: Vec<String> = rows
511 .iter()
512 .filter_map(|r| r.get(field)?.as_str().map(str::to_string))
513 .collect();
514 v.sort();
515 v
516 }
517
518 #[test]
519 fn a_collection_appears_as_a_table_in_every_place_a_client_looks() {
520 let (_t, db) = db_with(&[
524 ("orders", "1", json!({"total": 1})),
525 ("drivers", "d1", json!({"name": "Bob"})),
526 ]);
527 let d = Some(&db);
528 assert_eq!(names(&rows("pg_class", d).unwrap(), "relname"),
529 vec!["drivers", "orders"]);
530 assert_eq!(names(&rows("information_schema.tables", d).unwrap(), "table_name"),
531 vec!["drivers", "orders"]);
532 }
533
534 #[test]
535 fn internal_bookkeeping_is_not_presented_as_a_user_table() {
536 let (_t, db) = db_with(&[("orders", "1", json!({"a": 1}))]);
539 let db2 = Arc::clone(&db);
540 db2.link("orders:1", "rel", "orders:1").ok();
541 let got = names(&rows("pg_class", Some(&db)).unwrap(), "relname");
542 assert!(!got.iter().any(|n| n.starts_with("__")), "{:?}", got);
543 }
544
545 #[test]
546 fn a_columns_reported_type_is_the_type_the_WIRE_sends() {
547 let (_t, db) = db_with(&[
552 ("t", "1", json!({"n": 7, "s": "x", "b": true, "f": 1.5})),
553 ]);
554 let cols = rows("information_schema.columns", Some(&db)).unwrap();
555 let by = |name: &str| -> String {
556 cols.iter()
557 .find(|r| r["column_name"] == json!(name))
558 .and_then(|r| r["data_type"].as_str())
559 .unwrap_or("<missing>").to_string()
560 };
561 assert_eq!(by("n"), "bigint");
562 assert_eq!(by("s"), "text");
563 assert_eq!(by("b"), "boolean");
564 assert_eq!(by("f"), "double precision");
565 }
566
567 #[test]
568 fn everything_is_nullable_because_a_schemaless_document_may_omit_anything() {
569 let (_t, db) = db_with(&[("t", "1", json!({"a": 1}))]);
570 let cols = rows("information_schema.columns", Some(&db)).unwrap();
571 assert!(cols.iter().all(|r| r["is_nullable"] == json!("YES")), "{:?}", cols);
572 let attrs = rows("pg_attribute", Some(&db)).unwrap();
573 assert!(attrs.iter().all(|r| r["attnotnull"] == json!(false)));
574 }
575
576 #[test]
577 fn pg_attribute_correlates_with_pg_class_by_oid() {
578 let (_t, db) = db_with(&[("orders", "1", json!({"total": 1}))]);
581 let d = Some(&db);
582 let rel = rows("pg_class", d).unwrap();
583 let oid = rel.iter().find(|r| r["relname"] == json!("orders")).unwrap()["oid"].clone();
584 let attrs = rows("pg_attribute", d).unwrap();
585 assert!(attrs.iter().any(|r| r["attrelid"] == oid),
586 "no pg_attribute row points at pg_class.oid {:?}", oid);
587 assert!(attrs.iter().all(|r| r["attnum"].as_i64().unwrap_or(0) >= 1));
589 }
590
591 #[test]
592 fn an_oid_is_stable_across_calls_so_a_join_holds() {
593 assert_eq!(oid_for("orders"), oid_for("orders"));
594 assert_ne!(oid_for("orders"), oid_for("drivers"));
595 assert!(oid_for("orders") >= 16_384);
598 assert!(oid_for("orders") < i32::MAX as i64);
600 }
601
602 #[test]
603 fn pg_namespace_answers_without_any_database_open() {
604 let ns = rows("pg_namespace", None).unwrap();
607 assert_eq!(names(&ns, "nspname"),
608 vec!["information_schema", "pg_catalog", "public"]);
609 }
610
611 #[test]
612 fn the_tables_nedb_genuinely_has_nothing_for_are_EMPTY_not_absent() {
613 for t in ["pg_index", "pg_constraint", "pg_description", "pg_tablespace",
618 "information_schema.key_column_usage",
619 "information_schema.table_constraints"] {
620 let r = rows(t, None).unwrap_or_else(|| panic!("{} must be served", t));
621 assert!(r.is_empty(), "{} should be empty, got {:?}", t, r);
622 }
623 }
624
625 #[test]
626 fn a_name_that_is_not_a_catalogue_relation_is_not_claimed() {
627 assert!(!is_catalog("orders"));
628 assert!(!is_catalog("tables"), "a bare `tables` is a user collection");
629 assert!(rows("orders", None).is_none());
630 assert!(is_catalog("information_schema.tables"));
633 }
634
635 #[test]
636 fn pg_type_lists_only_types_this_endpoint_can_actually_send() {
637 let t = names(&rows("pg_type", None).unwrap(), "typname");
638 assert!(t.contains(&"int8".to_string()));
639 assert!(t.contains(&"text".to_string()));
640 assert!(!t.contains(&"tsvector".to_string()));
643 assert!(!t.contains(&"jsonb".to_string()));
644 }
645}