Repository: tconbeer/sqlfmt

Issue:
Extra space added for DuckDB's *COLUMNS
**Describe the bug**
DuckDB supports a kind of special syntax with the function `COLUMNS`: https://duckdb.org/docs/sql/expressions/star.html#columns-unpacked-columns

**To Reproduce**
```python
import sqlfmt.api as sf

print(sf.format_string("""SELECT coalesce(*COLUMNS(['a', 'b', 'c'])) AS result""", sf.Mode()))
```
outputs:
```sql
select coalesce(* columns(['a', 'b', 'c'])) as result
```

**Expected behavior**
There should not be an extra space between * and COLUMNS


**Additional context**
What is the output of `sqlfmt --version`?
`sqlfmt, version 0.24.0`

Discussion:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbccc93..094fb1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 
 ## [Unreleased]
 
+### Formatting Changes and Bug Fixes
+
+- sqlfmt no longer adds a space between the `*` and `columns` in DuckDB `*columns` expressions ([#657](https://github.com/tconbeer/sqlfmt/issues/657) - thank you [@aersam](https://github.com/aersam)!)
+
 ## [0.24.0] - 2024-11-22
 
 ### Formatting Changes and Bug Fixes
diff --git a/src/sqlfmt/rules/__init__.py b/src/sqlfmt/rules/__init__.py
index e8212af..35aedff 100644
--- a/src/sqlfmt/rules/__init__.py
+++ b/src/sqlfmt/rules/__init__.py
@@ -21,6 +21,12 @@ from sqlfmt.token import TokenType
 
 MAIN = [
     *CORE,
+    Rule(
+        name="star_columns",
+        priority=409,  # star is 410
+        pattern=group(r"\*columns") + group(r"\(", r"$"),
+        action=partial(actions.add_node_to_buffer, token_type=TokenType.NAME),
+    ),
     Rule(
         name="statement_start",
         priority=1000,


Test patch:
diff --git a/tests/data/unformatted/135_star_columns.sql b/tests/data/unformatted/135_star_columns.sql
new file mode 100644
index 0000000..b10ad71
--- /dev/null
+++ b/tests/data/unformatted/135_star_columns.sql
@@ -0,0 +1,5 @@
+-- https://github.com/tconbeer/sqlfmt/issues/657
+SELECT coalesce(*COLUMNS(['a', 'b', 'c'])) AS result
+)))))__SQLFMT_OUTPUT__(((((
+-- https://github.com/tconbeer/sqlfmt/issues/657
+select coalesce(*columns(['a', 'b', 'c'])) as result
diff --git a/tests/functional_tests/test_general_formatting.py b/tests/functional_tests/test_general_formatting.py
index 1413d50..605b4eb 100644
--- a/tests/functional_tests/test_general_formatting.py
+++ b/tests/functional_tests/test_general_formatting.py
@@ -55,6 +55,7 @@ from tests.util import check_formatting, read_test_data
         "unformatted/132_spark_number_literals.sql",
         "unformatted/133_for_else.sql",
         "unformatted/134_databricks_type_hints.sql",
+        "unformatted/135_star_columns.sql",
         "unformatted/200_base_model.sql",
         "unformatted/201_basic_snapshot.sql",
         "unformatted/202_unpivot_macro.sql",
diff --git a/tests/unit_tests/test_rule.py b/tests/unit_tests/test_rule.py
index 30c07d3..4de4947 100644
--- a/tests/unit_tests/test_rule.py
+++ b/tests/unit_tests/test_rule.py
@@ -238,6 +238,7 @@ def get_rule(ruleset: List[Rule], rule_name: str) -> Rule:
         (MAIN, "other_identifiers", "%s"),
         (MAIN, "other_identifiers", "%(name)s"),
         (MAIN, "other_identifiers", "%(anything! else!)s"),
+        (MAIN, "star_columns", "*columns"),
         (MAIN, "newline", "\n"),
         (JINJA, "jinja_comment", "{# my comment #}"),
         (JINJA, "jinja_comment", "{#-my comment -#}"),