Repository: edgedb/edgedb

Issue:
No index names in interactive migrations
I have to go blindfolded when creating a migration. Index names should be more descriptive than this:

```
did you drop index '__::idx' of object type 'default::Shift'? [y,n,l,c,b,s,q,?]
> y
did you drop index '__::idx' of object type 'default::Shift'? [y,n,l,c,b,s,q,?]
> y
did you create index '__::idx' of object type 'default::Shift'? [y,n,l,c,b,s,q,?]
> y
did you create index '__::idx' of object type 'default::Shift'? [y,n,l,c,b,s,q,?]
> y
did you drop index '__::idx' of object type 'default::Shift'? [y,n,l,c,b,s,q,?]
> y
```


- EdgeDB Version: 5.2
- EdgeDB CLI Version: 5.1.0+7c5764f
- OS Version: Linux

Steps to Reproduce:

1. `edgedb migration create`

<!-- If the issue is about a query error, please also provide your schema -->

Schema:
```
type Shift {
    ...
    property date -> datetime;
    index on (.date);
    ...
}
```



Discussion:
@dnwpark See `get_friendly_description` and the stuff it calls; you'll probably want to overload those in `IndexCommand`
Probably `get_friendly_object_name_for_description` is the main thing you care about here

Resolving patch:
diff --git a/edb/schema/indexes.py b/edb/schema/indexes.py
--- a/edb/schema/indexes.py
+++ b/edb/schema/indexes.py
@@ -790,6 +790,32 @@ def get_ddl_identity_fields(
         else:
             return id_fields
 
+    def get_friendly_object_name_for_description(
+        self,
+        *,
+        parent_op: Optional[sd.Command] = None,
+        schema: Optional[s_schema.Schema] = None,
+        object: Optional[so.Object_T] = None,
+        object_desc: Optional[str] = None,
+    ) -> str:
+        friendly_name: str = 'index'
+
+        expr: Optional[s_expr.Expression] = None
+        if (
+            self.has_ddl_identity('expr') and
+            (expr := self.get_ddl_identity('expr'))
+        ):
+            expr_text = expr.text
+            if expr_text[0] != '(' or expr_text[-1] != ')':
+                expr_text = '(' + expr_text + ')'
+
+            friendly_name = f"index on {expr_text}"
+
+        if not isinstance(parent_op, sd.ObjectCommand):
+            return f"{friendly_name}"
+        else:
+            return f"{friendly_name} of {parent_op.get_verbosename()}"
+
     def compile_expr_field(
         self,
         schema: s_schema.Schema,


Test patch:
diff --git a/tests/test_edgeql_data_migration.py b/tests/test_edgeql_data_migration.py
--- a/tests/test_edgeql_data_migration.py
+++ b/tests/test_edgeql_data_migration.py
@@ -1470,6 +1470,162 @@ async def test_edgeql_migration_describe_link_03(self):
         # Auto-complete migration
         await self.fast_forward_describe_migration()
 
+    async def test_edgeql_migration_describe_index_01(self):
+        # Migration that creates index.
+        await self.con.execute(r'''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        property a -> int64;
+                    };
+                };
+            };
+        ''')
+        # Auto-complete migration
+        await self.fast_forward_describe_migration()
+
+        await self.con.execute('''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        property a -> int64;
+                        index on (.a)
+                    };
+                };
+            };
+        ''')
+
+        await self.assert_describe_migration({
+            'confirmed': [],
+            'complete': False,
+            'proposed': {
+                'prompt': (
+                    "did you create index on (.a) "
+                    "of object type 'test::Foo'?"
+                )
+            }
+        })
+
+    async def test_edgeql_migration_describe_index_02(self):
+        # Migration that drops index expression.
+        await self.con.execute(r'''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        property a -> int64;
+                        index on (.a)
+                    };
+                };
+            };
+        ''')
+        # Auto-complete migration
+        await self.fast_forward_describe_migration()
+
+        await self.con.execute('''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        property a -> int64;
+                    };
+                };
+            };
+        ''')
+
+        await self.assert_describe_migration({
+            'confirmed': [],
+            'complete': False,
+            'proposed': {
+                'prompt': (
+                    "did you drop index on (.a) "
+                    "of object type 'test::Foo'?"
+                )
+            }
+        })
+
+    async def test_edgeql_migration_describe_index_03(self):
+        # Migration that creates index on link property
+        await self.con.execute(r'''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        link bar -> Bar {
+                            baz -> int64;
+                        }
+                    };
+                    type Bar;
+                };
+            };
+        ''')
+        # Auto-complete migration
+        await self.fast_forward_describe_migration()
+
+        await self.con.execute('''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        link bar -> Bar {
+                            baz -> int64;
+                            index on (@baz);
+                        }
+                    };
+                    type Bar;
+                };
+            };
+        ''')
+
+        await self.assert_describe_migration({
+            'confirmed': [],
+            'complete': False,
+            'proposed': {
+                'prompt': (
+                    "did you create index on (@baz) "
+                    "of link 'bar'?"
+                )
+            }
+        })
+
+    async def test_edgeql_migration_describe_index_04(self):
+        # Migration that drops index on link property
+        await self.con.execute(r'''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        link bar -> Bar {
+                            baz -> int64;
+                            index on (@baz);
+                        }
+                    };
+                    type Bar;
+                };
+            };
+        ''')
+        # Auto-complete migration
+        await self.fast_forward_describe_migration()
+
+        await self.con.execute('''
+            START MIGRATION TO {
+                module test {
+                    type Foo {
+                        link bar -> Bar {
+                            baz -> int64;
+                        }
+                    };
+                    type Bar;
+                };
+            };
+        ''')
+
+        await self.assert_describe_migration({
+            'confirmed': [],
+            'complete': False,
+            'proposed': {
+                'prompt': (
+                    "did you drop index on (@baz) "
+                    "of link 'bar'?"
+                )
+            }
+        })
+
     async def test_edgeql_migration_describe_scalar_01(self):
         # Migration that renames a type.
         await self.con.execute('''