1use serde::{Deserialize, Serialize};
15
16use crate::backend::{StorageBackendError, StorageBackendResult};
17
18mod cache_revisions;
19mod graph_access;
20pub mod graph_guards;
21pub mod graph_identifiers;
22pub mod graph_observations;
23pub(crate) mod graph_snapshot;
24mod identity;
25mod relation;
26pub mod relation_acl;
27mod relation_security;
28mod schema;
29mod sequence_security;
30mod sequence_values;
31
32pub use cache_revisions::CatalogCacheRevisions;
33pub use graph_access::validate_graph_page;
34pub use graph_access::{
35 GraphEntityFilter, GraphEntityKind, GraphEntitySelector, MAX_GRAPH_ID_PAGE,
36};
37pub use identity::new_nonzero_catalog_identity;
38pub use relation::RelationIdentity;
39pub use relation_security::{BoundRelationSecurity, LegacyRelationSecurity, RelationSecurityRow};
40pub use sequence_security::{BoundSequenceSecurity, LegacySequenceSecurity, SequenceSecurityRow};
41pub use sequence_values::{
42 sequence_value_allocation, sequence_value_reservation, SequenceLogResult,
43 SequenceReservationResult, SequenceSetValueResult, SequenceValueAllocation,
44 SequenceValuePosition, SequenceValueReservation,
45};
46mod table;
47
48pub use schema::{BoundSchemaRow, SchemaAclEntry, SchemaPrivileges, SchemaRow};
49pub use table::{TableAclEntry, TablePrivileges};
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum RelationKind {
54 Table,
55 View,
56 Sequence,
57 ForeignTable,
58 Index,
59}
60
61impl RelationKind {
62 pub fn as_str(self) -> &'static str {
63 match self {
64 Self::Table => "table",
65 Self::View => "view",
66 Self::Sequence => "sequence",
67 Self::ForeignTable => "foreign_table",
68 Self::Index => "index",
69 }
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct TableSchema {
75 pub relation: RelationIdentity,
76 #[serde(flatten)]
77 pub security: RelationSecurityRow,
78 #[serde(default)]
80 pub object_id: [u8; 16],
81 #[serde(default)]
83 pub storage_generation: [u8; 16],
84 pub analyzer_json: String,
85 pub fts_fields: Vec<String>,
86 pub vector_fields: Vec<VectorFieldSchema>,
87 #[serde(default)]
92 pub columns_json: String,
93 #[serde(default)]
96 pub constraints_json: String,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct VectorFieldSchema {
101 pub field: String,
102 pub dimensions: u32,
103}
104
105#[derive(Debug, Clone)]
108pub struct EdgeRow {
109 pub edge_id: u64,
110 pub source_id: u64,
111 pub target_id: u64,
112 pub label: String,
113 pub properties_json: String,
114}
115
116#[derive(Debug, Clone)]
118pub struct GraphVertexRow {
119 pub vertex_id: u64,
120 pub label: String,
121 pub properties_json: String,
122}
123
124#[derive(Debug, Clone)]
127pub struct GraphSnapshot {
128 pub vertices: Vec<GraphVertexRow>,
129 pub edges: Vec<EdgeRow>,
130 pub label_registry_json: String,
131}
132
133#[derive(Debug, Clone)]
135pub struct ForeignTableRow {
136 pub relation: RelationIdentity,
137 pub security: RelationSecurityRow,
138 pub server_name: String,
139 pub columns_json: String,
140 pub options_json: String,
141}
142
143#[derive(Debug, Clone)]
146pub struct ViewRow {
147 pub relation: RelationIdentity,
148 pub security: RelationSecurityRow,
149 pub definition_json: String,
150}
151
152pub use uqa_core::catalog_index::CatalogIndexRow;
153
154#[derive(Debug, Clone, Copy)]
156pub struct ColumnStatsInput<'a> {
157 pub table_name: &'a str,
158 pub column_name: &'a str,
159 pub distinct_count: i64,
160 pub null_count: i64,
161 pub min_value: Option<&'a str>,
162 pub max_value: Option<&'a str>,
163 pub row_count: i64,
164 pub histogram_json: &'a str,
165 pub mcv_values_json: &'a str,
166 pub mcv_frequencies_json: &'a str,
167}
168
169impl<'a> ColumnStatsInput<'a> {
170 pub fn basic(
171 table_name: &'a str,
172 column_name: &'a str,
173 distinct_count: i64,
174 null_count: i64,
175 min_value: Option<&'a str>,
176 max_value: Option<&'a str>,
177 row_count: i64,
178 ) -> Self {
179 Self {
180 table_name,
181 column_name,
182 distinct_count,
183 null_count,
184 min_value,
185 max_value,
186 row_count,
187 histogram_json: "[]",
188 mcv_values_json: "[]",
189 mcv_frequencies_json: "[]",
190 }
191 }
192}
193
194#[derive(Debug, Clone, PartialEq)]
196pub struct ColumnStatsRow {
197 pub column_name: String,
198 pub distinct_count: i64,
199 pub null_count: i64,
200 pub min_value: Option<String>,
201 pub max_value: Option<String>,
202 pub row_count: i64,
203 pub histogram_json: String,
204 pub mcv_values_json: String,
205 pub mcv_frequencies_json: String,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212pub struct SequenceOptions {
213 pub data_type: String,
214 pub min_value: Option<i64>,
216 pub max_value: Option<i64>,
218 pub cycle: bool,
219 #[serde(default = "default_sequence_cache_size")]
220 pub cache_size: i64,
221}
222
223const fn default_sequence_cache_size() -> i64 {
224 1
225}
226
227impl Default for SequenceOptions {
228 fn default() -> Self {
229 Self {
230 data_type: "bigint".into(),
231 min_value: None,
232 max_value: None,
233 cycle: false,
234 cache_size: default_sequence_cache_size(),
235 }
236 }
237}
238
239pub use uqa_core::catalog_sequence::{
240 SequenceAclEntry, SequenceOwner, SequenceOwnerDependency, SequencePrivileges,
241};
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244pub struct SequenceRow {
245 pub relation: RelationIdentity,
246 #[serde(flatten)]
248 pub security: SequenceSecurityRow,
249 #[serde(default)]
251 pub object_id: [u8; 16],
252 #[serde(default)]
254 pub definition_generation: [u8; 16],
255 pub start: i64,
256 pub increment: i64,
257 pub current: i64,
258 pub called: bool,
260 #[serde(default)]
262 pub log_count: i64,
263 pub persistence: String,
265 #[serde(default)]
266 pub owner: Option<SequenceOwner>,
267 #[serde(default)]
268 pub options: SequenceOptions,
269}
270
271pub trait CatalogFacade: Send + Sync {
273 fn transaction_model(&self) -> crate::StorageTransactionModel {
275 crate::StorageTransactionModel::ProviderSerialized
276 }
277
278 fn transaction_affinity(&self) -> Option<crate::StorageSessionAffinity> {
280 None
281 }
282
283 fn initialize_storage(&self) -> StorageBackendResult<()> {
285 Ok(())
286 }
287
288 fn cache_revisions(&self) -> StorageBackendResult<Option<CatalogCacheRevisions>> {
291 Ok(None)
292 }
293
294 fn set_metadata(&self, key: &str, value: &str) -> StorageBackendResult<()>;
295 fn delete_metadata(&self, key: &str) -> StorageBackendResult<()>;
297 fn get_metadata(&self, key: &str) -> StorageBackendResult<Option<String>>;
298 fn metadata_with_prefix(&self, prefix: &str) -> StorageBackendResult<Vec<(String, String)>>;
300 fn save_relation_acl(
302 &self,
303 relation: &RelationIdentity,
304 column: Option<&str>,
305 entry: &relation_acl::RelationAclTuple,
306 ) -> StorageBackendResult<()> {
307 if !self.transaction_model().is_versioned() {
308 return relation_acl::save_serialized(self, relation, column, entry);
309 }
310 self.set_metadata(&relation_acl::key(relation, column), &entry.encode(column)?)
311 }
312 fn metadata_has_private_changes(&self, _key: &str) -> StorageBackendResult<bool> {
314 if self.transaction_model().is_versioned() {
315 return Err(StorageBackendError::Other(
316 "private metadata provenance is not supported by this catalog".into(),
317 ));
318 }
319 Ok(false)
320 }
321 fn save_statistics_maintenance(
323 &self,
324 table: &str,
325 state: &crate::statistics_maintenance::StatisticsMaintenance,
326 ) -> StorageBackendResult<()> {
327 self.set_metadata(
328 &crate::statistics_maintenance::StatisticsMaintenance::key(table),
329 &serde_json::to_string(state)?,
330 )
331 }
332 fn fts_storage_was_reset(&self) -> bool {
333 false
334 }
335
336 fn migrate_relation_namespace(&self) -> StorageBackendResult<()>;
340
341 fn save_schema_row(&self, schema: &SchemaRow) -> StorageBackendResult<()>;
342 fn drop_schema(&self, name: &str) -> StorageBackendResult<()>;
343 fn load_schema_rows(&self) -> StorageBackendResult<Vec<SchemaRow>>;
344
345 fn schema_has_private_changes(&self, _name: &str) -> StorageBackendResult<bool> {
347 if self.transaction_model().is_versioned() {
348 return Err(StorageBackendError::Other(
349 "private schema provenance is not supported by this catalog".into(),
350 ));
351 }
352 Ok(false)
353 }
354
355 fn save_schema(&self, name: &str) -> StorageBackendResult<()> {
356 self.save_schema_row(&SchemaRow::bootstrap(name))
357 }
358
359 fn load_schemas(&self) -> StorageBackendResult<Vec<String>> {
360 Ok(self
361 .load_schema_rows()?
362 .into_iter()
363 .map(|schema| schema.name().to_owned())
364 .collect())
365 }
366
367 fn save_table(&self, schema: &TableSchema) -> StorageBackendResult<()>;
368 fn load_tables(&self) -> StorageBackendResult<Vec<TableSchema>>;
369 fn drop_table(&self, name: &str) -> StorageBackendResult<()>;
370 fn drop_table_and_data(&self, name: &str) -> StorageBackendResult<()>;
373 fn purge_table_data(&self, name: &str) -> StorageBackendResult<()>;
374 fn rename_table_data(&self, from: &str, to: &str) -> StorageBackendResult<()>;
375 fn drop_column_data(&self, table_name: &str, column_name: &str) -> StorageBackendResult<()>;
376 fn rename_column_data(
377 &self,
378 table_name: &str,
379 from: &str,
380 to: &str,
381 ) -> StorageBackendResult<()>;
382
383 fn save_model(&self, name: &str, json: &str) -> StorageBackendResult<()>;
384 fn load_models(&self) -> StorageBackendResult<Vec<(String, String)>>;
385 fn load_model(&self, name: &str) -> StorageBackendResult<Option<String>>;
386 fn drop_model(&self, name: &str) -> StorageBackendResult<()>;
387
388 fn save_scoring_params(&self, name: &str, params_json: &str) -> StorageBackendResult<()>;
389 fn load_scoring_params(&self, name: &str) -> StorageBackendResult<Option<String>>;
390 fn load_all_scoring_params(&self) -> StorageBackendResult<Vec<(String, String)>>;
391 fn drop_scoring_params(&self, name: &str) -> StorageBackendResult<()>;
392
393 fn create_sequence_row(&self, sequence: &SequenceRow) -> StorageBackendResult<bool>;
395 fn replace_sequence_row(&self, sequence: &SequenceRow) -> StorageBackendResult<bool>;
397 fn rename_sequence_row(&self, from: &str, to: &str) -> StorageBackendResult<bool>;
399 fn drop_sequence_row(&self, name: &str) -> StorageBackendResult<bool>;
401 fn load_sequence_rows(&self) -> StorageBackendResult<Vec<SequenceRow>>;
403
404 fn sequence_has_private_changes(
406 &self,
407 _relation: &RelationIdentity,
408 _object_id: [u8; 16],
409 ) -> StorageBackendResult<bool> {
410 if self.transaction_model().is_versioned() {
411 return Err(StorageBackendError::Other(
412 "private sequence provenance is not supported by this catalog".into(),
413 ));
414 }
415 Ok(false)
416 }
417
418 fn sequence_value_has_private_changes(
420 &self,
421 _object_id: [u8; 16],
422 _definition_generation: [u8; 16],
423 ) -> StorageBackendResult<bool> {
424 if self.transaction_model().is_versioned() {
425 return Err(StorageBackendError::Other(
426 "private sequence value provenance is not supported by this catalog".into(),
427 ));
428 }
429 Ok(false)
430 }
431
432 fn migrate_sequence_values(&self) -> StorageBackendResult<()> {
434 Ok(())
435 }
436 fn reserve_sequence_values(
438 &self,
439 name: &str,
440 object_id: [u8; 16],
441 definition_generation: [u8; 16],
442 ) -> StorageBackendResult<SequenceReservationResult>;
443 fn next_sequence_value(
445 &self,
446 name: &str,
447 object_id: [u8; 16],
448 ) -> StorageBackendResult<Option<i64>> {
449 loop {
450 let relation =
451 RelationIdentity::from_legacy_name(name).map_err(StorageBackendError::Other)?;
452 let Some(row) = self
453 .load_sequence_rows()?
454 .into_iter()
455 .find(|row| row.relation == relation && row.object_id == object_id)
456 else {
457 return Ok(None);
458 };
459 match self.reserve_sequence_values(name, object_id, row.definition_generation)? {
460 SequenceReservationResult::Reserved(reservation) => {
461 return Ok(Some(reservation.first_value));
462 }
463 SequenceReservationResult::DefinitionChanged => {}
464 SequenceReservationResult::Missing => return Ok(None),
465 SequenceReservationResult::Exhausted => {
466 return Err(StorageBackendError::Other(format!(
467 "sequence `{name}` exhausted"
468 )));
469 }
470 }
471 }
472 }
473 fn set_sequence_value(
475 &self,
476 name: &str,
477 object_id: [u8; 16],
478 definition_generation: [u8; 16],
479 value: i64,
480 called: bool,
481 log_count: i64,
482 ) -> StorageBackendResult<SequenceSetValueResult>;
483 fn log_sequence_values(
485 &self,
486 name: &str,
487 object_id: [u8; 16],
488 definition_generation: [u8; 16],
489 expected: (i64, bool),
490 logged: SequenceValuePosition,
491 ) -> StorageBackendResult<SequenceLogResult>;
492
493 fn save_view(&self, view: &ViewRow) -> StorageBackendResult<()>;
494 fn rename_view(
496 &self,
497 from: &RelationIdentity,
498 to: &RelationIdentity,
499 ) -> StorageBackendResult<bool>;
500 fn drop_view(&self, relation: &RelationIdentity) -> StorageBackendResult<bool>;
501 fn load_views(&self) -> StorageBackendResult<Vec<ViewRow>>;
502
503 fn save_named_graph(&self, name: &str) -> StorageBackendResult<()>;
504 fn guard_graph_definition(&self, _graph: Option<&str>) -> StorageBackendResult<()> {
506 Err(crate::StorageBackendError::Other(
507 "graph definition guards are not supported".into(),
508 ))
509 }
510 fn drop_named_graph(&self, name: &str) -> StorageBackendResult<()>;
511 fn load_named_graphs(&self) -> StorageBackendResult<Vec<String>>;
512 fn named_graph_exists(&self, name: &str) -> StorageBackendResult<bool>;
513
514 fn graph_vertex(&self, id: u64) -> StorageBackendResult<Option<GraphVertexRow>>;
516
517 fn for_each_graph_vertex_borrowed(
523 &self,
524 _ids: &[u64],
525 _visit: &mut dyn FnMut(u64, Option<&GraphVertexRow>) -> bool,
526 ) -> StorageBackendResult<Option<usize>> {
527 Ok(None)
528 }
529
530 fn graph_edge(&self, id: u64) -> StorageBackendResult<Option<EdgeRow>>;
531
532 fn graph_entity_ids(
535 &self,
536 filter: GraphEntityFilter<'_>,
537 after: Option<u64>,
538 limit: usize,
539 ) -> StorageBackendResult<Vec<u64>>;
540
541 fn graph_entity_count(&self, filter: GraphEntityFilter<'_>) -> StorageBackendResult<u64>;
542 fn graph_entity_max_id(&self, kind: GraphEntityKind) -> StorageBackendResult<Option<u64>>;
543 fn graph_entity_memberships(
544 &self,
545 kind: GraphEntityKind,
546 id: u64,
547 ) -> StorageBackendResult<Vec<String>>;
548 fn graph_has_membership(
549 &self,
550 kind: GraphEntityKind,
551 id: u64,
552 graph: &str,
553 ) -> StorageBackendResult<bool>;
554 fn load_named_graph_snapshot(&self, name: &str) -> StorageBackendResult<Option<GraphSnapshot>> {
558 graph_snapshot::load(self, name)
559 }
560 fn save_vertex(
561 &self,
562 vertex_id: u64,
563 label: &str,
564 properties_json: &str,
565 ) -> StorageBackendResult<()>;
566 fn delete_vertex(&self, vertex_id: u64) -> StorageBackendResult<()>;
567 fn load_vertices(&self) -> StorageBackendResult<Vec<(u64, String, String)>>;
568 fn save_edge(
569 &self,
570 edge_id: u64,
571 source_id: u64,
572 target_id: u64,
573 label: &str,
574 properties_json: &str,
575 ) -> StorageBackendResult<()>;
576 fn delete_edge(&self, edge_id: u64) -> StorageBackendResult<()>;
577 fn load_edges(&self) -> StorageBackendResult<Vec<EdgeRow>>;
578 fn save_graph_membership(
579 &self,
580 entity_type: &str,
581 entity_id: u64,
582 graph_name: &str,
583 ) -> StorageBackendResult<()>;
584 fn delete_graph_membership(
585 &self,
586 entity_type: &str,
587 entity_id: u64,
588 graph_name: &str,
589 ) -> StorageBackendResult<()>;
590 fn delete_graph_membership_for_graph(&self, graph_name: &str) -> StorageBackendResult<()>;
591 fn load_graph_memberships(&self) -> StorageBackendResult<Vec<(String, u64, String)>>;
592 fn purge_orphan_graph_entities(&self) -> StorageBackendResult<()>;
593 fn replace_named_graph(
594 &self,
595 graph_name: &str,
596 snapshot: &GraphSnapshot,
597 ) -> StorageBackendResult<()>;
598 fn drop_named_graph_data(&self, graph_name: &str) -> StorageBackendResult<()>;
599
600 fn save_analyzer(&self, name: &str, config_json: &str) -> StorageBackendResult<()>;
601 fn drop_analyzer(&self, name: &str) -> StorageBackendResult<()>;
602 fn load_analyzers(&self) -> StorageBackendResult<Vec<(String, String)>>;
603
604 fn save_analyzer_revision(
606 &self,
607 _name: &str,
608 _config_json: &str,
609 _descriptor_json: &str,
610 ) -> StorageBackendResult<()> {
611 Err(StorageBackendError::Other(
612 "durable analyzer descriptors are not supported by this catalog".into(),
613 ))
614 }
615
616 fn load_analyzer_descriptors(&self) -> StorageBackendResult<Vec<(String, String)>> {
618 Ok(Vec::new())
619 }
620
621 fn replace_table_field_analyzer_binding(
623 &self,
624 _table: &str,
625 _field: &str,
626 _phase: &str,
627 _name: &str,
628 _binding_json: &str,
629 ) -> StorageBackendResult<()> {
630 Err(StorageBackendError::Other(
631 "durable analyzer bindings are not supported by this catalog".into(),
632 ))
633 }
634
635 fn load_table_field_analyzer_bindings(
637 &self,
638 ) -> StorageBackendResult<Vec<(String, String, String)>> {
639 Ok(Vec::new())
640 }
641
642 fn save_table_field_analyzer(
643 &self,
644 table_name: &str,
645 field: &str,
646 phase: &str,
647 analyzer_name: &str,
648 ) -> StorageBackendResult<()>;
649 fn replace_table_field_analyzer(
650 &self,
651 table_name: &str,
652 field: &str,
653 phase: &str,
654 analyzer_name: &str,
655 ) -> StorageBackendResult<()>;
656 fn drop_table_field_analyzer_field(
657 &self,
658 table_name: &str,
659 field: &str,
660 ) -> StorageBackendResult<()>;
661 fn drop_table_field_analyzers(&self, table_name: &str) -> StorageBackendResult<()>;
662 fn load_table_field_analyzers(
663 &self,
664 ) -> StorageBackendResult<Vec<(String, String, String, String)>>;
665
666 fn save_foreign_server(
667 &self,
668 name: &str,
669 fdw_type: &str,
670 options_json: &str,
671 ) -> StorageBackendResult<()>;
672 fn drop_foreign_server(&self, name: &str) -> StorageBackendResult<()>;
673 fn load_foreign_servers(&self) -> StorageBackendResult<Vec<(String, String, String)>>;
674
675 fn save_foreign_table(&self, row: &ForeignTableRow) -> StorageBackendResult<()>;
676 fn rename_foreign_table(
678 &self,
679 from: &RelationIdentity,
680 to: &RelationIdentity,
681 ) -> StorageBackendResult<bool>;
682 fn update_foreign_table_security(
683 &self,
684 relation: &RelationIdentity,
685 security: &RelationSecurityRow,
686 ) -> StorageBackendResult<bool>;
687 fn drop_foreign_table(&self, relation: &RelationIdentity) -> StorageBackendResult<()>;
688 fn load_foreign_tables(&self) -> StorageBackendResult<Vec<ForeignTableRow>>;
689
690 fn save_catalog_index(
691 &self,
692 relation: &RelationIdentity,
693 index_type: &str,
694 table_name: &str,
695 columns_json: &str,
696 parameters_json: &str,
697 ) -> StorageBackendResult<()> {
698 self.save_catalog_index_row(&CatalogIndexRow {
699 relation: relation.clone(),
700 index_type: index_type.to_string(),
701 table_name: table_name.to_string(),
702 columns_json: columns_json.to_string(),
703 parameters_json: parameters_json.to_string(),
704 definition_json: None,
705 })
706 }
707 fn save_catalog_index_row(&self, index: &CatalogIndexRow) -> StorageBackendResult<()>;
708 fn drop_catalog_index(&self, relation: &RelationIdentity) -> StorageBackendResult<()>;
709 fn drop_catalog_indexes_for_table(&self, table_name: &str) -> StorageBackendResult<()>;
710 fn load_catalog_indexes(&self) -> StorageBackendResult<Vec<CatalogIndexRow>>;
711
712 fn save_path_index(
713 &self,
714 graph_name: &str,
715 label_sequences_json: &str,
716 ) -> StorageBackendResult<()>;
717 fn drop_path_index(&self, graph_name: &str) -> StorageBackendResult<()>;
718 fn load_path_indexes(&self) -> StorageBackendResult<Vec<(String, String)>>;
719
720 fn clear_path_index_data(&self, index: &str) -> StorageBackendResult<()>;
724 fn save_path_index_pairs(
725 &self,
726 index: &str,
727 sequence: &str,
728 pairs: &[(u64, u64)],
729 ) -> StorageBackendResult<()>;
730 fn finish_path_index_data(
731 &self,
732 index: &str,
733 graph: &str,
734 definition: &str,
735 ) -> StorageBackendResult<()>;
736 fn path_index_data_is_current(
737 &self,
738 index: &str,
739 definition: &str,
740 ) -> StorageBackendResult<bool>;
741 fn path_index_pairs(
742 &self,
743 index: &str,
744 sequence: &str,
745 after: Option<(u64, u64)>,
746 limit: usize,
747 ) -> StorageBackendResult<Vec<(u64, u64)>>;
748
749 fn save_column_stats(&self, stats: ColumnStatsInput<'_>) -> StorageBackendResult<()>;
750 fn replace_column_stats(
753 &self,
754 table_name: &str,
755 stats: &[ColumnStatsInput<'_>],
756 ) -> StorageBackendResult<()>;
757 fn load_column_stats(&self, table_name: &str) -> StorageBackendResult<Vec<ColumnStatsRow>>;
758 fn delete_column_stats(&self, table_name: &str) -> StorageBackendResult<()>;
759}
760
761#[cfg(test)]
762mod tests {
763 use super::RelationIdentity;
764
765 #[test]
766 fn relation_identity_rendering_is_reversible_and_collision_free() {
767 let left = RelationIdentity::new("a.b", "c");
768 let right = RelationIdentity::new("a", "b.c");
769 assert_eq!(left.qualified_name(), "\"a.b\".c");
770 assert_eq!(right.qualified_name(), "a.\"b.c\"");
771 assert_ne!(left.qualified_name(), right.qualified_name());
772 assert_eq!(
773 RelationIdentity::from_legacy_name(&left.qualified_name()).unwrap(),
774 left
775 );
776 assert_eq!(
777 RelationIdentity::from_legacy_name(&right.qualified_name()).unwrap(),
778 right
779 );
780 }
781
782 #[test]
783 fn relation_identity_preserves_quotes_and_unqualified_public_alias() {
784 let quoted = RelationIdentity::new("public", "a\"b.c");
785 assert_eq!(quoted.qualified_name(), "public.\"a\"\"b.c\"");
786 assert_eq!(
787 quoted.canonical_and_legacy_public_names(),
788 vec![
789 "public.\"a\"\"b.c\"".to_string(),
790 "\"a\"\"b.c\"".to_string()
791 ]
792 );
793 assert_eq!(
794 RelationIdentity::from_legacy_name("ed.qualified_name()).unwrap(),
795 quoted
796 );
797 assert_eq!(
798 RelationIdentity::from_legacy_name("plain").unwrap(),
799 RelationIdentity::new("public", "plain")
800 );
801 assert_eq!(
802 RelationIdentity::new("app", "plain").canonical_and_legacy_public_names(),
803 vec!["app.plain".to_string()]
804 );
805 assert_eq!(
806 RelationIdentity::new("public", "Upper").canonical_and_legacy_public_names(),
807 vec![
808 "public.\"Upper\"".to_string(),
809 "\"Upper\"".to_string(),
810 "Upper".to_string()
811 ]
812 );
813 }
814}