Repository: edgedb/edgedb

Issue:
JSON properties cannot be indexed by pg::gin.
<!-- 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.0
- EdgeDB CLI Version:
- OS Version:

Steps to Reproduce:

https://github.com/edgedb/edgedb/blob/62bbaf17f8f00d2a8980f6a5d4c8090294842146/edb/schema/indexes.py#L67C30-L67C38

`alter type A { create index pg::gist on (.data) };`

```
error: SchemaDefinitionError: index expression (.data) is not of a valid type for the index 'pg::gin'
```


Discussion:
@vpetrovykh could you make sure json is supported by pg::gin?

We should also test that the index actually can get hit; that might require some tweaks too

Resolving patch:
diff --git a/edb/schema/indexes.py b/edb/schema/indexes.py
--- a/edb/schema/indexes.py
+++ b/edb/schema/indexes.py
@@ -65,7 +65,14 @@ def is_index_valid_for_type(
         case 'pg::btree':
             return True
         case 'pg::gin':
-            return expr_type.is_array()
+            return (
+                expr_type.is_array()
+                or
+                expr_type.issubclass(
+                    schema,
+                    schema.get('std::json', type=s_scalars.ScalarType),
+                )
+            )
         case 'fts::textsearch':
             return expr_type.issubclass(
                 schema, schema.get('std::str', type=s_scalars.ScalarType))


Test patch:
diff --git a/tests/schemas/explain.esdl b/tests/schemas/explain.esdl
--- a/tests/schemas/explain.esdl
+++ b/tests/schemas/explain.esdl
@@ -108,13 +108,19 @@ type URL extending Named {
 }
 
 type RangeTest {
-    required property rval -> range<int64>;
-    required property mval -> multirange<int64>;
-    required property rdate -> range<cal::local_date>;
-    required property mdate -> multirange<cal::local_date>;
+    required rval: range<int64>;
+    required mval: multirange<int64>;
+    required rdate: range<cal::local_date>;
+    required mdate: multirange<cal::local_date>;
 
     index pg::gist on (.rval);
     index pg::gist on (.mval);
     index pg::gist on (.rdate);
     index pg::gist on (.mdate);
+}
+
+type JSONTest {
+    required val: json;
+
+    index pg::gin on (.val);
 }
\ No newline at end of file
diff --git a/tests/schemas/explain_setup.edgeql b/tests/schemas/explain_setup.edgeql
--- a/tests/schemas/explain_setup.edgeql
+++ b/tests/schemas/explain_setup.edgeql
@@ -181,4 +181,9 @@ for x in range_unpack(range(0, 500_000)) union (
 );
 
 
+for x in range_unpack(range(0, 100_000)) union (
+    insert JSONTest{val := <json>(a:=x, b:=round(random()*1000))}
+);
+
+
 administer statistics_update();
diff --git a/tests/test_edgeql_explain.py b/tests/test_edgeql_explain.py
--- a/tests/test_edgeql_explain.py
+++ b/tests/test_edgeql_explain.py
@@ -1873,6 +1873,53 @@ async def test_edgeql_explain_ranges_bounded_above_01(self):
             self.get_gist_index_expected_res('mdate', 'IndexScan'),
         )
 
+    async def test_edgeql_explain_json_contains_01(self):
+        res = await self.explain('''
+            select JSONTest {id, val}
+            filter contains(.val, <json>(a := 123))
+        ''')
+        self.assert_plan(
+            res['fine_grained']['subplans'][1],
+            {
+                "pipeline": [
+                    {
+                        "plan_type": "BitmapHeapScan",
+                    },
+                ],
+                "subplans": [
+                    {
+                        "pipeline": [
+                            {
+                                "plan_type": "BitmapIndexScan",
+                                "properties": tb.bag([
+                                    {
+                                        'important': False,
+                                        'title': 'parent_relationship',
+                                        'type': 'text',
+                                        'value': 'Outer',
+                                    },
+                                    {
+                                        'important': True,
+                                        'title': 'index_name',
+                                        'type': 'index',
+                                        'value':
+                                            f"index 'pg::gin' of object"
+                                            f" type 'default::JSONTest'"
+                                            f" on (.val)",
+                                    },
+                                    {
+                                        'important': False,
+                                        'title': 'index_cond',
+                                        'type': 'expr',
+                                    },
+                                ]),
+                            },
+                        ],
+                    },
+                ],
+            },
+        )
+
 
 class NameTranslation(unittest.TestCase):