Repository: edgedb/edgedb

Issue:
Defaulting to `EMPTY LAST` in `ORDER BY .. DESC` prevents the use of backward index scan resulting in poor perf
See #3232 for context.  We should avoid emitting `EMPTY LAST` implicitly (both in GraphQL and EdgeQL translation).


Discussion:
Additionally, placing the ORDER BY value in a subquery contributes to the problem

Resolving patch:
diff --git a/edb/buildmeta.py b/edb/buildmeta.py
--- a/edb/buildmeta.py
+++ b/edb/buildmeta.py
@@ -42,7 +42,7 @@
 
 
 # Increment this whenever the database layout or stdlib changes.
-EDGEDB_CATALOG_VERSION = 2021_11_18_00_00
+EDGEDB_CATALOG_VERSION = 2021_12_02_00_00
 
 
 class MetadataError(Exception):
diff --git a/edb/pgsql/dbops/indexes.py b/edb/pgsql/dbops/indexes.py
--- a/edb/pgsql/dbops/indexes.py
+++ b/edb/pgsql/dbops/indexes.py
@@ -58,7 +58,7 @@ def code(self, block: base.PLBlock) -> str:
 
 class Index(tables.InheritableTableObject):
     def __init__(
-            self, name, table_name, unique=True, expr=None, predicate=None,
+            self, name, table_name, unique=True, exprs=None, predicate=None,
             inherit=False, metadata=None, columns=None):
         super().__init__(inherit=inherit, metadata=metadata)
 
@@ -71,7 +71,7 @@ def __init__(
             self.add_columns(columns)
         self.predicate = predicate
         self.unique = unique
-        self.expr = expr
+        self.exprs = exprs
 
         if self.name_in_catalog != self.name:
             self.add_metadata('fullname', self.name)
@@ -99,7 +99,7 @@ def declare_pl_desc_var(self, block: base.PLBlock) -> str:
             {desc_var}.is_unique := {self.unique};
             {desc_var}.predicate := {ql(self.predicate) if self.predicate
                                      else 'NULL'};
-            {desc_var}.expression := {ql(self.expr) if self.expr
+            {desc_var}.expression := {ql(', '.join(self.exprs)) if self.exprs
                                       else 'NULL'};
             {desc_var}.columns := ARRAY[{cols}]::text[];
             {desc_var}.metadata := {ql(json.dumps(self.metadata))};
@@ -147,10 +147,13 @@ def creation_pl_code(cls, desc_var: str, block: base.PLBlock) -> str:
         ''')
 
     def creation_code(self, block: base.PLBlock) -> str:
-        if self.expr:
-            expr = self.expr
+        if self.exprs:
+            exprs = self.exprs
         else:
-            expr = ', '.join(qi(c) for c in self.columns)
+            exprs = [qi(c) for c in self.columns]
+
+        # TODO: Make NULLs behavior configurable
+        expr = ', '.join(f'{e} NULLS FIRST' for e in exprs)
 
         code = '''
             CREATE {unique} INDEX {name}
diff --git a/edb/pgsql/delta.py b/edb/pgsql/delta.py
--- a/edb/pgsql/delta.py
+++ b/edb/pgsql/delta.py
@@ -2726,19 +2726,20 @@ def create_index(cls, index, schema, context):
 
         sql_tree = compiler.compile_ir_to_sql_tree(
             ir.expr, singleton_mode=True)
-        sql_expr = codegen.SQLSourceGenerator.to_source(sql_tree)
 
         if isinstance(sql_tree, pg_ast.ImplicitRowExpr):
-            # Trim the parentheses to avoid PostgreSQL choking on double
-            # parentheses. since it expects only a single set around the column
-            # list.
-            sql_expr = sql_expr[1:-1]
+            sql_exprs = [
+                codegen.SQLSourceGenerator.to_source(el)
+                for el in sql_tree.args
+            ]
+        else:
+            sql_exprs = [codegen.SQLSourceGenerator.to_source(sql_tree)]
 
         module_name = index.get_name(schema).module
         index_name = common.get_index_backend_name(
             index.id, module_name, catenate=False)
         pg_index = dbops.Index(
-            name=index_name[1], table_name=table_name, expr=sql_expr,
+            name=index_name[1], table_name=table_name, exprs=sql_exprs,
             unique=False, inherit=True,
             metadata={'schemaname': str(index.get_name(schema))})
         return dbops.CreateIndex(pg_index)


Test patch: