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>;
394 fn replace_sequence_row(&self, sequence: &SequenceRow) -> StorageBackendResult<bool>;
396 fn rename_sequence_row(&self, from: &str, to: &str) -> StorageBackendResult<bool>;
398 fn drop_sequence_row(&self, name: &str) -> StorageBackendResult<bool>;
399 fn load_sequence_rows(&self) -> StorageBackendResult<Vec<SequenceRow>>;
400
401 fn sequence_has_private_changes(
403 &self,
404 _relation: &RelationIdentity,
405 _object_id: [u8; 16],
406 ) -> StorageBackendResult<bool> {
407 if self.transaction_model().is_versioned() {
408 return Err(StorageBackendError::Other(
409 "private sequence provenance is not supported by this catalog".into(),
410 ));
411 }
412 Ok(false)
413 }
414 fn reserve_sequence_values(
415 &self,
416 name: &str,
417 object_id: [u8; 16],
418 definition_generation: [u8; 16],
419 ) -> StorageBackendResult<SequenceReservationResult>;
420 fn next_sequence_value(
422 &self,
423 name: &str,
424 object_id: [u8; 16],
425 ) -> StorageBackendResult<Option<i64>> {
426 loop {
427 let relation =
428 RelationIdentity::from_legacy_name(name).map_err(StorageBackendError::Other)?;
429 let Some(row) = self
430 .load_sequence_rows()?
431 .into_iter()
432 .find(|row| row.relation == relation && row.object_id == object_id)
433 else {
434 return Ok(None);
435 };
436 match self.reserve_sequence_values(name, object_id, row.definition_generation)? {
437 SequenceReservationResult::Reserved(reservation) => {
438 return Ok(Some(reservation.first_value));
439 }
440 SequenceReservationResult::DefinitionChanged => {}
441 SequenceReservationResult::Missing => return Ok(None),
442 SequenceReservationResult::Exhausted => {
443 return Err(StorageBackendError::Other(format!(
444 "sequence `{name}` exhausted"
445 )));
446 }
447 }
448 }
449 }
450 fn set_sequence_value(
451 &self,
452 name: &str,
453 object_id: [u8; 16],
454 definition_generation: [u8; 16],
455 value: i64,
456 called: bool,
457 log_count: i64,
458 ) -> StorageBackendResult<SequenceSetValueResult>;
459 fn log_sequence_values(
461 &self,
462 name: &str,
463 object_id: [u8; 16],
464 definition_generation: [u8; 16],
465 expected: (i64, bool),
466 logged: SequenceValuePosition,
467 ) -> StorageBackendResult<SequenceLogResult>;
468
469 fn save_view(&self, view: &ViewRow) -> StorageBackendResult<()>;
470 fn rename_view(
472 &self,
473 from: &RelationIdentity,
474 to: &RelationIdentity,
475 ) -> StorageBackendResult<bool>;
476 fn drop_view(&self, relation: &RelationIdentity) -> StorageBackendResult<bool>;
477 fn load_views(&self) -> StorageBackendResult<Vec<ViewRow>>;
478
479 fn save_named_graph(&self, name: &str) -> StorageBackendResult<()>;
480 fn guard_graph_definition(&self, _graph: Option<&str>) -> StorageBackendResult<()> {
482 Err(crate::StorageBackendError::Other(
483 "graph definition guards are not supported".into(),
484 ))
485 }
486 fn drop_named_graph(&self, name: &str) -> StorageBackendResult<()>;
487 fn load_named_graphs(&self) -> StorageBackendResult<Vec<String>>;
488 fn named_graph_exists(&self, name: &str) -> StorageBackendResult<bool>;
489
490 fn graph_vertex(&self, id: u64) -> StorageBackendResult<Option<GraphVertexRow>>;
492
493 fn for_each_graph_vertex_borrowed(
499 &self,
500 _ids: &[u64],
501 _visit: &mut dyn FnMut(u64, Option<&GraphVertexRow>) -> bool,
502 ) -> StorageBackendResult<Option<usize>> {
503 Ok(None)
504 }
505
506 fn graph_edge(&self, id: u64) -> StorageBackendResult<Option<EdgeRow>>;
507
508 fn graph_entity_ids(
511 &self,
512 filter: GraphEntityFilter<'_>,
513 after: Option<u64>,
514 limit: usize,
515 ) -> StorageBackendResult<Vec<u64>>;
516
517 fn graph_entity_count(&self, filter: GraphEntityFilter<'_>) -> StorageBackendResult<u64>;
518 fn graph_entity_max_id(&self, kind: GraphEntityKind) -> StorageBackendResult<Option<u64>>;
519 fn graph_entity_memberships(
520 &self,
521 kind: GraphEntityKind,
522 id: u64,
523 ) -> StorageBackendResult<Vec<String>>;
524 fn graph_has_membership(
525 &self,
526 kind: GraphEntityKind,
527 id: u64,
528 graph: &str,
529 ) -> StorageBackendResult<bool>;
530 fn load_named_graph_snapshot(&self, name: &str) -> StorageBackendResult<Option<GraphSnapshot>> {
534 graph_snapshot::load(self, name)
535 }
536 fn save_vertex(
537 &self,
538 vertex_id: u64,
539 label: &str,
540 properties_json: &str,
541 ) -> StorageBackendResult<()>;
542 fn delete_vertex(&self, vertex_id: u64) -> StorageBackendResult<()>;
543 fn load_vertices(&self) -> StorageBackendResult<Vec<(u64, String, String)>>;
544 fn save_edge(
545 &self,
546 edge_id: u64,
547 source_id: u64,
548 target_id: u64,
549 label: &str,
550 properties_json: &str,
551 ) -> StorageBackendResult<()>;
552 fn delete_edge(&self, edge_id: u64) -> StorageBackendResult<()>;
553 fn load_edges(&self) -> StorageBackendResult<Vec<EdgeRow>>;
554 fn save_graph_membership(
555 &self,
556 entity_type: &str,
557 entity_id: u64,
558 graph_name: &str,
559 ) -> StorageBackendResult<()>;
560 fn delete_graph_membership(
561 &self,
562 entity_type: &str,
563 entity_id: u64,
564 graph_name: &str,
565 ) -> StorageBackendResult<()>;
566 fn delete_graph_membership_for_graph(&self, graph_name: &str) -> StorageBackendResult<()>;
567 fn load_graph_memberships(&self) -> StorageBackendResult<Vec<(String, u64, String)>>;
568 fn purge_orphan_graph_entities(&self) -> StorageBackendResult<()>;
569 fn replace_named_graph(
570 &self,
571 graph_name: &str,
572 snapshot: &GraphSnapshot,
573 ) -> StorageBackendResult<()>;
574 fn drop_named_graph_data(&self, graph_name: &str) -> StorageBackendResult<()>;
575
576 fn save_analyzer(&self, name: &str, config_json: &str) -> StorageBackendResult<()>;
577 fn drop_analyzer(&self, name: &str) -> StorageBackendResult<()>;
578 fn load_analyzers(&self) -> StorageBackendResult<Vec<(String, String)>>;
579
580 fn save_analyzer_revision(
582 &self,
583 _name: &str,
584 _config_json: &str,
585 _descriptor_json: &str,
586 ) -> StorageBackendResult<()> {
587 Err(StorageBackendError::Other(
588 "durable analyzer descriptors are not supported by this catalog".into(),
589 ))
590 }
591
592 fn load_analyzer_descriptors(&self) -> StorageBackendResult<Vec<(String, String)>> {
594 Ok(Vec::new())
595 }
596
597 fn replace_table_field_analyzer_binding(
599 &self,
600 _table: &str,
601 _field: &str,
602 _phase: &str,
603 _name: &str,
604 _binding_json: &str,
605 ) -> StorageBackendResult<()> {
606 Err(StorageBackendError::Other(
607 "durable analyzer bindings are not supported by this catalog".into(),
608 ))
609 }
610
611 fn load_table_field_analyzer_bindings(
613 &self,
614 ) -> StorageBackendResult<Vec<(String, String, String)>> {
615 Ok(Vec::new())
616 }
617
618 fn save_table_field_analyzer(
619 &self,
620 table_name: &str,
621 field: &str,
622 phase: &str,
623 analyzer_name: &str,
624 ) -> StorageBackendResult<()>;
625 fn replace_table_field_analyzer(
626 &self,
627 table_name: &str,
628 field: &str,
629 phase: &str,
630 analyzer_name: &str,
631 ) -> StorageBackendResult<()>;
632 fn drop_table_field_analyzer_field(
633 &self,
634 table_name: &str,
635 field: &str,
636 ) -> StorageBackendResult<()>;
637 fn drop_table_field_analyzers(&self, table_name: &str) -> StorageBackendResult<()>;
638 fn load_table_field_analyzers(
639 &self,
640 ) -> StorageBackendResult<Vec<(String, String, String, String)>>;
641
642 fn save_foreign_server(
643 &self,
644 name: &str,
645 fdw_type: &str,
646 options_json: &str,
647 ) -> StorageBackendResult<()>;
648 fn drop_foreign_server(&self, name: &str) -> StorageBackendResult<()>;
649 fn load_foreign_servers(&self) -> StorageBackendResult<Vec<(String, String, String)>>;
650
651 fn save_foreign_table(&self, row: &ForeignTableRow) -> StorageBackendResult<()>;
652 fn rename_foreign_table(
654 &self,
655 from: &RelationIdentity,
656 to: &RelationIdentity,
657 ) -> StorageBackendResult<bool>;
658 fn update_foreign_table_security(
659 &self,
660 relation: &RelationIdentity,
661 security: &RelationSecurityRow,
662 ) -> StorageBackendResult<bool>;
663 fn drop_foreign_table(&self, relation: &RelationIdentity) -> StorageBackendResult<()>;
664 fn load_foreign_tables(&self) -> StorageBackendResult<Vec<ForeignTableRow>>;
665
666 fn save_catalog_index(
667 &self,
668 relation: &RelationIdentity,
669 index_type: &str,
670 table_name: &str,
671 columns_json: &str,
672 parameters_json: &str,
673 ) -> StorageBackendResult<()> {
674 self.save_catalog_index_row(&CatalogIndexRow {
675 relation: relation.clone(),
676 index_type: index_type.to_string(),
677 table_name: table_name.to_string(),
678 columns_json: columns_json.to_string(),
679 parameters_json: parameters_json.to_string(),
680 definition_json: None,
681 })
682 }
683 fn save_catalog_index_row(&self, index: &CatalogIndexRow) -> StorageBackendResult<()>;
684 fn drop_catalog_index(&self, relation: &RelationIdentity) -> StorageBackendResult<()>;
685 fn drop_catalog_indexes_for_table(&self, table_name: &str) -> StorageBackendResult<()>;
686 fn load_catalog_indexes(&self) -> StorageBackendResult<Vec<CatalogIndexRow>>;
687
688 fn save_path_index(
689 &self,
690 graph_name: &str,
691 label_sequences_json: &str,
692 ) -> StorageBackendResult<()>;
693 fn drop_path_index(&self, graph_name: &str) -> StorageBackendResult<()>;
694 fn load_path_indexes(&self) -> StorageBackendResult<Vec<(String, String)>>;
695
696 fn clear_path_index_data(&self, index: &str) -> StorageBackendResult<()>;
700 fn save_path_index_pairs(
701 &self,
702 index: &str,
703 sequence: &str,
704 pairs: &[(u64, u64)],
705 ) -> StorageBackendResult<()>;
706 fn finish_path_index_data(
707 &self,
708 index: &str,
709 graph: &str,
710 definition: &str,
711 ) -> StorageBackendResult<()>;
712 fn path_index_data_is_current(
713 &self,
714 index: &str,
715 definition: &str,
716 ) -> StorageBackendResult<bool>;
717 fn path_index_pairs(
718 &self,
719 index: &str,
720 sequence: &str,
721 after: Option<(u64, u64)>,
722 limit: usize,
723 ) -> StorageBackendResult<Vec<(u64, u64)>>;
724
725 fn save_column_stats(&self, stats: ColumnStatsInput<'_>) -> StorageBackendResult<()>;
726 fn replace_column_stats(
729 &self,
730 table_name: &str,
731 stats: &[ColumnStatsInput<'_>],
732 ) -> StorageBackendResult<()>;
733 fn load_column_stats(&self, table_name: &str) -> StorageBackendResult<Vec<ColumnStatsRow>>;
734 fn delete_column_stats(&self, table_name: &str) -> StorageBackendResult<()>;
735}
736
737#[cfg(test)]
738mod tests {
739 use super::RelationIdentity;
740
741 #[test]
742 fn relation_identity_rendering_is_reversible_and_collision_free() {
743 let left = RelationIdentity::new("a.b", "c");
744 let right = RelationIdentity::new("a", "b.c");
745 assert_eq!(left.qualified_name(), "\"a.b\".c");
746 assert_eq!(right.qualified_name(), "a.\"b.c\"");
747 assert_ne!(left.qualified_name(), right.qualified_name());
748 assert_eq!(
749 RelationIdentity::from_legacy_name(&left.qualified_name()).unwrap(),
750 left
751 );
752 assert_eq!(
753 RelationIdentity::from_legacy_name(&right.qualified_name()).unwrap(),
754 right
755 );
756 }
757
758 #[test]
759 fn relation_identity_preserves_quotes_and_unqualified_public_alias() {
760 let quoted = RelationIdentity::new("public", "a\"b.c");
761 assert_eq!(quoted.qualified_name(), "public.\"a\"\"b.c\"");
762 assert_eq!(
763 quoted.canonical_and_legacy_public_names(),
764 vec![
765 "public.\"a\"\"b.c\"".to_string(),
766 "\"a\"\"b.c\"".to_string()
767 ]
768 );
769 assert_eq!(
770 RelationIdentity::from_legacy_name("ed.qualified_name()).unwrap(),
771 quoted
772 );
773 assert_eq!(
774 RelationIdentity::from_legacy_name("plain").unwrap(),
775 RelationIdentity::new("public", "plain")
776 );
777 assert_eq!(
778 RelationIdentity::new("app", "plain").canonical_and_legacy_public_names(),
779 vec!["app.plain".to_string()]
780 );
781 assert_eq!(
782 RelationIdentity::new("public", "Upper").canonical_and_legacy_public_names(),
783 vec![
784 "public.\"Upper\"".to_string(),
785 "\"Upper\"".to_string(),
786 "Upper".to_string()
787 ]
788 );
789 }
790}