We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
bug: re_replace() doesn't fill params right?
### What happened?

Thanks for looking at this.

```python
import ibis
# this errors:
ibis.literal("hi").re_replace("x", "y").substitute({"d": "b"}, else_="k")
# This works fine:
# ibis.literal("hi").re_replace("x", "y")
# this works fine:
# ibis.literal("hi").substitute({"d": "b"}, else_="k")
```

It generates:
```
SELECT CASE regexp_replace(:param_1, :param_2, :param_3, :regexp_replace_1) WHEN :param_4 THEN :param_5 ELSE :param_6 END AS "SimpleCase(RegexReplace('hi', 'x', 'y'), 'k')"
```
which results in
```
Error: Invalid Input Error: Unrecognized Regex option d
[SQL: SELECT CASE regexp_replace(?, ?, ?, ?) WHEN ? THEN ? ELSE ? END AS "SimpleCase(RegexReplace('hi', 'x', 'y'), 'k')"]
[parameters: ('hi', 'x', 'y', 'g', 'd', 'b', 'k')]
```

So something about how ibis doesn't correctly fill in `:regexp_replace_1` with the constant "g" flag as it should, but the value I passed in, "d" from the substitute(), is used instead.

Lemme know if you think of a workaround, I could write my own extension function for re_replace() until this is fixed, but maybe there's something easier.

### What version of ibis are you using?

4.1.0

### What backend(s) are you using, if any?

duckdb

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ibis/backends/duckdb/registry.py`
Content:
```
1 from __future__ import annotations
2 
3 import operator
4 from typing import Any, Mapping
5 
6 import numpy as np
7 import sqlalchemy as sa
8 from sqlalchemy.ext.compiler import compiles
9 from sqlalchemy.sql.functions import GenericFunction
10 
11 import ibis.expr.operations as ops
12 from ibis.backends.base.sql import alchemy
13 from ibis.backends.base.sql.alchemy import unary
14 from ibis.backends.base.sql.alchemy.datatypes import StructType
15 from ibis.backends.base.sql.alchemy.registry import (
16     _table_column,
17     geospatial_functions,
18     reduction,
19 )
20 from ibis.backends.postgres.registry import (
21     _array_index,
22     _array_slice,
23     fixed_arity,
24     operation_registry,
25 )
26 
27 operation_registry = {
28     op: operation_registry[op]
29     for op in operation_registry.keys() - geospatial_functions.keys()
30 }
31 
32 
33 def _round(t, op):
34     arg, digits = op.args
35     sa_arg = t.translate(arg)
36 
37     if digits is None:
38         return sa.func.round(sa_arg)
39 
40     return sa.func.round(sa_arg, t.translate(digits))
41 
42 
43 _LOG_BASE_FUNCS = {
44     2: sa.func.log2,
45     10: sa.func.log,
46 }
47 
48 
49 def _generic_log(arg, base):
50     return sa.func.ln(arg) / sa.func.ln(base)
51 
52 
53 def _log(t, op):
54     arg, base = op.args
55     sa_arg = t.translate(arg)
56     if base is not None:
57         sa_base = t.translate(base)
58         try:
59             base_value = sa_base.value
60         except AttributeError:
61             return _generic_log(sa_arg, sa_base)
62         else:
63             func = _LOG_BASE_FUNCS.get(base_value, _generic_log)
64             return func(sa_arg)
65     return sa.func.ln(sa_arg)
66 
67 
68 def _timestamp_from_unix(t, op):
69     arg, unit = op.args
70     arg = t.translate(arg)
71 
72     if unit == "ms":
73         return sa.func.epoch_ms(arg)
74     elif unit == "s":
75         return sa.func.to_timestamp(arg)
76     else:
77         raise ValueError(f"`{unit}` unit is not supported!")
78 
79 
80 class struct_pack(GenericFunction):
81     def __init__(self, values: Mapping[str, Any], *, type: StructType) -> None:
82         super().__init__()
83         self.values = values
84         self.type = type
85 
86 
87 @compiles(struct_pack, "duckdb")
88 def compiles_struct_pack(element, compiler, **kw):
89     quote = compiler.preparer.quote
90     args = ", ".join(
91         "{key} := {value}".format(key=quote(key), value=compiler.process(value, **kw))
92         for key, value in element.values.items()
93     )
94     return f"struct_pack({args})"
95 
96 
97 def _literal(t, op):
98     dtype = op.output_dtype
99     sqla_type = t.get_sqla_type(dtype)
100 
101     value = op.value
102     if dtype.is_interval():
103         return sa.literal_column(f"INTERVAL '{value} {dtype.resolution}'")
104     elif dtype.is_set() or dtype.is_array():
105         values = value.tolist() if isinstance(value, np.ndarray) else value
106         return sa.cast(sa.func.list_value(*values), sqla_type)
107     elif dtype.is_floating():
108         if not np.isfinite(value):
109             if np.isnan(value):
110                 value = "NaN"
111             else:
112                 assert np.isinf(value), "value is neither finite, nan nor infinite"
113                 prefix = "-" * (value < 0)
114                 value = f"{prefix}Inf"
115         return sa.cast(sa.literal(value), sqla_type)
116     elif dtype.is_struct():
117         return struct_pack(
118             {
119                 key: t.translate(ops.Literal(val, dtype=dtype[key]))
120                 for key, val in value.items()
121             },
122             type=sqla_type,
123         )
124     elif dtype.is_string():
125         return sa.literal(value)
126     elif dtype.is_map():
127         raise NotImplementedError(
128             f"Ibis dtype `{dtype}` with mapping type "
129             f"`{type(value).__name__}` isn't yet supported with the duckdb "
130             "backend"
131         )
132     else:
133         return sa.cast(sa.literal(value), sqla_type)
134 
135 
136 def _neg_idx_to_pos(array, idx):
137     if_ = getattr(sa.func, "if")
138     arg_length = sa.func.array_length(array)
139     return if_(idx < 0, arg_length + sa.func.greatest(idx, -arg_length), idx)
140 
141 
142 def _regex_extract(string, pattern, index):
143     result = sa.case(
144         (
145             sa.func.regexp_matches(string, pattern),
146             sa.func.regexp_extract(
147                 string,
148                 pattern,
149                 # DuckDB requires the index to be a constant so we compile
150                 # the value and inline it using sa.text
151                 sa.text(str(index.compile(compile_kwargs=dict(literal_binds=True)))),
152             ),
153         ),
154         else_="",
155     )
156     return result
157 
158 
159 def _json_get_item(left, path):
160     # Workaround for https://github.com/duckdb/duckdb/issues/5063
161     # In some situations duckdb silently does the wrong thing if
162     # the path is parametrized.
163     sa_path = sa.text(str(path.compile(compile_kwargs=dict(literal_binds=True))))
164     return left.op("->")(sa_path)
165 
166 
167 def _strftime(t, op):
168     format_str = op.format_str
169     if not isinstance(format_str_op := format_str, ops.Literal):
170         raise TypeError(
171             f"DuckDB format_str must be a literal `str`; got {type(format_str)}"
172         )
173     return sa.func.strftime(t.translate(op.arg), sa.text(repr(format_str_op.value)))
174 
175 
176 def _arbitrary(t, op):
177     if (how := op.how) == "heavy":
178         raise ValueError(f"how={how!r} not supported in the DuckDB backend")
179     return t._reduction(getattr(sa.func, how), op)
180 
181 
182 def _string_agg(t, op):
183     if not isinstance(op.sep, ops.Literal):
184         raise TypeError(
185             "Separator argument to group_concat operation must be a constant"
186         )
187     agg = sa.func.string_agg(t.translate(op.arg), sa.text(repr(op.sep.value)))
188     if (where := op.where) is not None:
189         return agg.filter(t.translate(where))
190     return agg
191 
192 
193 def _struct_column(t, op):
194     return struct_pack(
195         dict(zip(op.names, map(t.translate, op.values))),
196         type=t.get_sqla_type(op.output_dtype),
197     )
198 
199 
200 operation_registry.update(
201     {
202         ops.ArrayColumn: (
203             lambda t, op: sa.cast(
204                 sa.func.list_value(*map(t.translate, op.cols)),
205                 t.get_sqla_type(op.output_dtype),
206             )
207         ),
208         ops.ArrayConcat: fixed_arity(sa.func.array_concat, 2),
209         ops.ArrayRepeat: fixed_arity(
210             lambda arg, times: sa.func.flatten(
211                 sa.func.array(
212                     sa.select(arg).select_from(sa.func.range(times)).scalar_subquery()
213                 )
214             ),
215             2,
216         ),
217         ops.ArrayLength: unary(sa.func.array_length),
218         ops.ArraySlice: _array_slice(
219             index_converter=_neg_idx_to_pos,
220             array_length=sa.func.array_length,
221             func=sa.func.list_slice,
222         ),
223         ops.ArrayIndex: _array_index(
224             index_converter=_neg_idx_to_pos, func=sa.func.list_extract
225         ),
226         ops.DayOfWeekName: unary(sa.func.dayname),
227         ops.Literal: _literal,
228         ops.Log2: unary(sa.func.log2),
229         ops.Ln: unary(sa.func.ln),
230         ops.Log: _log,
231         ops.IsNan: unary(sa.func.isnan),
232         # TODO: map operations, but DuckDB's maps are multimaps
233         ops.Modulus: fixed_arity(operator.mod, 2),
234         ops.Round: _round,
235         ops.StructField: (
236             lambda t, op: sa.func.struct_extract(
237                 t.translate(op.arg),
238                 sa.text(repr(op.field)),
239                 type_=t.get_sqla_type(op.output_dtype),
240             )
241         ),
242         ops.TableColumn: _table_column,
243         ops.TimestampFromUNIX: _timestamp_from_unix,
244         ops.TimestampNow: fixed_arity(
245             # duckdb 0.6.0 changes now to be a tiemstamp with time zone force
246             # it back to the original for backwards compatibility
247             lambda *_: sa.cast(sa.func.now(), sa.TIMESTAMP),
248             0,
249         ),
250         ops.RegexExtract: fixed_arity(_regex_extract, 3),
251         ops.RegexReplace: fixed_arity(
252             lambda *args: sa.func.regexp_replace(*args, "g"), 3
253         ),
254         ops.StringContains: fixed_arity(sa.func.contains, 2),
255         ops.ApproxMedian: reduction(
256             # without inline text, duckdb fails with
257             # RuntimeError: INTERNAL Error: Invalid PhysicalType for GetTypeIdSize
258             lambda arg: sa.func.approx_quantile(arg, sa.text(str(0.5)))
259         ),
260         ops.ApproxCountDistinct: reduction(sa.func.approx_count_distinct),
261         ops.Mode: reduction(sa.func.mode),
262         ops.Strftime: _strftime,
263         ops.Arbitrary: _arbitrary,
264         ops.GroupConcat: _string_agg,
265         ops.StructColumn: _struct_column,
266         ops.ArgMin: reduction(sa.func.min_by),
267         ops.ArgMax: reduction(sa.func.max_by),
268         ops.BitwiseXor: fixed_arity(sa.func.xor, 2),
269         ops.JSONGetItem: fixed_arity(_json_get_item, 2),
270         ops.RowID: lambda *_: sa.literal_column('rowid'),
271         ops.StringToTimestamp: fixed_arity(sa.func.strptime, 2),
272         ops.Quantile: reduction(sa.func.quantile_cont),
273         ops.MultiQuantile: reduction(sa.func.quantile_cont),
274         ops.TypeOf: unary(sa.func.typeof),
275         ops.IntervalAdd: fixed_arity(operator.add, 2),
276         ops.IntervalSubtract: fixed_arity(operator.sub, 2),
277         ops.Capitalize: alchemy.sqlalchemy_operation_registry[ops.Capitalize],
278         ops.ArrayStringJoin: fixed_arity(
279             lambda sep, arr: sa.func.array_aggr(arr, sa.text("'string_agg'"), sep), 2
280         ),
281     }
282 )
283 
284 
285 _invalid_operations = {
286     # ibis.expr.operations.analytic
287     ops.CumulativeAll,
288     ops.CumulativeAny,
289     ops.CumulativeOp,
290     ops.NTile,
291     # ibis.expr.operations.strings
292     ops.Translate,
293     # ibis.expr.operations.maps
294     ops.MapGet,
295     ops.MapContains,
296     ops.MapKeys,
297     ops.MapValues,
298     ops.MapMerge,
299     ops.MapLength,
300     ops.Map,
301 }
302 
303 operation_registry = {
304     k: v for k, v in operation_registry.items() if k not in _invalid_operations
305 }
306
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ibis/backends/duckdb/registry.py b/ibis/backends/duckdb/registry.py
--- a/ibis/backends/duckdb/registry.py
+++ b/ibis/backends/duckdb/registry.py
@@ -249,7 +249,7 @@
         ),
         ops.RegexExtract: fixed_arity(_regex_extract, 3),
         ops.RegexReplace: fixed_arity(
-            lambda *args: sa.func.regexp_replace(*args, "g"), 3
+            lambda *args: sa.func.regexp_replace(*args, sa.text("'g'")), 3
         ),
         ops.StringContains: fixed_arity(sa.func.contains, 2),
         ops.ApproxMedian: reduction(

{"golden_diff": "diff --git a/ibis/backends/duckdb/registry.py b/ibis/backends/duckdb/registry.py\n--- a/ibis/backends/duckdb/registry.py\n+++ b/ibis/backends/duckdb/registry.py\n@@ -249,7 +249,7 @@\n         ),\n         ops.RegexExtract: fixed_arity(_regex_extract, 3),\n         ops.RegexReplace: fixed_arity(\n-            lambda *args: sa.func.regexp_replace(*args, \"g\"), 3\n+            lambda *args: sa.func.regexp_replace(*args, sa.text(\"'g'\")), 3\n         ),\n         ops.StringContains: fixed_arity(sa.func.contains, 2),\n         ops.ApproxMedian: reduction(\n", "issue": "bug: re_replace() doesn't fill params right?\n### What happened?\n\nThanks for looking at this.\r\n\r\n```python\r\nimport ibis\r\n# this errors:\r\nibis.literal(\"hi\").re_replace(\"x\", \"y\").substitute({\"d\": \"b\"}, else_=\"k\")\r\n# This works fine:\r\n# ibis.literal(\"hi\").re_replace(\"x\", \"y\")\r\n# this works fine:\r\n# ibis.literal(\"hi\").substitute({\"d\": \"b\"}, else_=\"k\")\r\n```\r\n\r\nIt generates:\r\n```\r\nSELECT CASE regexp_replace(:param_1, :param_2, :param_3, :regexp_replace_1) WHEN :param_4 THEN :param_5 ELSE :param_6 END AS \"SimpleCase(RegexReplace('hi', 'x', 'y'), 'k')\"\r\n```\r\nwhich results in\r\n```\r\nError: Invalid Input Error: Unrecognized Regex option d\r\n[SQL: SELECT CASE regexp_replace(?, ?, ?, ?) WHEN ? THEN ? ELSE ? END AS \"SimpleCase(RegexReplace('hi', 'x', 'y'), 'k')\"]\r\n[parameters: ('hi', 'x', 'y', 'g', 'd', 'b', 'k')]\r\n```\r\n\r\nSo something about how ibis doesn't correctly fill in `:regexp_replace_1` with the constant \"g\" flag as it should, but the value I passed in, \"d\" from the substitute(), is used instead.\r\n\r\nLemme know if you think of a workaround, I could write my own extension function for re_replace() until this is fixed, but maybe there's something easier.\n\n### What version of ibis are you using?\n\n4.1.0\n\n### What backend(s) are you using, if any?\n\nduckdb\n\n### Relevant log output\n\n_No response_\n\n### Code of Conduct\n\n- [X] I agree to follow this project's Code of Conduct\n", "before_files": [{"content": "from __future__ import annotations\n\nimport operator\nfrom typing import Any, Mapping\n\nimport numpy as np\nimport sqlalchemy as sa\nfrom sqlalchemy.ext.compiler import compiles\nfrom sqlalchemy.sql.functions import GenericFunction\n\nimport ibis.expr.operations as ops\nfrom ibis.backends.base.sql import alchemy\nfrom ibis.backends.base.sql.alchemy import unary\nfrom ibis.backends.base.sql.alchemy.datatypes import StructType\nfrom ibis.backends.base.sql.alchemy.registry import (\n    _table_column,\n    geospatial_functions,\n    reduction,\n)\nfrom ibis.backends.postgres.registry import (\n    _array_index,\n    _array_slice,\n    fixed_arity,\n    operation_registry,\n)\n\noperation_registry = {\n    op: operation_registry[op]\n    for op in operation_registry.keys() - geospatial_functions.keys()\n}\n\n\ndef _round(t, op):\n    arg, digits = op.args\n    sa_arg = t.translate(arg)\n\n    if digits is None:\n        return sa.func.round(sa_arg)\n\n    return sa.func.round(sa_arg, t.translate(digits))\n\n\n_LOG_BASE_FUNCS = {\n    2: sa.func.log2,\n    10: sa.func.log,\n}\n\n\ndef _generic_log(arg, base):\n    return sa.func.ln(arg) / sa.func.ln(base)\n\n\ndef _log(t, op):\n    arg, base = op.args\n    sa_arg = t.translate(arg)\n    if base is not None:\n        sa_base = t.translate(base)\n        try:\n            base_value = sa_base.value\n        except AttributeError:\n            return _generic_log(sa_arg, sa_base)\n        else:\n            func = _LOG_BASE_FUNCS.get(base_value, _generic_log)\n            return func(sa_arg)\n    return sa.func.ln(sa_arg)\n\n\ndef _timestamp_from_unix(t, op):\n    arg, unit = op.args\n    arg = t.translate(arg)\n\n    if unit == \"ms\":\n        return sa.func.epoch_ms(arg)\n    elif unit == \"s\":\n        return sa.func.to_timestamp(arg)\n    else:\n        raise ValueError(f\"`{unit}` unit is not supported!\")\n\n\nclass struct_pack(GenericFunction):\n    def __init__(self, values: Mapping[str, Any], *, type: StructType) -> None:\n        super().__init__()\n        self.values = values\n        self.type = type\n\n\n@compiles(struct_pack, \"duckdb\")\ndef compiles_struct_pack(element, compiler, **kw):\n    quote = compiler.preparer.quote\n    args = \", \".join(\n        \"{key} := {value}\".format(key=quote(key), value=compiler.process(value, **kw))\n        for key, value in element.values.items()\n    )\n    return f\"struct_pack({args})\"\n\n\ndef _literal(t, op):\n    dtype = op.output_dtype\n    sqla_type = t.get_sqla_type(dtype)\n\n    value = op.value\n    if dtype.is_interval():\n        return sa.literal_column(f\"INTERVAL '{value} {dtype.resolution}'\")\n    elif dtype.is_set() or dtype.is_array():\n        values = value.tolist() if isinstance(value, np.ndarray) else value\n        return sa.cast(sa.func.list_value(*values), sqla_type)\n    elif dtype.is_floating():\n        if not np.isfinite(value):\n            if np.isnan(value):\n                value = \"NaN\"\n            else:\n                assert np.isinf(value), \"value is neither finite, nan nor infinite\"\n                prefix = \"-\" * (value < 0)\n                value = f\"{prefix}Inf\"\n        return sa.cast(sa.literal(value), sqla_type)\n    elif dtype.is_struct():\n        return struct_pack(\n            {\n                key: t.translate(ops.Literal(val, dtype=dtype[key]))\n                for key, val in value.items()\n            },\n            type=sqla_type,\n        )\n    elif dtype.is_string():\n        return sa.literal(value)\n    elif dtype.is_map():\n        raise NotImplementedError(\n            f\"Ibis dtype `{dtype}` with mapping type \"\n            f\"`{type(value).__name__}` isn't yet supported with the duckdb \"\n            \"backend\"\n        )\n    else:\n        return sa.cast(sa.literal(value), sqla_type)\n\n\ndef _neg_idx_to_pos(array, idx):\n    if_ = getattr(sa.func, \"if\")\n    arg_length = sa.func.array_length(array)\n    return if_(idx < 0, arg_length + sa.func.greatest(idx, -arg_length), idx)\n\n\ndef _regex_extract(string, pattern, index):\n    result = sa.case(\n        (\n            sa.func.regexp_matches(string, pattern),\n            sa.func.regexp_extract(\n                string,\n                pattern,\n                # DuckDB requires the index to be a constant so we compile\n                # the value and inline it using sa.text\n                sa.text(str(index.compile(compile_kwargs=dict(literal_binds=True)))),\n            ),\n        ),\n        else_=\"\",\n    )\n    return result\n\n\ndef _json_get_item(left, path):\n    # Workaround for https://github.com/duckdb/duckdb/issues/5063\n    # In some situations duckdb silently does the wrong thing if\n    # the path is parametrized.\n    sa_path = sa.text(str(path.compile(compile_kwargs=dict(literal_binds=True))))\n    return left.op(\"->\")(sa_path)\n\n\ndef _strftime(t, op):\n    format_str = op.format_str\n    if not isinstance(format_str_op := format_str, ops.Literal):\n        raise TypeError(\n            f\"DuckDB format_str must be a literal `str`; got {type(format_str)}\"\n        )\n    return sa.func.strftime(t.translate(op.arg), sa.text(repr(format_str_op.value)))\n\n\ndef _arbitrary(t, op):\n    if (how := op.how) == \"heavy\":\n        raise ValueError(f\"how={how!r} not supported in the DuckDB backend\")\n    return t._reduction(getattr(sa.func, how), op)\n\n\ndef _string_agg(t, op):\n    if not isinstance(op.sep, ops.Literal):\n        raise TypeError(\n            \"Separator argument to group_concat operation must be a constant\"\n        )\n    agg = sa.func.string_agg(t.translate(op.arg), sa.text(repr(op.sep.value)))\n    if (where := op.where) is not None:\n        return agg.filter(t.translate(where))\n    return agg\n\n\ndef _struct_column(t, op):\n    return struct_pack(\n        dict(zip(op.names, map(t.translate, op.values))),\n        type=t.get_sqla_type(op.output_dtype),\n    )\n\n\noperation_registry.update(\n    {\n        ops.ArrayColumn: (\n            lambda t, op: sa.cast(\n                sa.func.list_value(*map(t.translate, op.cols)),\n                t.get_sqla_type(op.output_dtype),\n            )\n        ),\n        ops.ArrayConcat: fixed_arity(sa.func.array_concat, 2),\n        ops.ArrayRepeat: fixed_arity(\n            lambda arg, times: sa.func.flatten(\n                sa.func.array(\n                    sa.select(arg).select_from(sa.func.range(times)).scalar_subquery()\n                )\n            ),\n            2,\n        ),\n        ops.ArrayLength: unary(sa.func.array_length),\n        ops.ArraySlice: _array_slice(\n            index_converter=_neg_idx_to_pos,\n            array_length=sa.func.array_length,\n            func=sa.func.list_slice,\n        ),\n        ops.ArrayIndex: _array_index(\n            index_converter=_neg_idx_to_pos, func=sa.func.list_extract\n        ),\n        ops.DayOfWeekName: unary(sa.func.dayname),\n        ops.Literal: _literal,\n        ops.Log2: unary(sa.func.log2),\n        ops.Ln: unary(sa.func.ln),\n        ops.Log: _log,\n        ops.IsNan: unary(sa.func.isnan),\n        # TODO: map operations, but DuckDB's maps are multimaps\n        ops.Modulus: fixed_arity(operator.mod, 2),\n        ops.Round: _round,\n        ops.StructField: (\n            lambda t, op: sa.func.struct_extract(\n                t.translate(op.arg),\n                sa.text(repr(op.field)),\n                type_=t.get_sqla_type(op.output_dtype),\n            )\n        ),\n        ops.TableColumn: _table_column,\n        ops.TimestampFromUNIX: _timestamp_from_unix,\n        ops.TimestampNow: fixed_arity(\n            # duckdb 0.6.0 changes now to be a tiemstamp with time zone force\n            # it back to the original for backwards compatibility\n            lambda *_: sa.cast(sa.func.now(), sa.TIMESTAMP),\n            0,\n        ),\n        ops.RegexExtract: fixed_arity(_regex_extract, 3),\n        ops.RegexReplace: fixed_arity(\n            lambda *args: sa.func.regexp_replace(*args, \"g\"), 3\n        ),\n        ops.StringContains: fixed_arity(sa.func.contains, 2),\n        ops.ApproxMedian: reduction(\n            # without inline text, duckdb fails with\n            # RuntimeError: INTERNAL Error: Invalid PhysicalType for GetTypeIdSize\n            lambda arg: sa.func.approx_quantile(arg, sa.text(str(0.5)))\n        ),\n        ops.ApproxCountDistinct: reduction(sa.func.approx_count_distinct),\n        ops.Mode: reduction(sa.func.mode),\n        ops.Strftime: _strftime,\n        ops.Arbitrary: _arbitrary,\n        ops.GroupConcat: _string_agg,\n        ops.StructColumn: _struct_column,\n        ops.ArgMin: reduction(sa.func.min_by),\n        ops.ArgMax: reduction(sa.func.max_by),\n        ops.BitwiseXor: fixed_arity(sa.func.xor, 2),\n        ops.JSONGetItem: fixed_arity(_json_get_item, 2),\n        ops.RowID: lambda *_: sa.literal_column('rowid'),\n        ops.StringToTimestamp: fixed_arity(sa.func.strptime, 2),\n        ops.Quantile: reduction(sa.func.quantile_cont),\n        ops.MultiQuantile: reduction(sa.func.quantile_cont),\n        ops.TypeOf: unary(sa.func.typeof),\n        ops.IntervalAdd: fixed_arity(operator.add, 2),\n        ops.IntervalSubtract: fixed_arity(operator.sub, 2),\n        ops.Capitalize: alchemy.sqlalchemy_operation_registry[ops.Capitalize],\n        ops.ArrayStringJoin: fixed_arity(\n            lambda sep, arr: sa.func.array_aggr(arr, sa.text(\"'string_agg'\"), sep), 2\n        ),\n    }\n)\n\n\n_invalid_operations = {\n    # ibis.expr.operations.analytic\n    ops.CumulativeAll,\n    ops.CumulativeAny,\n    ops.CumulativeOp,\n    ops.NTile,\n    # ibis.expr.operations.strings\n    ops.Translate,\n    # ibis.expr.operations.maps\n    ops.MapGet,\n    ops.MapContains,\n    ops.MapKeys,\n    ops.MapValues,\n    ops.MapMerge,\n    ops.MapLength,\n    ops.Map,\n}\n\noperation_registry = {\n    k: v for k, v in operation_registry.items() if k not in _invalid_operations\n}\n", "path": "ibis/backends/duckdb/registry.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport operator\nfrom typing import Any, Mapping\n\nimport numpy as np\nimport sqlalchemy as sa\nfrom sqlalchemy.ext.compiler import compiles\nfrom sqlalchemy.sql.functions import GenericFunction\n\nimport ibis.expr.operations as ops\nfrom ibis.backends.base.sql import alchemy\nfrom ibis.backends.base.sql.alchemy import unary\nfrom ibis.backends.base.sql.alchemy.datatypes import StructType\nfrom ibis.backends.base.sql.alchemy.registry import (\n    _table_column,\n    geospatial_functions,\n    reduction,\n)\nfrom ibis.backends.postgres.registry import (\n    _array_index,\n    _array_slice,\n    fixed_arity,\n    operation_registry,\n)\n\noperation_registry = {\n    op: operation_registry[op]\n    for op in operation_registry.keys() - geospatial_functions.keys()\n}\n\n\ndef _round(t, op):\n    arg, digits = op.args\n    sa_arg = t.translate(arg)\n\n    if digits is None:\n        return sa.func.round(sa_arg)\n\n    return sa.func.round(sa_arg, t.translate(digits))\n\n\n_LOG_BASE_FUNCS = {\n    2: sa.func.log2,\n    10: sa.func.log,\n}\n\n\ndef _generic_log(arg, base):\n    return sa.func.ln(arg) / sa.func.ln(base)\n\n\ndef _log(t, op):\n    arg, base = op.args\n    sa_arg = t.translate(arg)\n    if base is not None:\n        sa_base = t.translate(base)\n        try:\n            base_value = sa_base.value\n        except AttributeError:\n            return _generic_log(sa_arg, sa_base)\n        else:\n            func = _LOG_BASE_FUNCS.get(base_value, _generic_log)\n            return func(sa_arg)\n    return sa.func.ln(sa_arg)\n\n\ndef _timestamp_from_unix(t, op):\n    arg, unit = op.args\n    arg = t.translate(arg)\n\n    if unit == \"ms\":\n        return sa.func.epoch_ms(arg)\n    elif unit == \"s\":\n        return sa.func.to_timestamp(arg)\n    else:\n        raise ValueError(f\"`{unit}` unit is not supported!\")\n\n\nclass struct_pack(GenericFunction):\n    def __init__(self, values: Mapping[str, Any], *, type: StructType) -> None:\n        super().__init__()\n        self.values = values\n        self.type = type\n\n\n@compiles(struct_pack, \"duckdb\")\ndef compiles_struct_pack(element, compiler, **kw):\n    quote = compiler.preparer.quote\n    args = \", \".join(\n        \"{key} := {value}\".format(key=quote(key), value=compiler.process(value, **kw))\n        for key, value in element.values.items()\n    )\n    return f\"struct_pack({args})\"\n\n\ndef _literal(t, op):\n    dtype = op.output_dtype\n    sqla_type = t.get_sqla_type(dtype)\n\n    value = op.value\n    if dtype.is_interval():\n        return sa.literal_column(f\"INTERVAL '{value} {dtype.resolution}'\")\n    elif dtype.is_set() or dtype.is_array():\n        values = value.tolist() if isinstance(value, np.ndarray) else value\n        return sa.cast(sa.func.list_value(*values), sqla_type)\n    elif dtype.is_floating():\n        if not np.isfinite(value):\n            if np.isnan(value):\n                value = \"NaN\"\n            else:\n                assert np.isinf(value), \"value is neither finite, nan nor infinite\"\n                prefix = \"-\" * (value < 0)\n                value = f\"{prefix}Inf\"\n        return sa.cast(sa.literal(value), sqla_type)\n    elif dtype.is_struct():\n        return struct_pack(\n            {\n                key: t.translate(ops.Literal(val, dtype=dtype[key]))\n                for key, val in value.items()\n            },\n            type=sqla_type,\n        )\n    elif dtype.is_string():\n        return sa.literal(value)\n    elif dtype.is_map():\n        raise NotImplementedError(\n            f\"Ibis dtype `{dtype}` with mapping type \"\n            f\"`{type(value).__name__}` isn't yet supported with the duckdb \"\n            \"backend\"\n        )\n    else:\n        return sa.cast(sa.literal(value), sqla_type)\n\n\ndef _neg_idx_to_pos(array, idx):\n    if_ = getattr(sa.func, \"if\")\n    arg_length = sa.func.array_length(array)\n    return if_(idx < 0, arg_length + sa.func.greatest(idx, -arg_length), idx)\n\n\ndef _regex_extract(string, pattern, index):\n    result = sa.case(\n        (\n            sa.func.regexp_matches(string, pattern),\n            sa.func.regexp_extract(\n                string,\n                pattern,\n                # DuckDB requires the index to be a constant so we compile\n                # the value and inline it using sa.text\n                sa.text(str(index.compile(compile_kwargs=dict(literal_binds=True)))),\n            ),\n        ),\n        else_=\"\",\n    )\n    return result\n\n\ndef _json_get_item(left, path):\n    # Workaround for https://github.com/duckdb/duckdb/issues/5063\n    # In some situations duckdb silently does the wrong thing if\n    # the path is parametrized.\n    sa_path = sa.text(str(path.compile(compile_kwargs=dict(literal_binds=True))))\n    return left.op(\"->\")(sa_path)\n\n\ndef _strftime(t, op):\n    format_str = op.format_str\n    if not isinstance(format_str_op := format_str, ops.Literal):\n        raise TypeError(\n            f\"DuckDB format_str must be a literal `str`; got {type(format_str)}\"\n        )\n    return sa.func.strftime(t.translate(op.arg), sa.text(repr(format_str_op.value)))\n\n\ndef _arbitrary(t, op):\n    if (how := op.how) == \"heavy\":\n        raise ValueError(f\"how={how!r} not supported in the DuckDB backend\")\n    return t._reduction(getattr(sa.func, how), op)\n\n\ndef _string_agg(t, op):\n    if not isinstance(op.sep, ops.Literal):\n        raise TypeError(\n            \"Separator argument to group_concat operation must be a constant\"\n        )\n    agg = sa.func.string_agg(t.translate(op.arg), sa.text(repr(op.sep.value)))\n    if (where := op.where) is not None:\n        return agg.filter(t.translate(where))\n    return agg\n\n\ndef _struct_column(t, op):\n    return struct_pack(\n        dict(zip(op.names, map(t.translate, op.values))),\n        type=t.get_sqla_type(op.output_dtype),\n    )\n\n\noperation_registry.update(\n    {\n        ops.ArrayColumn: (\n            lambda t, op: sa.cast(\n                sa.func.list_value(*map(t.translate, op.cols)),\n                t.get_sqla_type(op.output_dtype),\n            )\n        ),\n        ops.ArrayConcat: fixed_arity(sa.func.array_concat, 2),\n        ops.ArrayRepeat: fixed_arity(\n            lambda arg, times: sa.func.flatten(\n                sa.func.array(\n                    sa.select(arg).select_from(sa.func.range(times)).scalar_subquery()\n                )\n            ),\n            2,\n        ),\n        ops.ArrayLength: unary(sa.func.array_length),\n        ops.ArraySlice: _array_slice(\n            index_converter=_neg_idx_to_pos,\n            array_length=sa.func.array_length,\n            func=sa.func.list_slice,\n        ),\n        ops.ArrayIndex: _array_index(\n            index_converter=_neg_idx_to_pos, func=sa.func.list_extract\n        ),\n        ops.DayOfWeekName: unary(sa.func.dayname),\n        ops.Literal: _literal,\n        ops.Log2: unary(sa.func.log2),\n        ops.Ln: unary(sa.func.ln),\n        ops.Log: _log,\n        ops.IsNan: unary(sa.func.isnan),\n        # TODO: map operations, but DuckDB's maps are multimaps\n        ops.Modulus: fixed_arity(operator.mod, 2),\n        ops.Round: _round,\n        ops.StructField: (\n            lambda t, op: sa.func.struct_extract(\n                t.translate(op.arg),\n                sa.text(repr(op.field)),\n                type_=t.get_sqla_type(op.output_dtype),\n            )\n        ),\n        ops.TableColumn: _table_column,\n        ops.TimestampFromUNIX: _timestamp_from_unix,\n        ops.TimestampNow: fixed_arity(\n            # duckdb 0.6.0 changes now to be a tiemstamp with time zone force\n            # it back to the original for backwards compatibility\n            lambda *_: sa.cast(sa.func.now(), sa.TIMESTAMP),\n            0,\n        ),\n        ops.RegexExtract: fixed_arity(_regex_extract, 3),\n        ops.RegexReplace: fixed_arity(\n            lambda *args: sa.func.regexp_replace(*args, sa.text(\"'g'\")), 3\n        ),\n        ops.StringContains: fixed_arity(sa.func.contains, 2),\n        ops.ApproxMedian: reduction(\n            # without inline text, duckdb fails with\n            # RuntimeError: INTERNAL Error: Invalid PhysicalType for GetTypeIdSize\n            lambda arg: sa.func.approx_quantile(arg, sa.text(str(0.5)))\n        ),\n        ops.ApproxCountDistinct: reduction(sa.func.approx_count_distinct),\n        ops.Mode: reduction(sa.func.mode),\n        ops.Strftime: _strftime,\n        ops.Arbitrary: _arbitrary,\n        ops.GroupConcat: _string_agg,\n        ops.StructColumn: _struct_column,\n        ops.ArgMin: reduction(sa.func.min_by),\n        ops.ArgMax: reduction(sa.func.max_by),\n        ops.BitwiseXor: fixed_arity(sa.func.xor, 2),\n        ops.JSONGetItem: fixed_arity(_json_get_item, 2),\n        ops.RowID: lambda *_: sa.literal_column('rowid'),\n        ops.StringToTimestamp: fixed_arity(sa.func.strptime, 2),\n        ops.Quantile: reduction(sa.func.quantile_cont),\n        ops.MultiQuantile: reduction(sa.func.quantile_cont),\n        ops.TypeOf: unary(sa.func.typeof),\n        ops.IntervalAdd: fixed_arity(operator.add, 2),\n        ops.IntervalSubtract: fixed_arity(operator.sub, 2),\n        ops.Capitalize: alchemy.sqlalchemy_operation_registry[ops.Capitalize],\n        ops.ArrayStringJoin: fixed_arity(\n            lambda sep, arr: sa.func.array_aggr(arr, sa.text(\"'string_agg'\"), sep), 2\n        ),\n    }\n)\n\n\n_invalid_operations = {\n    # ibis.expr.operations.analytic\n    ops.CumulativeAll,\n    ops.CumulativeAny,\n    ops.CumulativeOp,\n    ops.NTile,\n    # ibis.expr.operations.strings\n    ops.Translate,\n    # ibis.expr.operations.maps\n    ops.MapGet,\n    ops.MapContains,\n    ops.MapKeys,\n    ops.MapValues,\n    ops.MapMerge,\n    ops.MapLength,\n    ops.Map,\n}\n\noperation_registry = {\n    k: v for k, v in operation_registry.items() if k not in _invalid_operations\n}\n", "path": "ibis/backends/duckdb/registry.py"}]}