1use crc32fast::Hasher;
2
3#[derive(Clone, Debug)]
6pub struct ClosureTreeConfig {
7 entity_name: String,
8 hierarchy_name: String,
9 parent_column: String,
10 name_column: String,
11 hierarchy_table: String,
12 dependent_behavior: DependentBehavior,
13 order_strategy: Option<OrderStrategy>,
14 advisory_lock_strategy: AdvisoryLockStrategy,
15}
16
17impl ClosureTreeConfig {
18 pub fn new(entity_name: impl Into<String>, hierarchy_name: impl Into<String>) -> Self {
20 let entity_name = entity_name.into();
21 let hierarchy_name = hierarchy_name.into();
22
23 let default_lock = AdvisoryLockStrategy::Namespaced(AdvisoryLockKey::derived_from(
24 &entity_name,
25 &hierarchy_name,
26 ));
27
28 Self {
29 entity_name,
30 hierarchy_name,
31 parent_column: "parent_id".to_string(),
32 name_column: "name".to_string(),
33 hierarchy_table: String::new(),
34 dependent_behavior: DependentBehavior::default(),
35 order_strategy: None,
36 advisory_lock_strategy: default_lock,
37 }
38 }
39
40 pub(crate) fn apply_options(mut self, options: ClosureTreeOptions) -> Self {
42 if let Some(parent_column) = options.parent_column {
43 self.parent_column = parent_column;
44 }
45 if let Some(name_column) = options.name_column {
46 self.name_column = name_column;
47 }
48 if let Some(hierarchy_table) = options.hierarchy_table {
49 self.hierarchy_table = hierarchy_table;
50 }
51 if let Some(behavior) = options.dependent_behavior {
52 self.dependent_behavior = behavior;
53 }
54 if let Some(order_strategy) = options.order_strategy {
55 self.order_strategy = Some(order_strategy);
56 }
57 if let Some(strategy) = options.advisory_lock_strategy {
58 self.advisory_lock_strategy = strategy;
59 }
60 self
61 }
62
63 pub fn entity_name(&self) -> &str {
65 &self.entity_name
66 }
67
68 pub fn hierarchy_name(&self) -> &str {
70 &self.hierarchy_name
71 }
72
73 pub fn parent_column(&self) -> &str {
75 &self.parent_column
76 }
77
78 pub fn name_column(&self) -> &str {
80 &self.name_column
81 }
82
83 pub fn hierarchy_table(&self) -> &str {
85 &self.hierarchy_table
86 }
87
88 pub fn dependent_behavior(&self) -> DependentBehavior {
90 self.dependent_behavior
91 }
92
93 pub fn order_strategy(&self) -> Option<&OrderStrategy> {
95 self.order_strategy.as_ref()
96 }
97
98 pub fn advisory_lock_strategy(&self) -> &AdvisoryLockStrategy {
100 &self.advisory_lock_strategy
101 }
102}
103
104#[derive(Clone, Debug, Default)]
106pub struct ClosureTreeOptions {
107 parent_column: Option<String>,
108 name_column: Option<String>,
109 hierarchy_table: Option<String>,
110 dependent_behavior: Option<DependentBehavior>,
111 order_strategy: Option<OrderStrategy>,
112 advisory_lock_strategy: Option<AdvisoryLockStrategy>,
113}
114
115impl ClosureTreeOptions {
116 pub fn parent_column(mut self, value: impl Into<String>) -> Self {
117 self.parent_column = Some(value.into());
118 self
119 }
120
121 pub fn name_column(mut self, value: impl Into<String>) -> Self {
122 self.name_column = Some(value.into());
123 self
124 }
125
126 pub fn hierarchy_table(mut self, value: impl Into<String>) -> Self {
127 self.hierarchy_table = Some(value.into());
128 self
129 }
130
131 pub fn dependent_behavior(mut self, behavior: DependentBehavior) -> Self {
132 self.dependent_behavior = Some(behavior);
133 self
134 }
135
136 pub fn order_strategy(mut self, strategy: OrderStrategy) -> Self {
137 self.order_strategy = Some(strategy);
138 self
139 }
140
141 pub fn advisory_lock_strategy(mut self, strategy: AdvisoryLockStrategy) -> Self {
142 self.advisory_lock_strategy = Some(strategy);
143 self
144 }
145
146 pub fn apply(self, base: ClosureTreeConfig) -> ClosureTreeConfig {
147 base.apply_options(self)
148 }
149}
150
151#[derive(Copy, Clone, Debug, Eq, PartialEq)]
153pub enum DependentBehavior {
154 Nullify,
155 Destroy,
156 DeleteAll,
157 None,
158}
159
160impl Default for DependentBehavior {
161 fn default() -> Self {
162 Self::Nullify
163 }
164}
165
166#[derive(Clone, Debug, Eq, PartialEq)]
168pub enum OrderStrategy {
169 Manual,
170 NumericColumn { column: String },
171}
172
173impl OrderStrategy {
174 pub fn numeric_column(column: impl Into<String>) -> Self {
175 Self::NumericColumn {
176 column: column.into(),
177 }
178 }
179}
180
181#[derive(Clone, Debug, Eq, PartialEq, Hash)]
183pub struct AdvisoryLockKey(String);
184
185impl AdvisoryLockKey {
186 pub fn new(value: impl Into<String>) -> Self {
187 Self(value.into())
188 }
189
190 pub fn as_str(&self) -> &str {
191 self.0.as_str()
192 }
193
194 fn derived_from(entity: &str, hierarchy: &str) -> Self {
195 let mut hasher = Hasher::new();
196 hasher.update(entity.as_bytes());
197 hasher.update(b"/");
198 hasher.update(hierarchy.as_bytes());
199 let crc = hasher.finalize();
200 Self(format!("closure-tree::{entity}::{hierarchy}::{crc:x}"))
201 }
202}
203
204#[derive(Clone, Debug, Eq, PartialEq)]
206pub enum AdvisoryLockStrategy {
207 Disabled,
208 Namespaced(AdvisoryLockKey),
209}
210
211impl AdvisoryLockStrategy {
212 pub fn key(&self) -> Option<&AdvisoryLockKey> {
213 match self {
214 AdvisoryLockStrategy::Disabled => None,
215 AdvisoryLockStrategy::Namespaced(key) => Some(key),
216 }
217 }
218}