Repository: edgedb/edgedb

Issue:
Migration system says that making a pointer computable is data safe
```
edgedb> rollback; start transaction; start migration to {
....... module default {
....... type Foo { property foo -> str; }
....... };
....... };
....... populate migration; commit migration; commit
```
Then
```
edgedb> start migration to {
....... module default {
....... type Foo { property foo := "1" ; }
....... };
....... };
....... describe current migration as json;
OK: START MIGRATION
[
  {
    "parent": "m1lnvgcjmqfq7h2icuj4sjqbpl6pxi6rqb37byp76nocwafjwnrukq",
    "complete": false,
    "proposed": {
      "prompt": "did you alter the type of property 'foo' of object type 'default::Foo'?",
      "data_safe": true,
      "prompt_id": "AlterProperty PROPERTY default::__|foo@default|Foo",
      "confidence": 1.0,
      "statements": [{"text": "ALTER TYPE default::Foo {\n    ALTER PROPERTY foo {\n        USING ('1');\n    };\n};"}],
      "required_user_input": []
    },
    "confirmed": []
  }
]
```
You can see that `data_safe` is reported as true, even though it really obviously is not, since it drops all the data in the property!

I think that the *correct* solution to this is for `expr` to go through the same `AlterSpecialObjectField` machinery as as `target`/`required`/`cardinality` (which would clean up a bit of other stuff too), but probably I will first do an expedient solution


Discussion:

Resolving patch:
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
--- a/edb/schema/pointers.py
+++ b/edb/schema/pointers.py
@@ -2192,6 +2192,14 @@ def canonicalize_alter_from_external_ref(
             computed=pointer.field_is_computed(schema, 'target'),
         )
 
+    def is_data_safe(self) -> bool:
+        # HACK: expr ought to be managed by AlterSpecialObjectField
+        # the way that target/required/cardinality are.
+        return super().is_data_safe() and not (
+            self.get_attribute_value('expr') is not None
+            and self.get_orig_attribute_value('expr') is None
+        )
+
 
 class DeletePointer(
     referencing.DeleteReferencedInheritingObject[Pointer_T],


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
@@ -9802,6 +9802,38 @@ async def test_edgeql_migration_data_safety_01(self):
             ]
         )
 
+        # And changing a property to computed is NOT!
+        await self.start_migration('''
+            type Obj11 {
+                single property name := "test";
+            }
+        ''')
+
+        await self.assert_describe_migration({
+            'confirmed': [],
+            'proposed': {
+                'data_safe': False,
+            },
+        })
+
+        await self.fast_forward_describe_migration()
+
+        # But just changing a computed is
+        await self.start_migration('''
+            type Obj11 {
+                single property name := "fffff";
+            }
+        ''')
+
+        await self.assert_describe_migration({
+            'confirmed': [],
+            'proposed': {
+                'data_safe': True,
+            },
+        })
+
+        await self.fast_forward_describe_migration()
+
     async def test_edgeql_migration_prompt_id_01(self):
         await self.start_migration('''
             type Bar { link spam -> Spam };