Repository: edgedb/edgedb

Issue:
Confusing migration error thrown due to multiline annotation
<!-- Please search existing issues to avoid creating duplicates. -->
- EdgeDB Version: 1.0.0-beta.2
- OS Version: macOS 11.4 (20F71)

Steps to Reproduce:

1. run `edgedb project init`
2. set default module to 👇 
```
module default {
	abstract type Recording {
		required property version -> int16 {
            annotation description := 
                "Version is incremented anytime a change
                in the data is registered";
        }
	}
}
```
3.  run `edgedb create-migration --non-interactive ; edgedb migrate`

This results in the message
```
edgedb error: could not read migrations in ./dbschema/migrations: could not read migration file ./dbschema/migrations/00001.edgeql: migration name should be `m1ydgjz533k6jnuknvkm3ybiofk2depc4s7a2pggecsf4pzcjsba5a` but `m1axn3wsnysm6hnnpijljgjv6ybyldbfvrajgvgdq7qdve7ky2adfa` is used instead.
Migration names are computed from the hash of the migration contents. To proceed you must fix the statement to read as:
  CREATE MIGRATION m1ydgjz533k6jnuknvkm3ybiofk2depc4s7a2pggecsf4pzcjsba5a ONTO ...
if this migration is not applied to any database. Alternatively, revert the changes to the file.
```

at this state, the migration 00001.edgeql is 
```
CREATE MIGRATION m1ydgjz533k6jnuknvkm3ybiofk2depc4s7a2pggecsf4pzcjsba5a
    ONTO initial
{
  CREATE ABSTRACT TYPE default::Recording {
      CREATE REQUIRED PROPERTY version -> std::int16 {
          CREATE ANNOTATION std::description := 'Version is incremented anytime a change
                  in the data is registered';
      };
  };
};
```

When we follow this instruction and swap the hash values, running `edgedb migrate` again results in 

```
edgedb error: ERROR: SchemaDefinitionError: specified migration name does not match the name derived from the migration contents: 'm1ydgjz533k6jnuknvkm3ybiofk2depc4s7a2pggecsf4pzcjsba5a', expected 'm1axn3wsnysm6hnnpijljgjv6ybyldbfvrajgvgdq7qdve7ky2adfa'
```

which is the previous hash that we were instructed to change.  This chain of errors can be resolved by setting the default module to 
```
module default {
	abstract type Recording {
		required property version -> int16 {
            annotation description := 
                "Version is incremented anytime a change  in the data is registered";
        }
	}
}
```
then running `edgedb create-migration --non-interactive ; edgedb migrate`

will result in 
```
Applied m1gci5naybrifrngsk2537zjerqk2zqxnmbdqslgamqcnm7vzknpmq (00001.edgeql)
```

I'm not quite sure if its specified anywhere that annotations or strings in general cannot span multiple lines.  As a brand new user (yesterday was my first schema setup attempt) I had a bax UX with this.  The suggestion from the error that was reported did not resolve my issue when followed, nor did it help me diagnose what the root cause was.  

I think a better UX would be to either have some parser functionality to remove newlines from annotation strings (during migration creation) if they are not supported in the DDL.  If this can't be the case, then having a more specific error message in regards to this limitation would be very valuable.

Thank you in advance for all the work being put into EdgeDB!  I have had an otherwise great experience learning about this tool.  The documentation on the website is superb.



Discussion:
Thank you for a great bug report!  Multiline annotations ought to be supported, this bug seems to stem from differences in how SDL and DDL are tokenized.
This is somewhat fiddly:

The crux of the issue here is that edgeql multi-line string literals don't do any sort of whitespace squishing (unless you backslash escape the newline, which is probably what you want to do), and so indenting or dedenting them causes their value to change.

Combine that with `edgedb-cli` indenting and you get this problem

Resolving patch:
diff --git a/edb/edgeql/codegen.py b/edb/edgeql/codegen.py
--- a/edb/edgeql/codegen.py
+++ b/edb/edgeql/codegen.py
@@ -35,7 +35,7 @@
 _module_name_re = re.compile(r'^(?!=\d)\w+(\.(?!=\d)\w+)*$')
 _BYTES_ESCAPE_RE = re.compile(b'[\\\'\x00-\x1f\x7e-\xff]')
 _NON_PRINTABLE_RE = re.compile(
-    r'[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F]')
+    r'[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\n]')
 _ESCAPES = {
     b'\\': b'\\\\',
     b'\'': b'\\\'',


Test patch:
diff --git a/tests/test_edgeql_syntax.py b/tests/test_edgeql_syntax.py
--- a/tests/test_edgeql_syntax.py
+++ b/tests/test_edgeql_syntax.py
@@ -302,6 +302,8 @@ def test_edgeql_syntax_constants_18(self):
         r"""
         SELECT 'aa
         aa';
+% OK %
+        SELECT 'aa\naa';
         """
 
     @tb.must_fail(errors.EdgeQLSyntaxError,
@@ -452,8 +454,7 @@ def test_edgeql_syntax_constants_38(self):
         r"""
         SELECT "\n";
 % OK %
-        SELECT '
-';
+        SELECT '\n';
         """
 
     def test_edgeql_syntax_constants_39(self):
@@ -3967,6 +3968,24 @@ def test_edgeql_syntax_ddl_annotation_04(self):
             RENAME TO foo::renamed_annotation;
         """
 
+    def test_edgeql_syntax_ddl_annotation_05(self):
+        # N.B: The multi line string literal here *must* get turned
+        # into a single line literal with a \n in it, since otherwise
+        # generated DDL migrations with multi-line literals will be
+        # mangled by the cli indenting them.
+        r"""
+        CREATE TYPE Foo {
+            CREATE ANNOTATION description :=
+                "multi
+                 line";
+        };
+% OK %
+        CREATE TYPE Foo {
+            CREATE ANNOTATION description :=
+                'multi\n                 line';
+        };
+        """
+
     def test_edgeql_syntax_ddl_constraint_01(self):
         """
         CREATE ABSTRACT CONSTRAINT std::enum(VARIADIC p: anytype)
diff --git a/tests/test_schema_syntax.py b/tests/test_schema_syntax.py
--- a/tests/test_schema_syntax.py
+++ b/tests/test_schema_syntax.py
@@ -369,7 +369,7 @@ def test_eschema_syntax_type_19(self):
         """
 
     def test_eschema_syntax_type_22(self):
-        """
+        r"""
         module test {
             type Foo {
                 property foo -> str {
@@ -403,13 +403,11 @@ def test_eschema_syntax_type_22(self):
                 };
                 property bar -> str {
                     # multi-line definition with correct indentation
-                    default := some_func('
-                        1, 2, 3');
+                    default := some_func('\n                        1, 2, 3');
                 };
                 property baz -> str {
                     # multi-line definition with correct indentation
-                    default := 'some_func(
-                        1, 2, 3)';
+                    default := 'some_func(\n                        1, 2, 3)';
                 };
             };
         };