Repository: edgedb/edgedb

Issue:
InternalServerError on long enum labels
<!-- Please search existing issues to avoid creating duplicates. -->

<!--
For the EdgeDB Version: run `edgedb query 'select sys::get_version_as_str()'` from your project directory (or run `select sys::get_version_as_str();` in the EdgeDB interactive shell).
For the EdgeDB CLI Version: Run `edgedb --version` from anywhere
-->

- EdgeDB Version: '3.1+1f030a7'
- EdgeDB CLI Version: EdgeDB CLI 3.5.0+907ff37
- OS Version: Ubuntu 20.04.5 LTS (Focal Fossa)

Steps to Reproduce:

1. Declare a scalar type extending enum which contains a value that is 65 characters long
2. Create migration
3. Apply migration
```
 `edgedb error: InternalServerError: invalid enum label`


Discussion:
This is a Postgres limitation, I believe, but we ought to produce a nicer error of course.

Resolving patch:
diff --git a/edb/pgsql/common.py b/edb/pgsql/common.py
--- a/edb/pgsql/common.py
+++ b/edb/pgsql/common.py
@@ -47,6 +47,12 @@
 from . import keywords as pg_keywords
 
 
+# This is a postgres limitation.
+# Note that this can be overridden in custom builds.
+# https://www.postgresql.org/docs/current/datatype-enum.html
+MAX_ENUM_LABEL_LENGTH = 63
+
+
 def quote_e_literal(string: str) -> str:
     def escape_sq(s):
         split = re.split(r"(\n|\\\\|\\')", s)
diff --git a/edb/schema/utils.py b/edb/schema/utils.py
--- a/edb/schema/utils.py
+++ b/edb/schema/utils.py
@@ -205,29 +205,51 @@ def ast_to_type_shell(
             and isinstance(node.maintype, qlast.ObjectRef)
             and node.maintype.name == 'enum'):
         from . import scalars as s_scalars
+        from edb.pgsql import common as pg_common
 
         assert node.subtypes
 
+        elements: List[str] = []
+        element_spans: List[Optional[parsing.Span]] = []
+
         if isinstance(node.subtypes[0], qlast.TypeExprLiteral):
-            return s_scalars.AnonymousEnumTypeShell(  # type: ignore
-                elements=[
-                    est.val.value
-                    for est in cast(List[qlast.TypeExprLiteral], node.subtypes)
-                ],
-            )
+            # handling enums as literals
+            # eg. enum<'A','B','C'>
+            for subtype_expr_literal in cast(
+                List[qlast.TypeExprLiteral], node.subtypes
+            ):
+                elements.append(subtype_expr_literal.val.value)
+                element_spans.append(subtype_expr_literal.val.span)
         else:
-            elements: List[str] = []
-            for est in cast(List[qlast.TypeName], node.subtypes):
-                if (not isinstance(est, qlast.TypeName) or
-                        not isinstance(est.maintype, qlast.ObjectRef)):
+            # handling enums as typenames
+            # eg. enum<A,B,C>
+            for subtype_type_name in cast(
+                List[qlast.TypeName], node.subtypes
+            ):
+                if (
+                    not isinstance(subtype_type_name, qlast.TypeName)
+                    or not isinstance(
+                        subtype_type_name.maintype, qlast.ObjectRef
+                    )
+                ):
                     raise errors.EdgeQLSyntaxError(
                         f'enums do not support mapped values',
-                        span=est.span,
+                        span=subtype_type_name.span,
                     )
-                elements.append(est.maintype.name)
-            return s_scalars.AnonymousEnumTypeShell(  # type: ignore
-                elements=elements
-            )
+                elements.append(subtype_type_name.maintype.name)
+                element_spans.append(subtype_type_name.maintype.span)
+
+        for element, element_span in zip(elements, element_spans):
+            if len(element) > pg_common.MAX_ENUM_LABEL_LENGTH:
+                raise errors.SchemaDefinitionError(
+                    f'enum labels cannot exceed '
+                    f'{pg_common.MAX_ENUM_LABEL_LENGTH} characters',
+                    span=element_span,
+                )
+
+        return s_scalars.AnonymousEnumTypeShell(  # type: ignore
+            elements=elements
+        )
 
     elif node.subtypes is not None:
         from . import types as s_types


Test patch:
diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py
--- a/tests/test_edgeql_ddl.py
+++ b/tests/test_edgeql_ddl.py
@@ -11868,6 +11868,112 @@ async def test_edgeql_ddl_enum_05(self):
                     EXTENDING enum<Green>;
             ''')
 
+    async def test_edgeql_ddl_enum_06(self):
+        await self.con.execute(
+            "CREATE SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+            "    AAAAAAAAAA"
+                "BBBBBBBBBB"
+                "CCCCCCCCCC"
+                "DDDDDDDDDD"
+                "EEEEEEEEEE"
+                "FFFFFFFFFF"
+                "GGG\n"
+            ">"
+        )
+
+    async def test_edgeql_ddl_enum_07(self):
+        await self.con.execute(
+            "CREATE SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+            "    'AAAAAAAAAA"
+                "BBBBBBBBBB"
+                "CCCCCCCCCC"
+                "DDDDDDDDDD"
+                "EEEEEEEEEE"
+                "FFFFFFFFFF"
+                "GGG'\n"
+            ">"
+        )
+
+    async def test_edgeql_ddl_enum_08(self):
+        with self.assertRaisesRegex(
+            edgedb.SchemaDefinitionError,
+            r'enum labels cannot exceed 63 characters'
+        ):
+            await self.con.execute(
+                "CREATE SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+                "    AAAAAAAAAA"
+                    "BBBBBBBBBB"
+                    "CCCCCCCCCC"
+                    "DDDDDDDDDD"
+                    "EEEEEEEEEE"
+                    "FFFFFFFFFF"
+                    "GGGG\n"
+                ">"
+            )
+
+    async def test_edgeql_ddl_enum_09(self):
+        with self.assertRaisesRegex(
+            edgedb.SchemaDefinitionError,
+            r'enum labels cannot exceed 63 characters'
+        ):
+            await self.con.execute(
+                "CREATE SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+                "    'AAAAAAAAAA"
+                    "BBBBBBBBBB"
+                    "CCCCCCCCCC"
+                    "DDDDDDDDDD"
+                    "EEEEEEEEEE"
+                    "FFFFFFFFFF"
+                    "GGGG'\n"
+                ">"
+            )
+
+    async def test_edgeql_ddl_enum_10(self):
+        await self.con.execute(
+            "CREATE SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+            "    AAAAAAAAAA\n"
+            ">"
+        )
+
+        with self.assertRaisesRegex(
+            edgedb.SchemaDefinitionError,
+            r'enum labels cannot exceed 63 characters'
+        ):
+            await self.con.execute(
+                "ALTER SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+                "    AAAAAAAAAA"
+                    "BBBBBBBBBB"
+                    "CCCCCCCCCC"
+                    "DDDDDDDDDD"
+                    "EEEEEEEEEE"
+                    "FFFFFFFFFF"
+                    "GGGG\n"
+                ">"
+            )
+
+    async def test_edgeql_ddl_enum_11(self):
+        await self.con.execute(
+            "CREATE SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+            "    'AAAAAAAAAA'\n"
+            ">"
+        )
+
+        with self.assertRaisesRegex(
+            edgedb.SchemaDefinitionError,
+            r'enum labels cannot exceed 63 characters'
+        ):
+            await self.con.execute(
+                "ALTER SCALAR TYPE default::LongLabel EXTENDING enum<\n"
+                "    'AAAAAAAAAA"
+                    "BBBBBBBBBB"
+                    "CCCCCCCCCC"
+                    "DDDDDDDDDD"
+                    "EEEEEEEEEE"
+                    "FFFFFFFFFF"
+                    "GGGG'\n"
+                ">"
+            )
+
     async def test_edgeql_ddl_explicit_id(self):
         await self.con.execute('''
             CREATE TYPE ExID {
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -2416,6 +2416,70 @@ def test_schema_range_01(self):
         }
         """
 
+    def test_schema_enum_01(self):
+        pass
+    test_schema_enum_01.__doc__ = (
+        "scalar type LongLabel extending enum<\n"
+        "    AAAAAAAAAA"
+            "BBBBBBBBBB"
+            "CCCCCCCCCC"
+            "DDDDDDDDDD"
+            "EEEEEEEEEE"
+            "FFFFFFFFFF"
+            "GGG\n"
+        ">"
+    )
+
+    def test_schema_enum_02(self):
+        pass
+    test_schema_enum_02.__doc__ = (
+        "scalar type LongLabel extending enum<\n"
+        "    'AAAAAAAAAA"
+            "BBBBBBBBBB"
+            "CCCCCCCCCC"
+            "DDDDDDDDDD"
+            "EEEEEEEEEE"
+            "FFFFFFFFFF"
+            "GGG'\n"
+        ">"
+    )
+
+    @tb.must_fail(
+        errors.SchemaDefinitionError,
+        "enum labels cannot exceed 63 characters",
+    )
+    def test_schema_enum_03(self):
+        pass
+    test_schema_enum_03.__doc__ = (
+        "scalar type LongLabel extending enum<\n"
+        "    AAAAAAAAAA"
+            "BBBBBBBBBB"
+            "CCCCCCCCCC"
+            "DDDDDDDDDD"
+            "EEEEEEEEEE"
+            "FFFFFFFFFF"
+            "GGGG\n"
+        ">"
+    )
+
+    @tb.must_fail(
+        errors.SchemaDefinitionError,
+        "enum labels cannot exceed 63 characters",
+    )
+    def test_schema_enum_04(self):
+        pass
+    test_schema_enum_04.__doc__ = (
+        "scalar type LongLabel extending enum<\n"
+        "    'AAAAAAAAAA"
+            "BBBBBBBBBB"
+            "CCCCCCCCCC"
+            "DDDDDDDDDD"
+            "EEEEEEEEEE"
+            "FFFFFFFFFF"
+            "GGGG'\n"
+        ">"
+    )
+
 
 class TestGetMigration(tb.BaseSchemaLoadTest):
     """Test migration deparse consistency.