Repository: geldata/gel
Pull request: Add error when enum length exceeds 63
Works for both SDL and DDL.

Note: it is possible to change this max length in a custom postgres build.

Close #6186

Problem statement:
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`

Hints from issue discussion:
This is a Postgres limitation, I believe, but we ought to produce a nicer error of course.

Commit message: Add max_enum_label_length enum.

Patch to review:
diff --git a/docs/stdlib/enum.rst b/docs/stdlib/enum.rst
index cf8110e72b7..2a8850d71b6 100644
--- a/docs/stdlib/enum.rst
+++ b/docs/stdlib/enum.rst
@@ -50,3 +50,5 @@ Enums
         properly reflected. To address this, consider using only
         identifier-like enum values in cases where such compatibility
         is needed.
+
+        Currently, enum values cannot be longer than 63 characters.
diff --git a/edb/pgsql/common.py b/edb/pgsql/common.py
index 2e63e1a2c38..4e456f9338d 100644
--- 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
index 05a09f164c4..64e66c03e76 100644
--- 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
diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py
index ed042db9e9b..ace3579fcff 100644
--- 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
index dd88552b16a..8d514a3f27f 100644
--- 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.


Code review comments:
diff_hunk: @@ -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
path: edb/pgsql/common.py
text: @user1:
Python constants are traditional `ALL_CAPS`