1#![allow(
7 dead_code,
8 reason = "DDL binding exposes prepare-only diagnostics and test-only inspection accessors"
9)]
10
11mod field;
12pub(in crate::db) use field::{
13 BoundSqlAddColumnRequest, BoundSqlAlterColumnDefaultRequest,
14 BoundSqlAlterColumnNullabilityRequest, BoundSqlDropColumnRequest, BoundSqlRenameColumnRequest,
15};
16use field::{
17 bind_alter_table_add_column_statement, bind_alter_table_alter_column_statement,
18 bind_alter_table_drop_column_statement, bind_alter_table_rename_column_statement,
19};
20
21mod index;
22pub(in crate::db) use index::{BoundSqlCreateIndexRequest, BoundSqlDropIndexRequest};
23use index::{bind_create_index_statement, bind_drop_index_statement, ddl_key_item_report};
24
25use crate::db::{
26 schema::{
27 AcceptedSchemaSnapshot, SchemaDdlAcceptedSnapshotDerivation, SchemaDdlMutationAdmission,
28 SchemaDdlMutationAdmissionError, SchemaInfo, admit_sql_ddl_expression_index_candidate,
29 admit_sql_ddl_field_addition_candidate, admit_sql_ddl_field_default_candidate,
30 admit_sql_ddl_field_drop_candidate, admit_sql_ddl_field_nullability_candidate,
31 admit_sql_ddl_field_path_index_candidate, admit_sql_ddl_field_rename_candidate,
32 admit_sql_ddl_secondary_index_drop_candidate,
33 derive_sql_ddl_expression_index_accepted_after,
34 derive_sql_ddl_field_addition_accepted_after, derive_sql_ddl_field_default_accepted_after,
35 derive_sql_ddl_field_drop_accepted_after, derive_sql_ddl_field_nullability_accepted_after,
36 derive_sql_ddl_field_path_index_accepted_after, derive_sql_ddl_field_rename_accepted_after,
37 derive_sql_ddl_secondary_index_drop_accepted_after,
38 },
39 sql::parser::{SqlDdlStatement, SqlStatement},
40};
41use thiserror::Error as ThisError;
42
43#[derive(Clone, Debug, Eq, PartialEq)]
51pub(in crate::db) struct PreparedSqlDdlCommand {
52 bound: BoundSqlDdlRequest,
53 derivation: Option<SchemaDdlAcceptedSnapshotDerivation>,
54 report: SqlDdlPreparationReport,
55}
56
57impl PreparedSqlDdlCommand {
58 #[must_use]
60 pub(in crate::db) const fn bound(&self) -> &BoundSqlDdlRequest {
61 &self.bound
62 }
63
64 #[must_use]
66 pub(in crate::db) const fn derivation(&self) -> Option<&SchemaDdlAcceptedSnapshotDerivation> {
67 self.derivation.as_ref()
68 }
69
70 #[must_use]
72 pub(in crate::db) const fn report(&self) -> &SqlDdlPreparationReport {
73 &self.report
74 }
75
76 #[must_use]
78 pub(in crate::db) const fn mutates_schema(&self) -> bool {
79 self.derivation.is_some()
80 }
81}
82
83#[derive(Clone, Debug, Eq, PartialEq)]
90pub struct SqlDdlPreparationReport {
91 mutation_kind: SqlDdlMutationKind,
92 target_index: String,
93 target_store: String,
94 field_path: Vec<String>,
95 execution_status: SqlDdlExecutionStatus,
96 rows_scanned: usize,
97 index_keys_written: usize,
98}
99
100impl SqlDdlPreparationReport {
101 #[must_use]
103 pub const fn mutation_kind(&self) -> SqlDdlMutationKind {
104 self.mutation_kind
105 }
106
107 #[must_use]
109 pub const fn target_index(&self) -> &str {
110 self.target_index.as_str()
111 }
112
113 #[must_use]
115 pub const fn target_store(&self) -> &str {
116 self.target_store.as_str()
117 }
118
119 #[must_use]
121 pub const fn field_path(&self) -> &[String] {
122 self.field_path.as_slice()
123 }
124
125 #[must_use]
127 pub const fn execution_status(&self) -> SqlDdlExecutionStatus {
128 self.execution_status
129 }
130
131 #[must_use]
133 pub const fn rows_scanned(&self) -> usize {
134 self.rows_scanned
135 }
136
137 #[must_use]
139 pub const fn index_keys_written(&self) -> usize {
140 self.index_keys_written
141 }
142
143 pub(in crate::db) const fn with_execution_status(
144 mut self,
145 execution_status: SqlDdlExecutionStatus,
146 ) -> Self {
147 self.execution_status = execution_status;
148 self
149 }
150
151 pub(in crate::db) const fn with_execution_metrics(
152 mut self,
153 rows_scanned: usize,
154 index_keys_written: usize,
155 ) -> Self {
156 self.rows_scanned = rows_scanned;
157 self.index_keys_written = index_keys_written;
158 self
159 }
160}
161
162#[derive(Clone, Copy, Debug, Eq, PartialEq)]
168pub enum SqlDdlMutationKind {
169 AddDefaultedField,
170 AddNullableField,
171 SetFieldDefault,
172 DropFieldDefault,
173 SetFieldNotNull,
174 DropFieldNotNull,
175 DropField,
176 RenameField,
177 AddFieldPathIndex,
178 AddExpressionIndex,
179 DropSecondaryIndex,
180}
181
182impl SqlDdlMutationKind {
183 #[must_use]
185 pub const fn as_str(self) -> &'static str {
186 match self {
187 Self::AddDefaultedField => "add_defaulted_field",
188 Self::AddNullableField => "add_nullable_field",
189 Self::SetFieldDefault => "set_field_default",
190 Self::DropFieldDefault => "drop_field_default",
191 Self::SetFieldNotNull => "set_field_not_null",
192 Self::DropFieldNotNull => "drop_field_not_null",
193 Self::DropField => "drop_field",
194 Self::RenameField => "rename_field",
195 Self::AddFieldPathIndex => "add_field_path_index",
196 Self::AddExpressionIndex => "add_expression_index",
197 Self::DropSecondaryIndex => "drop_secondary_index",
198 }
199 }
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub enum SqlDdlExecutionStatus {
209 PreparedOnly,
210 Published,
211 NoOp,
212}
213
214impl SqlDdlExecutionStatus {
215 #[must_use]
217 pub const fn as_str(self) -> &'static str {
218 match self {
219 Self::PreparedOnly => "prepared_only",
220 Self::Published => "published",
221 Self::NoOp => "no_op",
222 }
223 }
224}
225
226#[derive(Clone, Debug, Eq, PartialEq)]
233pub(in crate::db) struct BoundSqlDdlRequest {
234 statement: BoundSqlDdlStatement,
235}
236
237impl BoundSqlDdlRequest {
238 #[must_use]
240 pub(in crate::db) const fn statement(&self) -> &BoundSqlDdlStatement {
241 &self.statement
242 }
243}
244
245#[derive(Clone, Debug, Eq, PartialEq)]
251pub(in crate::db) enum BoundSqlDdlStatement {
252 AddColumn(BoundSqlAddColumnRequest),
253 AlterColumnDefault(BoundSqlAlterColumnDefaultRequest),
254 AlterColumnNullability(BoundSqlAlterColumnNullabilityRequest),
255 DropColumn(BoundSqlDropColumnRequest),
256 RenameColumn(BoundSqlRenameColumnRequest),
257 CreateIndex(BoundSqlCreateIndexRequest),
258 DropIndex(BoundSqlDropIndexRequest),
259 NoOp(BoundSqlDdlNoOpRequest),
260}
261
262#[derive(Clone, Debug, Eq, PartialEq)]
268pub(in crate::db) struct BoundSqlDdlNoOpRequest {
269 mutation_kind: SqlDdlMutationKind,
270 index_name: String,
271 entity_name: String,
272 target_store: String,
273 field_path: Vec<String>,
274}
275
276impl BoundSqlDdlNoOpRequest {
277 #[must_use]
279 pub(in crate::db) const fn mutation_kind(&self) -> SqlDdlMutationKind {
280 self.mutation_kind
281 }
282
283 #[must_use]
285 pub(in crate::db) const fn index_name(&self) -> &str {
286 self.index_name.as_str()
287 }
288
289 #[must_use]
291 pub(in crate::db) const fn entity_name(&self) -> &str {
292 self.entity_name.as_str()
293 }
294
295 #[must_use]
297 pub(in crate::db) const fn target_store(&self) -> &str {
298 self.target_store.as_str()
299 }
300
301 #[must_use]
303 pub(in crate::db) const fn field_path(&self) -> &[String] {
304 self.field_path.as_slice()
305 }
306}
307
308#[derive(Debug, Eq, PartialEq, ThisError)]
314pub(in crate::db) enum SqlDdlBindError {
315 #[error("SQL DDL binder requires a DDL statement")]
316 NotDdl,
317
318 #[error("accepted schema does not expose an entity name")]
319 MissingEntityName,
320
321 #[error("accepted schema does not expose an entity path")]
322 MissingEntityPath,
323
324 #[error("SQL entity '{sql_entity}' does not match accepted entity '{expected_entity}'")]
325 EntityMismatch {
326 sql_entity: String,
327 expected_entity: String,
328 },
329
330 #[error("unknown field path '{field_path}' for accepted entity '{entity_name}'")]
331 UnknownFieldPath {
332 entity_name: String,
333 field_path: String,
334 },
335
336 #[error("field path '{field_path}' is not indexable")]
337 FieldPathNotIndexable { field_path: String },
338
339 #[error("field path '{field_path}' depends on generated-only metadata")]
340 FieldPathNotAcceptedCatalogBacked { field_path: String },
341
342 #[error("invalid filtered index predicate: {detail}")]
343 InvalidFilteredIndexPredicate { detail: String },
344
345 #[error("index name '{index_name}' already exists in the accepted schema")]
346 DuplicateIndexName { index_name: String },
347
348 #[error("accepted schema already has index '{existing_index}' for field path '{field_path}'")]
349 DuplicateFieldPathIndex {
350 field_path: String,
351 existing_index: String,
352 },
353
354 #[error("unknown index '{index_name}' for accepted entity '{entity_name}'")]
355 UnknownIndex {
356 entity_name: String,
357 index_name: String,
358 },
359
360 #[error(
361 "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"
362 )]
363 GeneratedIndexDropRejected { index_name: String },
364
365 #[error(
366 "index '{index_name}' is not a supported DDL-droppable secondary index; SQL DDL can currently drop only indexes created through SQL DDL"
367 )]
368 UnsupportedDropIndex { index_name: String },
369
370 #[error(
371 "SQL DDL ALTER TABLE ADD COLUMN is not executable yet for accepted entity '{entity_name}' column '{column_name}'"
372 )]
373 UnsupportedAlterTableAddColumn {
374 entity_name: String,
375 column_name: String,
376 },
377
378 #[error(
379 "SQL DDL ALTER TABLE ADD COLUMN DEFAULT value is not encodable for accepted entity '{entity_name}' column '{column_name}': {detail}"
380 )]
381 InvalidAlterTableAddColumnDefault {
382 entity_name: String,
383 column_name: String,
384 detail: String,
385 },
386
387 #[error(
388 "SQL DDL ALTER TABLE ADD COLUMN NOT NULL is not executable yet for accepted entity '{entity_name}' column '{column_name}'"
389 )]
390 UnsupportedAlterTableAddColumnNotNull {
391 entity_name: String,
392 column_name: String,
393 },
394
395 #[error("field '{column_name}' already exists in accepted entity '{entity_name}'")]
396 DuplicateColumn {
397 entity_name: String,
398 column_name: String,
399 },
400
401 #[error(
402 "SQL DDL ALTER TABLE ADD COLUMN type '{column_type}' is not supported yet for accepted entity '{entity_name}' column '{column_name}'"
403 )]
404 UnsupportedAlterTableAddColumnType {
405 entity_name: String,
406 column_name: String,
407 column_type: String,
408 },
409
410 #[error("unknown column '{column_name}' for accepted entity '{entity_name}'")]
411 UnknownColumn {
412 entity_name: String,
413 column_name: String,
414 },
415
416 #[error(
417 "SQL DDL ALTER TABLE ALTER COLUMN {action} is not executable yet for accepted entity '{entity_name}' column '{column_name}'"
418 )]
419 UnsupportedAlterTableAlterColumn {
420 entity_name: String,
421 column_name: String,
422 action: String,
423 },
424
425 #[error(
426 "SQL DDL ALTER TABLE ALTER COLUMN SET DEFAULT value is not encodable for accepted entity '{entity_name}' column '{column_name}': {detail}"
427 )]
428 InvalidAlterTableAlterColumnDefault {
429 entity_name: String,
430 column_name: String,
431 detail: String,
432 },
433
434 #[error(
435 "SQL DDL ALTER TABLE ALTER COLUMN DROP DEFAULT is not executable yet for required accepted entity '{entity_name}' column '{column_name}'"
436 )]
437 UnsupportedAlterTableDropDefaultRequired {
438 entity_name: String,
439 column_name: String,
440 },
441
442 #[error(
443 "SQL DDL ALTER TABLE ALTER COLUMN DEFAULT cannot change generated accepted field '{column_name}' on entity '{entity_name}'; change the Rust schema default instead"
444 )]
445 GeneratedFieldDefaultChangeRejected {
446 entity_name: String,
447 column_name: String,
448 },
449
450 #[error(
451 "SQL DDL ALTER TABLE ALTER COLUMN NULLABILITY cannot change generated accepted field '{column_name}' on entity '{entity_name}'; change the Rust schema nullability instead"
452 )]
453 GeneratedFieldNullabilityChangeRejected {
454 entity_name: String,
455 column_name: String,
456 },
457
458 #[error(
459 "SQL DDL ALTER TABLE DROP COLUMN cannot drop primary-key field '{column_name}' on entity '{entity_name}'"
460 )]
461 PrimaryKeyFieldDropRejected {
462 entity_name: String,
463 column_name: String,
464 },
465
466 #[error(
467 "SQL DDL ALTER TABLE DROP COLUMN cannot change generated accepted field '{column_name}' on entity '{entity_name}'; remove the field from the Rust schema instead"
468 )]
469 GeneratedFieldDropRejected {
470 entity_name: String,
471 column_name: String,
472 },
473
474 #[error(
475 "SQL DDL ALTER TABLE DROP COLUMN cannot drop accepted field '{column_name}' on entity '{entity_name}' while index '{index_name}' depends on it; drop dependent DDL-owned indexes first"
476 )]
477 IndexedFieldDropRejected {
478 entity_name: String,
479 column_name: String,
480 index_name: String,
481 },
482
483 #[error(
484 "SQL DDL ALTER TABLE RENAME COLUMN cannot change generated accepted field '{column_name}' on entity '{entity_name}'; rename the field in the Rust schema instead"
485 )]
486 GeneratedFieldRenameRejected {
487 entity_name: String,
488 column_name: String,
489 },
490
491 #[error(
492 "SQL DDL ALTER TABLE RENAME COLUMN is not executable for accepted entity '{entity_name}' column '{old_column_name}' to '{new_column_name}'"
493 )]
494 UnsupportedAlterTableRenameColumn {
495 entity_name: String,
496 old_column_name: String,
497 new_column_name: String,
498 },
499}
500
501#[derive(Debug, Eq, PartialEq, ThisError)]
508pub(in crate::db) enum SqlDdlLoweringError {
509 #[error("SQL DDL lowering requires a supported DDL statement")]
510 UnsupportedStatement,
511
512 #[error("schema mutation admission rejected DDL candidate: {0:?}")]
513 MutationAdmission(SchemaDdlMutationAdmissionError),
514}
515
516#[derive(Debug, Eq, PartialEq, ThisError)]
522pub(in crate::db) enum SqlDdlPrepareError {
523 #[error("{0}")]
524 Bind(#[from] SqlDdlBindError),
525
526 #[error("{0}")]
527 Lowering(#[from] SqlDdlLoweringError),
528}
529
530pub(in crate::db) fn prepare_sql_ddl_statement(
532 statement: &SqlStatement,
533 accepted_before: &AcceptedSchemaSnapshot,
534 schema: &SchemaInfo,
535 index_store_path: &'static str,
536) -> Result<PreparedSqlDdlCommand, SqlDdlPrepareError> {
537 let bound = bind_sql_ddl_statement(statement, accepted_before, schema, index_store_path)?;
538 let derivation = if matches!(bound.statement(), BoundSqlDdlStatement::NoOp(_)) {
539 None
540 } else {
541 Some(derive_bound_sql_ddl_accepted_after(
542 accepted_before,
543 &bound,
544 )?)
545 };
546 let report = ddl_preparation_report(&bound);
547
548 Ok(PreparedSqlDdlCommand {
549 bound,
550 derivation,
551 report,
552 })
553}
554
555pub(in crate::db) fn bind_sql_ddl_statement(
557 statement: &SqlStatement,
558 accepted_before: &AcceptedSchemaSnapshot,
559 schema: &SchemaInfo,
560 index_store_path: &'static str,
561) -> Result<BoundSqlDdlRequest, SqlDdlBindError> {
562 let SqlStatement::Ddl(ddl) = statement else {
563 return Err(SqlDdlBindError::NotDdl);
564 };
565
566 match ddl {
567 SqlDdlStatement::CreateIndex(statement) => {
568 bind_create_index_statement(statement, schema, index_store_path)
569 }
570 SqlDdlStatement::DropIndex(statement) => {
571 bind_drop_index_statement(statement, accepted_before, schema)
572 }
573 SqlDdlStatement::AlterTableAddColumn(statement) => {
574 bind_alter_table_add_column_statement(statement, accepted_before, schema)
575 }
576 SqlDdlStatement::AlterTableAlterColumn(statement) => {
577 bind_alter_table_alter_column_statement(statement, accepted_before, schema)
578 }
579 SqlDdlStatement::AlterTableDropColumn(statement) => {
580 bind_alter_table_drop_column_statement(statement, accepted_before, schema)
581 }
582 SqlDdlStatement::AlterTableRenameColumn(statement) => {
583 bind_alter_table_rename_column_statement(statement, accepted_before, schema)
584 }
585 }
586}
587
588pub(in crate::db) fn lower_bound_sql_ddl_to_schema_mutation_admission(
590 request: &BoundSqlDdlRequest,
591) -> Result<SchemaDdlMutationAdmission, SqlDdlLoweringError> {
592 match request.statement() {
593 BoundSqlDdlStatement::AddColumn(add) => {
594 Ok(admit_sql_ddl_field_addition_candidate(add.field()))
595 }
596 BoundSqlDdlStatement::AlterColumnDefault(alter) => {
597 Ok(admit_sql_ddl_field_default_candidate(alter.field()))
598 }
599 BoundSqlDdlStatement::AlterColumnNullability(alter) => {
600 Ok(admit_sql_ddl_field_nullability_candidate(alter.field()))
601 }
602 BoundSqlDdlStatement::DropColumn(drop) => {
603 Ok(admit_sql_ddl_field_drop_candidate(drop.field()))
604 }
605 BoundSqlDdlStatement::RenameColumn(rename) => {
606 let after = rename
607 .field()
608 .clone_with_name(rename.new_name().to_string());
609 Ok(admit_sql_ddl_field_rename_candidate(rename.field(), &after))
610 }
611 BoundSqlDdlStatement::CreateIndex(create) => {
612 if create.candidate_index().key().is_field_path_only() {
613 admit_sql_ddl_field_path_index_candidate(create.candidate_index())
614 } else {
615 admit_sql_ddl_expression_index_candidate(create.candidate_index())
616 }
617 }
618 BoundSqlDdlStatement::DropIndex(drop) => {
619 admit_sql_ddl_secondary_index_drop_candidate(drop.dropped_index())
620 }
621 BoundSqlDdlStatement::NoOp(_) => return Err(SqlDdlLoweringError::UnsupportedStatement),
622 }
623 .map_err(SqlDdlLoweringError::MutationAdmission)
624}
625
626pub(in crate::db) fn derive_bound_sql_ddl_accepted_after(
628 accepted_before: &AcceptedSchemaSnapshot,
629 request: &BoundSqlDdlRequest,
630) -> Result<SchemaDdlAcceptedSnapshotDerivation, SqlDdlLoweringError> {
631 match request.statement() {
632 BoundSqlDdlStatement::AddColumn(add) => {
633 derive_sql_ddl_field_addition_accepted_after(accepted_before, add.field().clone())
634 }
635 BoundSqlDdlStatement::AlterColumnDefault(alter) => {
636 derive_sql_ddl_field_default_accepted_after(
637 accepted_before,
638 alter.field_name(),
639 alter.default().clone(),
640 )
641 }
642 BoundSqlDdlStatement::AlterColumnNullability(alter) => {
643 derive_sql_ddl_field_nullability_accepted_after(
644 accepted_before,
645 alter.field_name(),
646 alter.nullable(),
647 )
648 }
649 BoundSqlDdlStatement::DropColumn(drop) => {
650 derive_sql_ddl_field_drop_accepted_after(accepted_before, drop.field_name())
651 }
652 BoundSqlDdlStatement::RenameColumn(rename) => derive_sql_ddl_field_rename_accepted_after(
653 accepted_before,
654 rename.old_name(),
655 rename.new_name(),
656 ),
657 BoundSqlDdlStatement::CreateIndex(create) => {
658 if create.candidate_index().key().is_field_path_only() {
659 derive_sql_ddl_field_path_index_accepted_after(
660 accepted_before,
661 create.candidate_index().clone(),
662 )
663 } else {
664 derive_sql_ddl_expression_index_accepted_after(
665 accepted_before,
666 create.candidate_index().clone(),
667 )
668 }
669 }
670 BoundSqlDdlStatement::DropIndex(drop) => {
671 derive_sql_ddl_secondary_index_drop_accepted_after(
672 accepted_before,
673 drop.dropped_index(),
674 )
675 }
676 BoundSqlDdlStatement::NoOp(_) => return Err(SqlDdlLoweringError::UnsupportedStatement),
677 }
678 .map_err(SqlDdlLoweringError::MutationAdmission)
679}
680
681fn ddl_preparation_report(bound: &BoundSqlDdlRequest) -> SqlDdlPreparationReport {
682 match bound.statement() {
683 BoundSqlDdlStatement::AddColumn(add) => SqlDdlPreparationReport {
684 mutation_kind: if add.field().default().is_none() {
685 SqlDdlMutationKind::AddNullableField
686 } else {
687 SqlDdlMutationKind::AddDefaultedField
688 },
689 target_index: add.field().name().to_string(),
690 target_store: add.entity_name().to_string(),
691 field_path: vec![add.field().name().to_string()],
692 execution_status: SqlDdlExecutionStatus::PreparedOnly,
693 rows_scanned: 0,
694 index_keys_written: 0,
695 },
696 BoundSqlDdlStatement::AlterColumnDefault(alter) => SqlDdlPreparationReport {
697 mutation_kind: alter.mutation_kind(),
698 target_index: alter.field_name().to_string(),
699 target_store: alter.entity_name().to_string(),
700 field_path: vec![alter.field_name().to_string()],
701 execution_status: SqlDdlExecutionStatus::PreparedOnly,
702 rows_scanned: 0,
703 index_keys_written: 0,
704 },
705 BoundSqlDdlStatement::AlterColumnNullability(alter) => SqlDdlPreparationReport {
706 mutation_kind: alter.mutation_kind(),
707 target_index: alter.field_name().to_string(),
708 target_store: alter.entity_name().to_string(),
709 field_path: vec![alter.field_name().to_string()],
710 execution_status: SqlDdlExecutionStatus::PreparedOnly,
711 rows_scanned: 0,
712 index_keys_written: 0,
713 },
714 BoundSqlDdlStatement::DropColumn(drop) => SqlDdlPreparationReport {
715 mutation_kind: SqlDdlMutationKind::DropField,
716 target_index: drop.field_name().to_string(),
717 target_store: drop.entity_name().to_string(),
718 field_path: vec![drop.field_name().to_string()],
719 execution_status: SqlDdlExecutionStatus::PreparedOnly,
720 rows_scanned: 0,
721 index_keys_written: 0,
722 },
723 BoundSqlDdlStatement::RenameColumn(rename) => SqlDdlPreparationReport {
724 mutation_kind: SqlDdlMutationKind::RenameField,
725 target_index: rename.new_name().to_string(),
726 target_store: rename.entity_name().to_string(),
727 field_path: vec![rename.old_name().to_string(), rename.new_name().to_string()],
728 execution_status: SqlDdlExecutionStatus::PreparedOnly,
729 rows_scanned: 0,
730 index_keys_written: 0,
731 },
732 BoundSqlDdlStatement::CreateIndex(create) => {
733 let target = create.candidate_index();
734
735 SqlDdlPreparationReport {
736 mutation_kind: if target.key().is_field_path_only() {
737 SqlDdlMutationKind::AddFieldPathIndex
738 } else {
739 SqlDdlMutationKind::AddExpressionIndex
740 },
741 target_index: target.name().to_string(),
742 target_store: target.store().to_string(),
743 field_path: ddl_key_item_report(create.key_items()),
744 execution_status: SqlDdlExecutionStatus::PreparedOnly,
745 rows_scanned: 0,
746 index_keys_written: 0,
747 }
748 }
749 BoundSqlDdlStatement::DropIndex(drop) => SqlDdlPreparationReport {
750 mutation_kind: SqlDdlMutationKind::DropSecondaryIndex,
751 target_index: drop.index_name().to_string(),
752 target_store: drop.dropped_index().store().to_string(),
753 field_path: drop.field_path().to_vec(),
754 execution_status: SqlDdlExecutionStatus::PreparedOnly,
755 rows_scanned: 0,
756 index_keys_written: 0,
757 },
758 BoundSqlDdlStatement::NoOp(no_op) => SqlDdlPreparationReport {
759 mutation_kind: no_op.mutation_kind(),
760 target_index: no_op.index_name().to_string(),
761 target_store: no_op.target_store().to_string(),
762 field_path: no_op.field_path().to_vec(),
763 execution_status: SqlDdlExecutionStatus::PreparedOnly,
764 rows_scanned: 0,
765 index_keys_written: 0,
766 },
767 }
768}