1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::ops::Range;

use crate::{
    ForeignKeyId, ForeignKeyWalker, IndexColumnWalker, IndexId, IndexWalker, NamespaceId, Table, TableColumnId,
    TableColumnWalker, TableId, TableProperties, Walker,
};

/// Traverse a table.
pub type TableWalker<'a> = Walker<'a, TableId>;

impl<'a> TableWalker<'a> {
    /// Get a column in the table, by name.
    pub fn column(self, column_name: &str) -> Option<TableColumnWalker<'a>> {
        self.columns().find(|column| column.name() == column_name)
    }

    fn columns_range(self) -> Range<usize> {
        super::range_for_key(&self.schema.table_columns, self.id, |(tid, _)| *tid)
    }

    /// Traverse the table's columns.
    pub fn columns(self) -> impl ExactSizeIterator<Item = TableColumnWalker<'a>> {
        self.columns_range()
            .map(move |idx| self.walk(TableColumnId(idx as u32)))
    }

    /// The number of foreign key constraints on the table.
    pub fn foreign_key_count(self) -> usize {
        self.foreign_keys_range().len()
    }

    /// Traverse the indexes on the table.
    pub fn indexes(self) -> impl ExactSizeIterator<Item = IndexWalker<'a>> {
        let range = super::range_for_key(&self.schema.indexes, self.id, |idx| idx.table_id);
        range.map(move |idx| self.walk(IndexId(idx as u32)))
    }

    /// Traverse the foreign keys on the table.
    pub fn foreign_keys(self) -> impl ExactSizeIterator<Item = ForeignKeyWalker<'a>> {
        self.foreign_keys_range()
            .map(move |id| self.walk(ForeignKeyId(id as u32)))
    }

    /// Traverse foreign keys from other tables, referencing current table.
    pub fn referencing_foreign_keys(self) -> impl Iterator<Item = ForeignKeyWalker<'a>> {
        self.schema
            .table_walkers()
            .filter(move |t| t.id != self.id)
            .flat_map(|t| t.foreign_keys())
            .filter(move |fk| fk.referenced_table().id == self.id)
    }

    /// The table name.
    pub fn name(self) -> &'a str {
        &self.table().name
    }

    fn foreign_keys_range(self) -> Range<usize> {
        super::range_for_key(&self.schema.foreign_keys, self.id, |fk| fk.constrained_table)
    }

    /// Try to traverse a foreign key for a single column.
    pub fn foreign_key_for_column(self, column: TableColumnId) -> Option<ForeignKeyWalker<'a>> {
        self.foreign_keys().find(|fk| {
            let cols = fk.columns();
            cols.len() == 1 && cols[0].constrained_column == column
        })
    }

    /// The namespace the table belongs to, if defined.
    pub fn namespace(self) -> Option<&'a str> {
        self.schema
            .namespaces
            .get(self.table().namespace_id.0 as usize)
            .map(|s| s.as_str())
    }

    /// The namespace the table belongs to.
    pub fn namespace_id(self) -> NamespaceId {
        self.table().namespace_id
    }

    /// Traverse to the primary key of the table.
    pub fn primary_key(self) -> Option<IndexWalker<'a>> {
        self.indexes().find(|idx| idx.is_primary_key())
    }

    /// The columns that are part of the primary keys.
    pub fn primary_key_columns(self) -> Option<impl ExactSizeIterator<Item = IndexColumnWalker<'a>>> {
        self.primary_key().map(|pk| pk.columns())
    }

    /// How many columns are in the primary key? Returns 0 in the absence of a pk.
    pub fn primary_key_columns_count(self) -> usize {
        self.primary_key_columns().map(|cols| cols.len()).unwrap_or(0)
    }

    /// Is the table a partition table?
    pub fn is_partition(self) -> bool {
        self.table().properties.contains(TableProperties::IsPartition)
    }

    /// Does the table have subclasses?
    pub fn has_subclass(self) -> bool {
        self.table().properties.contains(TableProperties::HasSubclass)
    }

    /// Does the table have row level security enabled?
    pub fn has_row_level_security(self) -> bool {
        self.table().properties.contains(TableProperties::HasRowLevelSecurity)
    }

    /// Does the table have check constraints?
    pub fn has_check_constraints(self) -> bool {
        self.schema
            .check_constraints
            .binary_search_by_key(&self.id, |(id, _)| *id)
            .is_ok()
    }

    /// The check constraint names for the table.
    pub fn check_constraints(self) -> impl ExactSizeIterator<Item = &'a str> {
        let low = self.schema.check_constraints.partition_point(|(id, _)| *id < self.id);
        let high = self.schema.check_constraints[low..].partition_point(|(id, _)| *id <= self.id);

        self.schema.check_constraints[low..low + high]
            .iter()
            .map(|(_, name)| name.as_str())
    }

    /// Description (comment) of the table.
    pub fn description(self) -> Option<&'a str> {
        self.table().description.as_deref()
    }

    /// Reference to the underlying `Table` struct.
    fn table(self) -> &'a Table {
        &self.schema.tables[self.id.0 as usize]
    }
}