1use crate::ast::FunctionBinding;
10use crate::{
11 plan::{QueryPlan, RelationalPlan, SourcePlan},
12 ScalarExpr,
13};
14use uqa_core::{RelationIdentity, Value};
15
16pub fn canonical_virtual_relation_reference(reference: &str) -> Option<String> {
17 let (schema, relation) = RelationIdentity::parse_reference(reference).ok()?;
18 let relation = relation.to_ascii_lowercase();
19 let schema = schema.map(|schema| schema.to_ascii_lowercase());
20 let information_schema = matches!(
21 relation.as_str(),
22 "schemata"
23 | "tables"
24 | "columns"
25 | "column_privileges"
26 | "role_column_grants"
27 | "views"
28 | "routines"
29 | "sequences"
30 | "table_constraints"
31 | "key_column_usage"
32 );
33 let pg_catalog = matches!(
34 relation.as_str(),
35 "pg_namespace"
36 | "pg_class"
37 | "pg_inherits"
38 | "pg_partitioned_table"
39 | "pg_attribute"
40 | "pg_attrdef"
41 | "pg_constraint"
42 | "pg_index"
43 | "pg_tables"
44 | "pg_views"
45 | "pg_indexes"
46 | "pg_type"
47 | "pg_proc"
48 | "pg_database"
49 | "pg_roles"
50 | "pg_user"
51 | "pg_settings"
52 | "pg_description"
53 | "pg_matviews"
54 | "pg_sequences"
55 );
56 match schema.as_deref() {
57 Some("information_schema") if information_schema => {
58 Some(format!("information_schema.{relation}"))
59 }
60 Some("pg_catalog") | None if pg_catalog => Some(format!("pg_catalog.{relation}")),
61 _ => None,
62 }
63}
64
65pub fn sequence_function_reference_mut(expression: &mut ScalarExpr) -> Option<&mut String> {
66 let ScalarExpr::Func { name, args, .. } = expression else {
67 return None;
68 };
69 let lower = name.to_ascii_lowercase();
70 let local = lower.strip_prefix("pg_catalog.").unwrap_or(&lower);
71 if !matches!(local, "nextval" | "currval" | "setval")
72 || (lower.contains('.') && !lower.starts_with("pg_catalog."))
73 {
74 return None;
75 }
76 regclass_literal_mut(args.first_mut()?)
77}
78
79pub fn regclass_literal_mut(expression: &mut ScalarExpr) -> Option<&mut String> {
80 match expression {
81 ScalarExpr::Literal(Value::Str(reference)) => Some(reference),
82 ScalarExpr::Cast { expr, ty }
83 if ty.eq_ignore_ascii_case("regclass")
84 || ty.eq_ignore_ascii_case("pg_catalog.regclass") =>
85 {
86 regclass_literal_mut(expr)
87 }
88 _ => None,
89 }
90}
91
92pub fn bind_query_plan_sequence_references<E>(
93 plan: &mut QueryPlan,
94 resolve: &mut impl FnMut(&str) -> Result<String, E>,
95) -> Result<(), E> {
96 let mut error = None;
97 plan.rewrite_scalar_expressions(&mut |expression| {
98 if error.is_some() {
99 return;
100 }
101 let Some(reference) = sequence_function_reference_mut(expression) else {
102 return;
103 };
104 match resolve(reference) {
105 Ok(canonical) => *reference = canonical,
106 Err(binding_error) => error = Some(binding_error),
107 }
108 });
109 error.map_or(Ok(()), Err)
110}
111
112pub fn bind_query_plan_relations<E>(
113 plan: &mut QueryPlan,
114 inherited_ctes: &std::collections::BTreeSet<String>,
115 resolve: &mut impl FnMut(&str) -> Result<String, E>,
116) -> Result<(), E> {
117 let mut visible_ctes = inherited_ctes.clone();
119 let recursive_ctes = plan.ctes.iter().any(|cte| cte.recursive).then(|| {
120 plan.ctes
121 .iter()
122 .map(|cte| cte.name.clone())
123 .collect::<std::collections::BTreeSet<_>>()
124 });
125 for cte in &mut plan.ctes {
126 let body_ctes = recursive_ctes.as_ref().map_or_else(
127 || visible_ctes.clone(),
128 |ctes| inherited_ctes.union(ctes).cloned().collect(),
129 );
130 bind_cte_plan_relations(&mut cte.body, &body_ctes, resolve)?;
131 visible_ctes.insert(cte.name.clone());
132 }
133 bind_relational_plan_relations(&mut plan.root, &visible_ctes, resolve)?;
134 plan.relations_bound = true;
135 Ok(())
136}
137
138pub fn bind_relational_plan_relations<E>(
139 plan: &mut RelationalPlan,
140 visible_ctes: &std::collections::BTreeSet<String>,
141 resolve: &mut impl FnMut(&str) -> Result<String, E>,
142) -> Result<(), E> {
143 match plan {
144 RelationalPlan::QueryBlock(block) => {
145 if let Some(source) = &mut block.from {
146 bind_source_plan_relations(source, visible_ctes, resolve)?;
147 }
148 for subquery in &mut block.subqueries {
149 bind_query_plan_relations(subquery, visible_ctes, resolve)?;
150 }
151 }
152 RelationalPlan::SetOp {
153 left,
154 right,
155 subqueries,
156 ..
157 } => {
158 bind_query_plan_relations(left, visible_ctes, resolve)?;
159 bind_query_plan_relations(right, visible_ctes, resolve)?;
160 for subquery in subqueries {
161 bind_query_plan_relations(subquery, visible_ctes, resolve)?;
162 }
163 }
164 RelationalPlan::Values { subqueries, .. } => {
165 for subquery in subqueries {
166 bind_query_plan_relations(subquery, visible_ctes, resolve)?;
167 }
168 }
169 }
170 Ok(())
171}
172
173pub fn bind_source_plan_relations<E>(
174 source: &mut SourcePlan,
175 visible_ctes: &std::collections::BTreeSet<String>,
176 resolve: &mut impl FnMut(&str) -> Result<String, E>,
177) -> Result<(), E> {
178 match source {
179 SourcePlan::Table {
180 name, qualifier, ..
181 } => {
182 if qualifier.is_empty() {
183 *qualifier = RelationIdentity::parse_reference(name)
184 .map_or_else(|_| name.clone(), |(_, relation)| relation);
185 }
186 let is_cte =
187 RelationIdentity::parse_reference(name)
188 .ok()
189 .is_some_and(|(schema, relation)| {
190 schema.is_none() && visible_ctes.contains(&relation)
191 });
192 if !is_cte {
193 *name = resolve(name)?;
194 }
195 }
196 SourcePlan::Join { left, right, .. } => {
197 bind_source_plan_relations(left, visible_ctes, resolve)?;
198 bind_source_plan_relations(right, visible_ctes, resolve)?;
199 }
200 SourcePlan::Subquery { body, .. } => {
201 bind_query_plan_relations(body, visible_ctes, resolve)?;
202 }
203 SourcePlan::Function {
204 name,
205 output_name,
206 relations,
207 ..
208 } => {
209 if output_name.is_empty() {
210 *output_name = RelationIdentity::parse_reference(name)
211 .map_or_else(|_| name.clone(), |(_, function)| function);
212 }
213 if let Some(relations) = relations {
214 relations.left = resolve(&relations.left)?;
215 relations.right = resolve(&relations.right)?;
216 }
217 }
218 SourcePlan::FunctionGroup { functions, .. } => {
219 for function in functions {
220 if function.output_name.is_empty() {
221 function.output_name = RelationIdentity::parse_reference(&function.name)
222 .map_or_else(|_| function.name.clone(), |(_, name)| name);
223 }
224 if let Some(relations) = &mut function.relations {
225 relations.left = resolve(&relations.left)?;
226 relations.right = resolve(&relations.right)?;
227 }
228 }
229 }
230 SourcePlan::Values { .. } => {}
231 }
232 Ok(())
233}
234
235pub fn relation_reference_matches(reference: &str, target: &RelationIdentity) -> bool {
236 match RelationIdentity::parse_reference(reference) {
237 Ok((Some(schema), name)) => schema == target.schema && name == target.name,
238 Ok((None, name)) => name == target.name,
239 Err(_) => true,
242 }
243}
244
245pub fn source_plan_references_relation(
246 source: &crate::plan::SourcePlan,
247 target: &RelationIdentity,
248 ctes: &std::collections::BTreeSet<String>,
249) -> bool {
250 match source {
251 crate::plan::SourcePlan::Table { name, .. } => {
252 let is_cte = RelationIdentity::parse_reference(name)
253 .ok()
254 .is_some_and(|(schema, relation)| schema.is_none() && ctes.contains(&relation));
255 !is_cte && relation_reference_matches(name, target)
256 }
257 crate::plan::SourcePlan::Join { left, right, .. } => {
258 source_plan_references_relation(left, target, ctes)
259 || source_plan_references_relation(right, target, ctes)
260 }
261 crate::plan::SourcePlan::Subquery { body, .. } => {
262 query_plan_references_relation(body, target, ctes)
263 }
264 crate::plan::SourcePlan::Function { relations, .. } => {
265 relations.as_ref().is_some_and(|relations| {
266 relation_reference_matches(&relations.left, target)
267 || relation_reference_matches(&relations.right, target)
268 })
269 }
270 crate::plan::SourcePlan::FunctionGroup { functions, .. } => {
271 functions.iter().any(|function| {
272 function.relations.as_ref().is_some_and(|relations| {
273 relation_reference_matches(&relations.left, target)
274 || relation_reference_matches(&relations.right, target)
275 })
276 })
277 }
278 crate::plan::SourcePlan::Values { .. } => false,
279 }
280}
281
282pub fn query_plan_references_relation(
283 query: &crate::plan::QueryPlan,
284 target: &RelationIdentity,
285 inherited_ctes: &std::collections::BTreeSet<String>,
286) -> bool {
287 let mut ctes = inherited_ctes.clone();
288 ctes.extend(query.ctes.iter().map(|cte| cte.name.clone()));
289 if query
290 .ctes
291 .iter()
292 .any(|cte| cte_plan_references_relation(&cte.body, target, &ctes))
293 {
294 return true;
295 }
296 match &query.root {
297 crate::plan::RelationalPlan::QueryBlock(block) => {
298 block
299 .from
300 .as_ref()
301 .is_some_and(|source| source_plan_references_relation(source, target, &ctes))
302 || block
303 .subqueries
304 .iter()
305 .any(|query| query_plan_references_relation(query, target, &ctes))
306 }
307 crate::plan::RelationalPlan::SetOp {
308 left,
309 right,
310 subqueries,
311 ..
312 } => {
313 query_plan_references_relation(left, target, &ctes)
314 || query_plan_references_relation(right, target, &ctes)
315 || subqueries
316 .iter()
317 .any(|query| query_plan_references_relation(query, target, &ctes))
318 }
319 crate::plan::RelationalPlan::Values { subqueries, .. } => subqueries
320 .iter()
321 .any(|query| query_plan_references_relation(query, target, &ctes)),
322 }
323}
324
325pub fn query_plan_references_sequence(plan: &QueryPlan, target: &RelationIdentity) -> bool {
326 let mut plan = plan.clone();
327 let mut referenced = false;
328 plan.rewrite_scalar_expressions(&mut |expression| {
329 if let Some(reference) = sequence_function_reference_mut(expression) {
330 referenced |= relation_reference_matches(reference, target);
331 }
332 });
333 referenced
334}
335
336pub use crate::routines::function_binding_matches;
337
338fn function_binding_needs_object_identity(binding: &FunctionBinding) -> bool {
339 !binding.builtin
340 && binding.dispatch.is_none()
341 && binding.resolution_error.is_none()
342 && binding.object_id.is_none()
343}
344
345fn source_plan_has_legacy_routine_identity(source: &SourcePlan) -> bool {
346 match source {
347 SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
348 SourcePlan::Join { left, right, .. } => {
349 source_plan_has_legacy_routine_identity(left)
350 || source_plan_has_legacy_routine_identity(right)
351 }
352 SourcePlan::Subquery { body, .. } => query_plan_sources_have_legacy_routine_identity(body),
353 SourcePlan::Function { binding, .. } => binding
354 .as_ref()
355 .is_some_and(function_binding_needs_object_identity),
356 SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
357 function
358 .binding
359 .as_ref()
360 .is_some_and(function_binding_needs_object_identity)
361 }),
362 }
363}
364
365fn relational_plan_has_legacy_routine_identity(plan: &RelationalPlan) -> bool {
366 match plan {
367 RelationalPlan::QueryBlock(block) => {
368 block
369 .from
370 .as_ref()
371 .is_some_and(source_plan_has_legacy_routine_identity)
372 || block
373 .subqueries
374 .iter()
375 .any(query_plan_sources_have_legacy_routine_identity)
376 }
377 RelationalPlan::SetOp {
378 left,
379 right,
380 subqueries,
381 ..
382 } => {
383 query_plan_sources_have_legacy_routine_identity(left)
384 || query_plan_sources_have_legacy_routine_identity(right)
385 || subqueries
386 .iter()
387 .any(query_plan_sources_have_legacy_routine_identity)
388 }
389 RelationalPlan::Values { subqueries, .. } => subqueries
390 .iter()
391 .any(query_plan_sources_have_legacy_routine_identity),
392 }
393}
394
395fn query_plan_sources_have_legacy_routine_identity(plan: &QueryPlan) -> bool {
396 plan.ctes.iter().any(|cte| {
397 cte_relational_inputs_any(
398 &cte.body,
399 &query_plan_sources_have_legacy_routine_identity,
400 &source_plan_has_legacy_routine_identity,
401 )
402 }) || relational_plan_has_legacy_routine_identity(&plan.root)
403}
404
405pub fn query_plan_has_legacy_routine_identity(plan: &QueryPlan) -> bool {
406 let mut scalar_plan = plan.clone();
407 let mut legacy = false;
408 scalar_plan.rewrite_scalar_expressions(&mut |expression| {
409 if let ScalarExpr::Func {
410 binding: Some(binding),
411 ..
412 } = expression
413 {
414 legacy |= function_binding_needs_object_identity(binding);
415 }
416 });
417 legacy || query_plan_sources_have_legacy_routine_identity(plan)
418}
419
420fn source_plan_references_function(source: &SourcePlan, target: &FunctionBinding) -> bool {
421 match source {
422 SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
423 SourcePlan::Join { left, right, .. } => {
424 source_plan_references_function(left, target)
425 || source_plan_references_function(right, target)
426 }
427 SourcePlan::Subquery { body, .. } => query_plan_sources_reference_function(body, target),
428 SourcePlan::Function { binding, .. } => binding
429 .as_ref()
430 .is_some_and(|binding| function_binding_matches(binding, target)),
431 SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
432 function
433 .binding
434 .as_ref()
435 .is_some_and(|binding| function_binding_matches(binding, target))
436 }),
437 }
438}
439
440fn relational_plan_references_function(plan: &RelationalPlan, target: &FunctionBinding) -> bool {
441 match plan {
442 RelationalPlan::QueryBlock(block) => {
443 block
444 .from
445 .as_ref()
446 .is_some_and(|source| source_plan_references_function(source, target))
447 || block
448 .subqueries
449 .iter()
450 .any(|query| query_plan_sources_reference_function(query, target))
451 }
452 RelationalPlan::SetOp {
453 left,
454 right,
455 subqueries,
456 ..
457 } => {
458 query_plan_sources_reference_function(left, target)
459 || query_plan_sources_reference_function(right, target)
460 || subqueries
461 .iter()
462 .any(|query| query_plan_sources_reference_function(query, target))
463 }
464 RelationalPlan::Values { subqueries, .. } => subqueries
465 .iter()
466 .any(|query| query_plan_sources_reference_function(query, target)),
467 }
468}
469
470fn query_plan_sources_reference_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
471 plan.ctes.iter().any(|cte| {
472 cte_relational_inputs_any(
473 &cte.body,
474 &|query| query_plan_sources_reference_function(query, target),
475 &|source| source_plan_references_function(source, target),
476 )
477 }) || relational_plan_references_function(&plan.root, target)
478}
479
480pub fn query_plan_references_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
481 let mut scalar_plan = plan.clone();
482 let mut referenced = false;
483 scalar_plan.rewrite_scalar_expressions(&mut |expression| {
484 if let ScalarExpr::Func {
485 binding: Some(binding),
486 ..
487 } = expression
488 {
489 referenced |= function_binding_matches(binding, target);
490 }
491 });
492 referenced || query_plan_sources_reference_function(plan, target)
493}
494
495fn rewrite_source_plan_routine_identity(
496 source: &mut SourcePlan,
497 target: &FunctionBinding,
498 new_name: &str,
499) -> bool {
500 match source {
501 SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
502 SourcePlan::Join { left, right, .. } => {
503 rewrite_source_plan_routine_identity(left, target, new_name)
504 | rewrite_source_plan_routine_identity(right, target, new_name)
505 }
506 SourcePlan::Subquery { body, .. } => {
507 rewrite_query_source_routine_identity(body, target, new_name)
508 }
509 SourcePlan::Function { name, binding, .. } => {
510 let Some(binding) = binding.as_mut() else {
511 return false;
512 };
513 if !function_binding_matches(binding, target) {
514 return false;
515 }
516 *name = new_name.to_string();
517 binding.name = new_name.to_string();
518 true
519 }
520 SourcePlan::FunctionGroup { functions, .. } => {
521 let mut changed = false;
522 for function in functions {
523 let Some(binding) = function.binding.as_mut() else {
524 continue;
525 };
526 if function_binding_matches(binding, target) {
527 function.name = new_name.to_string();
528 binding.name = new_name.to_string();
529 changed = true;
530 }
531 }
532 changed
533 }
534 }
535}
536
537fn rewrite_relational_plan_source_routine_identity(
538 plan: &mut RelationalPlan,
539 target: &FunctionBinding,
540 new_name: &str,
541) -> bool {
542 match plan {
543 RelationalPlan::QueryBlock(block) => {
544 let mut changed = block.from.as_mut().is_some_and(|source| {
545 rewrite_source_plan_routine_identity(source, target, new_name)
546 });
547 for subquery in &mut block.subqueries {
548 changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
549 }
550 changed
551 }
552 RelationalPlan::SetOp {
553 left,
554 right,
555 subqueries,
556 ..
557 } => {
558 let mut changed = rewrite_query_source_routine_identity(left, target, new_name)
559 | rewrite_query_source_routine_identity(right, target, new_name);
560 for subquery in subqueries {
561 changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
562 }
563 changed
564 }
565 RelationalPlan::Values { subqueries, .. } => {
566 let mut changed = false;
567 for subquery in subqueries {
568 changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
569 }
570 changed
571 }
572 }
573}
574
575fn rewrite_query_source_routine_identity(
576 plan: &mut QueryPlan,
577 target: &FunctionBinding,
578 new_name: &str,
579) -> bool {
580 let mut changed = false;
581 for cte in &mut plan.ctes {
582 changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
583 }
584 changed | rewrite_relational_plan_source_routine_identity(&mut plan.root, target, new_name)
585}
586
587pub fn rewrite_query_plan_routine_identity(
588 plan: &mut QueryPlan,
589 target: &FunctionBinding,
590 new_name: &str,
591) -> bool {
592 let mut changed = false;
593 plan.rewrite_scalar_expressions(&mut |expression| {
594 let ScalarExpr::Func {
595 name,
596 binding: Some(binding),
597 ..
598 } = expression
599 else {
600 return;
601 };
602 if function_binding_matches(binding, target) {
603 *name = new_name.to_string();
604 binding.name = new_name.to_string();
605 changed = true;
606 }
607 });
608 changed | rewrite_query_source_routine_identity(plan, target, new_name)
609}
610
611pub fn bind_cte_plan_relations<E>(
612 body: &mut crate::plan::CtePlanBody,
613 inherited: &std::collections::BTreeSet<String>,
614 resolve: &mut impl FnMut(&str) -> Result<String, E>,
615) -> Result<(), E> {
616 let crate::plan::CtePlanBody::Command(command) = body else {
617 let crate::plan::CtePlanBody::Query(query) = body else {
618 unreachable!()
619 };
620 return bind_query_plan_relations(query, inherited, resolve);
621 };
622 if let Some(target) = command.mutation_target_mut() {
623 *target = resolve(target)?;
624 }
625 match command.as_mut() {
626 crate::plan::CommandPlan::Insert(plan) => {
627 plan.target_relation_bound = true;
628 plan.relations_bound = true;
629 }
630 crate::plan::CommandPlan::Update(plan) => {
631 plan.target_relation_bound = true;
632 plan.relations_bound = true;
633 }
634 crate::plan::CommandPlan::Delete(plan) => {
635 plan.target_relation_bound = true;
636 plan.relations_bound = true;
637 }
638 _ => {}
639 }
640 let mut visible = inherited.clone();
641 if let Some(ctes) = command.ctes_mut() {
642 let recursive = ctes.iter().any(|cte| cte.recursive).then(|| {
643 ctes.iter()
644 .map(|cte| cte.name.clone())
645 .collect::<std::collections::BTreeSet<_>>()
646 });
647 for cte in ctes {
648 let scope = recursive.as_ref().map_or_else(
649 || visible.clone(),
650 |names| inherited.union(names).cloned().collect(),
651 );
652 bind_cte_plan_relations(&mut cte.body, &scope, resolve)?;
653 visible.insert(cte.name.clone());
654 }
655 }
656 if let Some(source) = command.source_input_mut() {
657 bind_source_plan_relations(source, &visible, resolve)?;
658 }
659 for query in command.query_inputs_mut() {
660 bind_query_plan_relations(query, &visible, resolve)?;
661 }
662 Ok(())
663}
664
665fn cte_plan_references_relation(
666 body: &crate::plan::CtePlanBody,
667 target: &RelationIdentity,
668 inherited: &std::collections::BTreeSet<String>,
669) -> bool {
670 match body {
671 crate::plan::CtePlanBody::Query(query) => {
672 query_plan_references_relation(query, target, inherited)
673 }
674 crate::plan::CtePlanBody::Command(command) => {
675 let mut visible = inherited.clone();
676 visible.extend(command.ctes().iter().map(|cte| cte.name.clone()));
677 command
678 .mutation_target()
679 .is_some_and(|name| relation_reference_matches(name, target))
680 || command
681 .ctes()
682 .iter()
683 .any(|cte| cte_plan_references_relation(&cte.body, target, &visible))
684 || command
685 .source_input()
686 .is_some_and(|source| source_plan_references_relation(source, target, &visible))
687 || command
688 .query_inputs()
689 .iter()
690 .any(|query| query_plan_references_relation(query, target, &visible))
691 }
692 }
693}
694
695fn cte_relational_inputs_any(
696 body: &crate::plan::CtePlanBody,
697 query: &dyn Fn(&QueryPlan) -> bool,
698 source: &dyn Fn(&crate::plan::SourcePlan) -> bool,
699) -> bool {
700 match body {
701 crate::plan::CtePlanBody::Query(plan) => query(plan),
702 crate::plan::CtePlanBody::Command(command) => {
703 command
704 .ctes()
705 .iter()
706 .any(|cte| cte_relational_inputs_any(&cte.body, query, source))
707 || command.query_inputs().iter().any(|plan| query(plan))
708 || command.source_input().is_some_and(source)
709 }
710 }
711}
712
713fn rewrite_cte_source_routine_identity(
714 body: &mut crate::plan::CtePlanBody,
715 target: &FunctionBinding,
716 new_name: &str,
717) -> bool {
718 match body {
719 crate::plan::CtePlanBody::Query(query) => {
720 rewrite_query_source_routine_identity(query, target, new_name)
721 }
722 crate::plan::CtePlanBody::Command(command) => {
723 let mut changed = false;
724 if let Some(ctes) = command.ctes_mut() {
725 for cte in ctes {
726 changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
727 }
728 }
729 if let Some(source) = command.source_input_mut() {
730 changed |= rewrite_source_plan_routine_identity(source, target, new_name);
731 }
732 for query in command.query_inputs_mut() {
733 changed |= rewrite_query_source_routine_identity(query, target, new_name);
734 }
735 changed
736 }
737 }
738}
739
740pub mod restoration;