Repository: edgedb/edgedb

Issue:
 `IF THEN ELSE` returns incorrect result
<!-- 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: https://packages.edgedb.com/archive/x86_64-unknown-linux-gnu/edgedb-server-4.1%2B2194ddc.tar.zst
- EdgeDB CLI Version: 4.1.0-dev
- OS Version: x86_64 GNU/Linux 6.6.1-arch1

```edgeql
with
  arr := <array<str>>to_json(<str>$raw),
  a := if len(arr) = 3 then array_get(arr, 2) else "NOT_THREE",
  b := array_get(arr, 2) if len(arr) = 3 else "NOT_THREE",
select {
  a, b
}
```
$raw: `["a", "b", "c"]`

output `{ "NOT_THREE", "c" }`
expected: `{ "c", "c" }`


Discussion:
🤔 I cannot seem to reproduce this issue locally with 4.1:

```
EdgeDB 4.1+0c628ea (repl 3.5.0-dev)
Type \help for help, \quit to quit.
edgedb> with
.......   arr := <array<str>>to_json(<str>'["a", "b", "c"]'),
.......   a := if len(arr) = 3 then array_get(arr, 2) else "NOT_THREE",
.......   b := array_get(arr, 2) if len(arr) = 3 else "NOT_THREE",
....... select {
.......   a, b
....... };
{'c', 'c'}
```
Hmm, this seems to be a serialization issue since I get the same (wrong) result, but only if I pass the JSON string as a parameter:

```
edgedb> with
.......   arr := <array<str>>to_json(<str>'["a", "b", "c"]'),
.......   a := if len(arr) = 3 then array_get(arr, 2) else "NOT_THREE",
.......   b := array_get(arr, 2) if len(arr) = 3 else "NOT_THREE",
....... select {
.......   a, b
....... };
{'c', 'c'}
edgedb> with
.......   arr := <array<str>>to_json(<str>$raw),
.......   a := if len(arr) = 3 then array_get(arr, 2) else "NOT_THREE",
.......   b := array_get(arr, 2) if len(arr) = 3 else "NOT_THREE",
....... select {
.......   a, b
....... };
Parameter <str>$raw: ["a", "b", "c"]
{'NOT_THREE', 'c'}
```

Resolving patch:
diff --git a/edb/edgeql/compiler/stmtctx.py b/edb/edgeql/compiler/stmtctx.py
--- a/edb/edgeql/compiler/stmtctx.py
+++ b/edb/edgeql/compiler/stmtctx.py
@@ -294,8 +294,9 @@ def collect_params(ctx: context.ContextLevel) -> List[irast.Param]:
     lparams = [
         p for p in ctx.env.query_parameters.values() if not p.is_sub_param
     ]
-    if lparams and lparams[0].name.isdecimal():
-        lparams.sort(key=lambda x: int(x.name))
+    if ctx.env.script_params:
+        script_ordering = {k: i for i, k in enumerate(ctx.env.script_params)}
+        lparams.sort(key=lambda x: script_ordering[x.name])
 
     params = []
     # Now flatten it out, including all sub_params, making sure subparams
@@ -882,13 +883,20 @@ def preprocess_script(
     if params:
         check_params(params)
 
-        # Put them in order if they are positional
-        lparams = list(params.items())
-        if lparams[0][0].isdecimal():
-            lparams.sort(key=lambda x: int(x[0]))
-        # Otherwise make sure injected args come after
-        else:
-            lparams.sort(key=lambda x: x[0].startswith('__edb_arg_'))
-        params = dict(lparams)
+        def _arg_key(k: tuple[str, object]) -> int:
+            name = k[0]
+            arg_prefix = '__edb_arg_'
+            # Positional arguments should just be sorted numerically,
+            # while for named arguments, injected args should be sorted and
+            # need to come after normal ones. Normal named arguments can have
+            # any order.
+            if name.isdecimal():
+                return int(name)
+            elif name.startswith(arg_prefix):
+                return int(k[0][len(arg_prefix):])
+            else:
+                return -1
+
+        params = dict(sorted(params.items(), key=_arg_key))
 
     return irast.ScriptInfo(params=params, schema=ctx.env.schema)


Test patch:
diff --git a/tests/test_edgeql_expressions.py b/tests/test_edgeql_expressions.py
--- a/tests/test_edgeql_expressions.py
+++ b/tests/test_edgeql_expressions.py
@@ -8293,6 +8293,39 @@ async def test_edgeql_expr_if_else_10(self):
             [[]],
         )
 
+    async def test_edgeql_expr_if_else_11(self):
+        await self.assert_query_result(
+            r"""
+                select if 1 = <int64>$x then 2 else 3
+            """,
+            [2],
+            variables=dict(x=1),
+        )
+
+        await self.assert_query_result(
+            r"""
+                select if 1 = <int64>$x then 2 else 3
+            """,
+            [3],
+            variables=dict(x=-1),
+        )
+
+        await self.assert_query_result(
+            r"""
+                select 2 if 1 = <int64>$x else 3
+            """,
+            [2],
+            variables=dict(x=1),
+        )
+
+        await self.assert_query_result(
+            r"""
+                select 2 if 1 = <int64>$x else 3
+            """,
+            [3],
+            variables=dict(x=-1),
+        )
+
     async def test_edgeql_expr_if_else_toplevel(self):
         await self.assert_query_result(
             r"""