1#![allow(
7 dead_code,
8 reason = "DDL binding exposes prepare-only diagnostics and test-only inspection accessors"
9)]
10
11use crate::db::{
12 schema::{
13 AcceptedSchemaSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
14 SchemaDdlAcceptedSnapshotDerivation, SchemaDdlIndexDropCandidateError,
15 SchemaDdlMutationAdmission, SchemaDdlMutationAdmissionError, SchemaInfo,
16 admit_sql_ddl_field_path_index_candidate, admit_sql_ddl_secondary_index_drop_candidate,
17 derive_sql_ddl_field_path_index_accepted_after,
18 derive_sql_ddl_secondary_index_drop_accepted_after,
19 resolve_sql_ddl_secondary_index_drop_candidate,
20 },
21 sql::{
22 identifier::identifiers_tail_match,
23 parser::{
24 SqlCreateIndexStatement, SqlCreateIndexUniqueness, SqlDdlStatement,
25 SqlDropIndexStatement, SqlStatement,
26 },
27 },
28};
29use thiserror::Error as ThisError;
30
31#[derive(Clone, Debug, Eq, PartialEq)]
39pub(in crate::db) struct PreparedSqlDdlCommand {
40 bound: BoundSqlDdlRequest,
41 derivation: Option<SchemaDdlAcceptedSnapshotDerivation>,
42 report: SqlDdlPreparationReport,
43}
44
45impl PreparedSqlDdlCommand {
46 #[must_use]
48 pub(in crate::db) const fn bound(&self) -> &BoundSqlDdlRequest {
49 &self.bound
50 }
51
52 #[must_use]
54 pub(in crate::db) const fn derivation(&self) -> Option<&SchemaDdlAcceptedSnapshotDerivation> {
55 self.derivation.as_ref()
56 }
57
58 #[must_use]
60 pub(in crate::db) const fn report(&self) -> &SqlDdlPreparationReport {
61 &self.report
62 }
63
64 #[must_use]
66 pub(in crate::db) const fn mutates_schema(&self) -> bool {
67 self.derivation.is_some()
68 }
69}
70
71#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct SqlDdlPreparationReport {
79 mutation_kind: SqlDdlMutationKind,
80 target_index: String,
81 target_store: String,
82 field_path: Vec<String>,
83 execution_status: SqlDdlExecutionStatus,
84 rows_scanned: usize,
85 index_keys_written: usize,
86}
87
88impl SqlDdlPreparationReport {
89 #[must_use]
91 pub const fn mutation_kind(&self) -> SqlDdlMutationKind {
92 self.mutation_kind
93 }
94
95 #[must_use]
97 pub const fn target_index(&self) -> &str {
98 self.target_index.as_str()
99 }
100
101 #[must_use]
103 pub const fn target_store(&self) -> &str {
104 self.target_store.as_str()
105 }
106
107 #[must_use]
109 pub const fn field_path(&self) -> &[String] {
110 self.field_path.as_slice()
111 }
112
113 #[must_use]
115 pub const fn execution_status(&self) -> SqlDdlExecutionStatus {
116 self.execution_status
117 }
118
119 #[must_use]
121 pub const fn rows_scanned(&self) -> usize {
122 self.rows_scanned
123 }
124
125 #[must_use]
127 pub const fn index_keys_written(&self) -> usize {
128 self.index_keys_written
129 }
130
131 pub(in crate::db) const fn with_execution_status(
132 mut self,
133 execution_status: SqlDdlExecutionStatus,
134 ) -> Self {
135 self.execution_status = execution_status;
136 self
137 }
138
139 pub(in crate::db) const fn with_execution_metrics(
140 mut self,
141 rows_scanned: usize,
142 index_keys_written: usize,
143 ) -> Self {
144 self.rows_scanned = rows_scanned;
145 self.index_keys_written = index_keys_written;
146 self
147 }
148}
149
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
156pub enum SqlDdlMutationKind {
157 AddFieldPathIndex,
158 DropSecondaryIndex,
159}
160
161impl SqlDdlMutationKind {
162 #[must_use]
164 pub const fn as_str(self) -> &'static str {
165 match self {
166 Self::AddFieldPathIndex => "add_field_path_index",
167 Self::DropSecondaryIndex => "drop_secondary_index",
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub enum SqlDdlExecutionStatus {
179 PreparedOnly,
180 Published,
181 NoOp,
182}
183
184impl SqlDdlExecutionStatus {
185 #[must_use]
187 pub const fn as_str(self) -> &'static str {
188 match self {
189 Self::PreparedOnly => "prepared_only",
190 Self::Published => "published",
191 Self::NoOp => "no_op",
192 }
193 }
194}
195
196#[derive(Clone, Debug, Eq, PartialEq)]
203pub(in crate::db) struct BoundSqlDdlRequest {
204 statement: BoundSqlDdlStatement,
205}
206
207impl BoundSqlDdlRequest {
208 #[must_use]
210 pub(in crate::db) const fn statement(&self) -> &BoundSqlDdlStatement {
211 &self.statement
212 }
213}
214
215#[derive(Clone, Debug, Eq, PartialEq)]
221pub(in crate::db) enum BoundSqlDdlStatement {
222 CreateIndex(BoundSqlCreateIndexRequest),
223 DropIndex(BoundSqlDropIndexRequest),
224 NoOp(BoundSqlDdlNoOpRequest),
225}
226
227#[derive(Clone, Debug, Eq, PartialEq)]
233pub(in crate::db) struct BoundSqlDdlNoOpRequest {
234 mutation_kind: SqlDdlMutationKind,
235 index_name: String,
236 entity_name: String,
237 target_store: String,
238 field_path: Vec<String>,
239}
240
241impl BoundSqlDdlNoOpRequest {
242 #[must_use]
244 pub(in crate::db) const fn mutation_kind(&self) -> SqlDdlMutationKind {
245 self.mutation_kind
246 }
247
248 #[must_use]
250 pub(in crate::db) const fn index_name(&self) -> &str {
251 self.index_name.as_str()
252 }
253
254 #[must_use]
256 pub(in crate::db) const fn entity_name(&self) -> &str {
257 self.entity_name.as_str()
258 }
259
260 #[must_use]
262 pub(in crate::db) const fn target_store(&self) -> &str {
263 self.target_store.as_str()
264 }
265
266 #[must_use]
268 pub(in crate::db) const fn field_path(&self) -> &[String] {
269 self.field_path.as_slice()
270 }
271}
272
273#[derive(Clone, Debug, Eq, PartialEq)]
279pub(in crate::db) struct BoundSqlCreateIndexRequest {
280 index_name: String,
281 entity_name: String,
282 field_paths: Vec<BoundSqlDdlFieldPath>,
283 candidate_index: PersistedIndexSnapshot,
284}
285
286impl BoundSqlCreateIndexRequest {
287 #[must_use]
289 pub(in crate::db) const fn index_name(&self) -> &str {
290 self.index_name.as_str()
291 }
292
293 #[must_use]
295 pub(in crate::db) const fn entity_name(&self) -> &str {
296 self.entity_name.as_str()
297 }
298
299 #[must_use]
301 pub(in crate::db) const fn field_paths(&self) -> &[BoundSqlDdlFieldPath] {
302 self.field_paths.as_slice()
303 }
304
305 #[must_use]
307 pub(in crate::db) const fn candidate_index(&self) -> &PersistedIndexSnapshot {
308 &self.candidate_index
309 }
310}
311
312#[derive(Clone, Debug, Eq, PartialEq)]
318pub(in crate::db) struct BoundSqlDropIndexRequest {
319 index_name: String,
320 entity_name: String,
321 dropped_index: PersistedIndexSnapshot,
322 field_path: Vec<String>,
323}
324
325impl BoundSqlDropIndexRequest {
326 #[must_use]
328 pub(in crate::db) const fn index_name(&self) -> &str {
329 self.index_name.as_str()
330 }
331
332 #[must_use]
334 pub(in crate::db) const fn entity_name(&self) -> &str {
335 self.entity_name.as_str()
336 }
337
338 #[must_use]
340 pub(in crate::db) const fn dropped_index(&self) -> &PersistedIndexSnapshot {
341 &self.dropped_index
342 }
343
344 #[must_use]
346 pub(in crate::db) const fn field_path(&self) -> &[String] {
347 self.field_path.as_slice()
348 }
349}
350
351#[derive(Clone, Debug, Eq, PartialEq)]
357pub(in crate::db) struct BoundSqlDdlFieldPath {
358 root: String,
359 segments: Vec<String>,
360 accepted_path: Vec<String>,
361}
362
363impl BoundSqlDdlFieldPath {
364 #[must_use]
366 pub(in crate::db) const fn root(&self) -> &str {
367 self.root.as_str()
368 }
369
370 #[must_use]
372 pub(in crate::db) const fn segments(&self) -> &[String] {
373 self.segments.as_slice()
374 }
375
376 #[must_use]
378 pub(in crate::db) const fn accepted_path(&self) -> &[String] {
379 self.accepted_path.as_slice()
380 }
381}
382
383#[derive(Debug, Eq, PartialEq, ThisError)]
389pub(in crate::db) enum SqlDdlBindError {
390 #[error("SQL DDL binder requires a DDL statement")]
391 NotDdl,
392
393 #[error("accepted schema does not expose an entity name")]
394 MissingEntityName,
395
396 #[error("accepted schema does not expose an entity path")]
397 MissingEntityPath,
398
399 #[error("SQL entity '{sql_entity}' does not match accepted entity '{expected_entity}'")]
400 EntityMismatch {
401 sql_entity: String,
402 expected_entity: String,
403 },
404
405 #[error("unknown field path '{field_path}' for accepted entity '{entity_name}'")]
406 UnknownFieldPath {
407 entity_name: String,
408 field_path: String,
409 },
410
411 #[error("field path '{field_path}' is not indexable")]
412 FieldPathNotIndexable { field_path: String },
413
414 #[error("field path '{field_path}' depends on generated-only metadata")]
415 FieldPathNotAcceptedCatalogBacked { field_path: String },
416
417 #[error("index name '{index_name}' already exists in the accepted schema")]
418 DuplicateIndexName { index_name: String },
419
420 #[error("accepted schema already has index '{existing_index}' for field path '{field_path}'")]
421 DuplicateFieldPathIndex {
422 field_path: String,
423 existing_index: String,
424 },
425
426 #[error("unknown index '{index_name}' for accepted entity '{entity_name}'")]
427 UnknownIndex {
428 entity_name: String,
429 index_name: String,
430 },
431
432 #[error(
433 "index '{index_name}' is generated by the entity model and cannot be dropped with SQL DDL; remove the index from the entity schema macro instead"
434 )]
435 GeneratedIndexDropRejected { index_name: String },
436
437 #[error(
438 "index '{index_name}' is not a supported DDL-droppable field-path index; SQL DDL can currently drop only field-path indexes created through SQL DDL"
439 )]
440 UnsupportedDropIndex { index_name: String },
441}
442
443#[derive(Debug, Eq, PartialEq, ThisError)]
450pub(in crate::db) enum SqlDdlLoweringError {
451 #[error("SQL DDL lowering requires a supported DDL statement")]
452 UnsupportedStatement,
453
454 #[error("schema mutation admission rejected DDL candidate: {0:?}")]
455 MutationAdmission(SchemaDdlMutationAdmissionError),
456}
457
458#[derive(Debug, Eq, PartialEq, ThisError)]
464pub(in crate::db) enum SqlDdlPrepareError {
465 #[error("{0}")]
466 Bind(#[from] SqlDdlBindError),
467
468 #[error("{0}")]
469 Lowering(#[from] SqlDdlLoweringError),
470}
471
472pub(in crate::db) fn prepare_sql_ddl_statement(
474 statement: &SqlStatement,
475 accepted_before: &AcceptedSchemaSnapshot,
476 schema: &SchemaInfo,
477 index_store_path: &'static str,
478) -> Result<PreparedSqlDdlCommand, SqlDdlPrepareError> {
479 let bound = bind_sql_ddl_statement(statement, accepted_before, schema, index_store_path)?;
480 let derivation = if matches!(bound.statement(), BoundSqlDdlStatement::NoOp(_)) {
481 None
482 } else {
483 Some(derive_bound_sql_ddl_accepted_after(
484 accepted_before,
485 &bound,
486 )?)
487 };
488 let report = ddl_preparation_report(&bound);
489
490 Ok(PreparedSqlDdlCommand {
491 bound,
492 derivation,
493 report,
494 })
495}
496
497pub(in crate::db) fn bind_sql_ddl_statement(
499 statement: &SqlStatement,
500 accepted_before: &AcceptedSchemaSnapshot,
501 schema: &SchemaInfo,
502 index_store_path: &'static str,
503) -> Result<BoundSqlDdlRequest, SqlDdlBindError> {
504 let SqlStatement::Ddl(ddl) = statement else {
505 return Err(SqlDdlBindError::NotDdl);
506 };
507
508 match ddl {
509 SqlDdlStatement::CreateIndex(statement) => {
510 bind_create_index_statement(statement, schema, index_store_path)
511 }
512 SqlDdlStatement::DropIndex(statement) => {
513 bind_drop_index_statement(statement, accepted_before, schema)
514 }
515 }
516}
517
518fn bind_create_index_statement(
519 statement: &SqlCreateIndexStatement,
520 schema: &SchemaInfo,
521 index_store_path: &'static str,
522) -> Result<BoundSqlDdlRequest, SqlDdlBindError> {
523 let entity_name = schema
524 .entity_name()
525 .ok_or(SqlDdlBindError::MissingEntityName)?;
526
527 if !identifiers_tail_match(statement.entity.as_str(), entity_name) {
528 return Err(SqlDdlBindError::EntityMismatch {
529 sql_entity: statement.entity.clone(),
530 expected_entity: entity_name.to_string(),
531 });
532 }
533
534 let field_paths = statement
535 .field_paths
536 .iter()
537 .map(|field_path| bind_create_index_field_path(field_path.as_str(), entity_name, schema))
538 .collect::<Result<Vec<_>, _>>()?;
539 if let Some(existing_index) = find_field_path_index_by_name(schema, statement.name.as_str()) {
540 if statement.if_not_exists
541 && existing_field_path_index_matches_request(
542 existing_index,
543 field_paths.as_slice(),
544 statement.uniqueness,
545 )
546 {
547 return Ok(BoundSqlDdlRequest {
548 statement: BoundSqlDdlStatement::NoOp(BoundSqlDdlNoOpRequest {
549 mutation_kind: SqlDdlMutationKind::AddFieldPathIndex,
550 index_name: statement.name.clone(),
551 entity_name: entity_name.to_string(),
552 target_store: existing_index.store().to_string(),
553 field_path: ddl_field_path_report(field_paths.as_slice()),
554 }),
555 });
556 }
557
558 return Err(SqlDdlBindError::DuplicateIndexName {
559 index_name: statement.name.clone(),
560 });
561 }
562 reject_duplicate_expression_index_name(statement.name.as_str(), schema)?;
563 reject_duplicate_field_path_index(field_paths.as_slice(), schema)?;
564 let candidate_index = candidate_index_snapshot(
565 statement.name.as_str(),
566 field_paths.as_slice(),
567 statement.uniqueness,
568 schema,
569 index_store_path,
570 )?;
571
572 Ok(BoundSqlDdlRequest {
573 statement: BoundSqlDdlStatement::CreateIndex(BoundSqlCreateIndexRequest {
574 index_name: statement.name.clone(),
575 entity_name: entity_name.to_string(),
576 field_paths,
577 candidate_index,
578 }),
579 })
580}
581
582fn bind_drop_index_statement(
583 statement: &SqlDropIndexStatement,
584 accepted_before: &AcceptedSchemaSnapshot,
585 schema: &SchemaInfo,
586) -> Result<BoundSqlDdlRequest, SqlDdlBindError> {
587 let entity_name = schema
588 .entity_name()
589 .ok_or(SqlDdlBindError::MissingEntityName)?;
590
591 if !identifiers_tail_match(statement.entity.as_str(), entity_name) {
592 return Err(SqlDdlBindError::EntityMismatch {
593 sql_entity: statement.entity.clone(),
594 expected_entity: entity_name.to_string(),
595 });
596 }
597 let drop_candidate = resolve_sql_ddl_secondary_index_drop_candidate(
598 accepted_before,
599 &statement.name,
600 )
601 .map_err(|error| match error {
602 SchemaDdlIndexDropCandidateError::Generated => {
603 SqlDdlBindError::GeneratedIndexDropRejected {
604 index_name: statement.name.clone(),
605 }
606 }
607 SchemaDdlIndexDropCandidateError::Unknown => SqlDdlBindError::UnknownIndex {
608 entity_name: entity_name.to_string(),
609 index_name: statement.name.clone(),
610 },
611 SchemaDdlIndexDropCandidateError::Unsupported => SqlDdlBindError::UnsupportedDropIndex {
612 index_name: statement.name.clone(),
613 },
614 });
615 let (dropped_index, field_path) = match drop_candidate {
616 Ok((dropped_index, field_path)) => (dropped_index, field_path),
617 Err(SqlDdlBindError::UnknownIndex { .. }) if statement.if_exists => {
618 return Ok(BoundSqlDdlRequest {
619 statement: BoundSqlDdlStatement::NoOp(BoundSqlDdlNoOpRequest {
620 mutation_kind: SqlDdlMutationKind::DropSecondaryIndex,
621 index_name: statement.name.clone(),
622 entity_name: entity_name.to_string(),
623 target_store: "-".to_string(),
624 field_path: Vec::new(),
625 }),
626 });
627 }
628 Err(error) => return Err(error),
629 };
630 Ok(BoundSqlDdlRequest {
631 statement: BoundSqlDdlStatement::DropIndex(BoundSqlDropIndexRequest {
632 index_name: statement.name.clone(),
633 entity_name: entity_name.to_string(),
634 dropped_index,
635 field_path,
636 }),
637 })
638}
639
640fn bind_create_index_field_path(
641 field_path: &str,
642 entity_name: &str,
643 schema: &SchemaInfo,
644) -> Result<BoundSqlDdlFieldPath, SqlDdlBindError> {
645 let mut path = field_path
646 .split('.')
647 .map(str::trim)
648 .filter(|segment| !segment.is_empty());
649 let Some(root) = path.next() else {
650 return Err(SqlDdlBindError::UnknownFieldPath {
651 entity_name: entity_name.to_string(),
652 field_path: field_path.to_string(),
653 });
654 };
655 let segments = path.map(str::to_string).collect::<Vec<_>>();
656
657 let capabilities = if segments.is_empty() {
658 schema.sql_capabilities(root)
659 } else {
660 schema.nested_sql_capabilities(root, segments.as_slice())
661 }
662 .ok_or_else(|| SqlDdlBindError::UnknownFieldPath {
663 entity_name: entity_name.to_string(),
664 field_path: field_path.to_string(),
665 })?;
666
667 if !capabilities.orderable() {
668 return Err(SqlDdlBindError::FieldPathNotIndexable {
669 field_path: field_path.to_string(),
670 });
671 }
672
673 let mut accepted_path = Vec::with_capacity(segments.len() + 1);
674 accepted_path.push(root.to_string());
675 accepted_path.extend(segments.iter().cloned());
676
677 Ok(BoundSqlDdlFieldPath {
678 root: root.to_string(),
679 segments,
680 accepted_path,
681 })
682}
683
684fn find_field_path_index_by_name<'a>(
685 schema: &'a SchemaInfo,
686 index_name: &str,
687) -> Option<&'a crate::db::schema::SchemaIndexInfo> {
688 schema
689 .field_path_indexes()
690 .iter()
691 .find(|index| index.name() == index_name)
692}
693
694fn existing_field_path_index_matches_request(
695 index: &crate::db::schema::SchemaIndexInfo,
696 field_paths: &[BoundSqlDdlFieldPath],
697 uniqueness: SqlCreateIndexUniqueness,
698) -> bool {
699 let fields = index.fields();
700
701 index.unique() == matches!(uniqueness, SqlCreateIndexUniqueness::Unique)
702 && fields.len() == field_paths.len()
703 && fields
704 .iter()
705 .zip(field_paths)
706 .all(|(field, requested)| field.path() == requested.accepted_path())
707}
708
709fn reject_duplicate_expression_index_name(
710 index_name: &str,
711 schema: &SchemaInfo,
712) -> Result<(), SqlDdlBindError> {
713 if schema
714 .expression_indexes()
715 .iter()
716 .any(|index| index.name() == index_name)
717 {
718 return Err(SqlDdlBindError::DuplicateIndexName {
719 index_name: index_name.to_string(),
720 });
721 }
722
723 Ok(())
724}
725
726fn reject_duplicate_field_path_index(
727 field_paths: &[BoundSqlDdlFieldPath],
728 schema: &SchemaInfo,
729) -> Result<(), SqlDdlBindError> {
730 let Some(existing_index) = schema.field_path_indexes().iter().find(|index| {
731 let fields = index.fields();
732 fields.len() == field_paths.len()
733 && fields
734 .iter()
735 .zip(field_paths)
736 .all(|(field, requested)| field.path() == requested.accepted_path())
737 }) else {
738 return Ok(());
739 };
740
741 Err(SqlDdlBindError::DuplicateFieldPathIndex {
742 field_path: ddl_field_path_report(field_paths).join(","),
743 existing_index: existing_index.name().to_string(),
744 })
745}
746
747fn candidate_index_snapshot(
748 index_name: &str,
749 field_paths: &[BoundSqlDdlFieldPath],
750 uniqueness: SqlCreateIndexUniqueness,
751 schema: &SchemaInfo,
752 index_store_path: &'static str,
753) -> Result<PersistedIndexSnapshot, SqlDdlBindError> {
754 let keys = field_paths
755 .iter()
756 .map(|field_path| {
757 schema
758 .accepted_index_field_path_snapshot(field_path.root(), field_path.segments())
759 .ok_or_else(|| SqlDdlBindError::FieldPathNotAcceptedCatalogBacked {
760 field_path: field_path.accepted_path().join("."),
761 })
762 })
763 .collect::<Result<Vec<_>, _>>()?;
764
765 Ok(PersistedIndexSnapshot::new_sql_ddl(
766 schema.next_secondary_index_ordinal(),
767 index_name.to_string(),
768 index_store_path.to_string(),
769 matches!(uniqueness, SqlCreateIndexUniqueness::Unique),
770 PersistedIndexKeySnapshot::FieldPath(keys),
771 None,
772 ))
773}
774
775fn ddl_field_path_report(field_paths: &[BoundSqlDdlFieldPath]) -> Vec<String> {
776 match field_paths {
777 [field_path] => field_path.accepted_path().to_vec(),
778 _ => vec![
779 field_paths
780 .iter()
781 .map(|field_path| field_path.accepted_path().join("."))
782 .collect::<Vec<_>>()
783 .join(","),
784 ],
785 }
786}
787
788pub(in crate::db) fn lower_bound_sql_ddl_to_schema_mutation_admission(
790 request: &BoundSqlDdlRequest,
791) -> Result<SchemaDdlMutationAdmission, SqlDdlLoweringError> {
792 match request.statement() {
793 BoundSqlDdlStatement::CreateIndex(create) => {
794 admit_sql_ddl_field_path_index_candidate(create.candidate_index())
795 }
796 BoundSqlDdlStatement::DropIndex(drop) => {
797 admit_sql_ddl_secondary_index_drop_candidate(drop.dropped_index())
798 }
799 BoundSqlDdlStatement::NoOp(_) => return Err(SqlDdlLoweringError::UnsupportedStatement),
800 }
801 .map_err(SqlDdlLoweringError::MutationAdmission)
802}
803
804pub(in crate::db) fn derive_bound_sql_ddl_accepted_after(
806 accepted_before: &AcceptedSchemaSnapshot,
807 request: &BoundSqlDdlRequest,
808) -> Result<SchemaDdlAcceptedSnapshotDerivation, SqlDdlLoweringError> {
809 match request.statement() {
810 BoundSqlDdlStatement::CreateIndex(create) => {
811 derive_sql_ddl_field_path_index_accepted_after(
812 accepted_before,
813 create.candidate_index().clone(),
814 )
815 }
816 BoundSqlDdlStatement::DropIndex(drop) => {
817 derive_sql_ddl_secondary_index_drop_accepted_after(
818 accepted_before,
819 drop.dropped_index(),
820 )
821 }
822 BoundSqlDdlStatement::NoOp(_) => return Err(SqlDdlLoweringError::UnsupportedStatement),
823 }
824 .map_err(SqlDdlLoweringError::MutationAdmission)
825}
826
827fn ddl_preparation_report(bound: &BoundSqlDdlRequest) -> SqlDdlPreparationReport {
828 match bound.statement() {
829 BoundSqlDdlStatement::CreateIndex(create) => {
830 let target = create.candidate_index();
831
832 SqlDdlPreparationReport {
833 mutation_kind: SqlDdlMutationKind::AddFieldPathIndex,
834 target_index: target.name().to_string(),
835 target_store: target.store().to_string(),
836 field_path: ddl_field_path_report(create.field_paths()),
837 execution_status: SqlDdlExecutionStatus::PreparedOnly,
838 rows_scanned: 0,
839 index_keys_written: 0,
840 }
841 }
842 BoundSqlDdlStatement::DropIndex(drop) => SqlDdlPreparationReport {
843 mutation_kind: SqlDdlMutationKind::DropSecondaryIndex,
844 target_index: drop.index_name().to_string(),
845 target_store: drop.dropped_index().store().to_string(),
846 field_path: drop.field_path().to_vec(),
847 execution_status: SqlDdlExecutionStatus::PreparedOnly,
848 rows_scanned: 0,
849 index_keys_written: 0,
850 },
851 BoundSqlDdlStatement::NoOp(no_op) => SqlDdlPreparationReport {
852 mutation_kind: no_op.mutation_kind(),
853 target_index: no_op.index_name().to_string(),
854 target_store: no_op.target_store().to_string(),
855 field_path: no_op.field_path().to_vec(),
856 execution_status: SqlDdlExecutionStatus::PreparedOnly,
857 rows_scanned: 0,
858 index_keys_written: 0,
859 },
860 }
861}