Repository: reata/sqllineage

Issue:
Misidentify Binary Operator * As Wildcard
```sql
insert into
    public.tgt_tbl1
(
    id
)
select
    nvl(src_tbl1.id, 0) * 1 as id
from
    public.src_tbl1 as src_tbl1
;
```

**To Reproduce**
*Note here we refer to SQL provided in prior step as stored in a file named `test.sql`*

```python
with open(sql_file, 'r') as f:
    sql = f.read()

lr = LineageRunner(sql, dialect='redshift')

lr.print_column_lineage()
```
```
public.tgt_tbl1.id <- public.src_tbl1.*
public.tgt_tbl1.id <- public.src_tbl1.id
```

**Expected behavior**
```
public.tgt_tbl1.id <- public.src_tbl1.id
```

**Python version (available via `python --version`)**
- 3.11.5
 
**SQLLineage version (available via `sqllineage --version`):**
 - 1.4.8
 
**The lineage of the two variants of SQL is also incorrect.**
```sql
insert into
    public.tgt_tbl1
(
    id
)
select
    coalesce(src_tbl1.id, 0) * 1 as id
from
    public.src_tbl1 as src_tbl1
;
```
```sql
insert into
    public.tgt_tbl1
(
    id
)
select
    case when src_tbl1.id is not null then src_tbl1.id else 0 end * 1 as id
from
    public.src_tbl1 as src_tbl1
;
```


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/reata/sqllineage/pull/510?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`c0cf6aa`)](https://app.codecov.io/gh/reata/sqllineage/commit/c0cf6aa6fc5976a61ed534040b44b21cafb41094?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 99.41% compared to head [(`dafae1a`)](https://app.codecov.io/gh/reata/sqllineage/pull/510?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 99.41%.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #510      +/-   ##
==========================================
- Coverage   99.41%   99.41%   -0.01%     
==========================================
  Files          40       40              
  Lines        2052     2051       -1     
==========================================
- Hits         2040     2039       -1     
  Misses         12       12              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/reata/sqllineage/pull/510?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/sqllineage/core/parser/sqlfluff/models.py b/sqllineage/core/parser/sqlfluff/models.py
index 6c60309..921600e 100644
--- a/sqllineage/core/parser/sqlfluff/models.py
+++ b/sqllineage/core/parser/sqlfluff/models.py
@@ -209,13 +209,9 @@ class SqlFluffColumn(Column):
     def _get_column_from_parenthesis(
         sub_segment: BaseSegment,
     ) -> List[ColumnQualifierTuple]:
-        """
-        :param sub_segment: segment to be processed
-        :return: list of columns and alias from the segment
-        """
-        col, _ = SqlFluffColumn._get_column_and_alias(sub_segment)
-        if col:
-            return col
+        # windows function has an extra layer, get rid of it so that it can be handled as regular functions
+        if window_specification := sub_segment.get_child("window_specification"):
+            sub_segment = window_specification
         col, _ = SqlFluffColumn._get_column_and_alias(sub_segment, False)
         return col if col else []
 
@@ -223,6 +219,10 @@ class SqlFluffColumn(Column):
     def _get_column_and_alias(
         segment: BaseSegment, check_bracketed: bool = True
     ) -> Tuple[List[ColumnQualifierTuple], Optional[str]]:
+        """
+        check_bracketed is True for top-level column definition, like (col1 + col2) as col3
+        set to False for bracket in function call, like coalesce(col1, col2) as col3
+        """
         alias = None
         columns = []
         sub_segments = list_child_segments(segment, check_bracketed)
diff --git a/sqllineage/core/parser/sqlfluff/utils.py b/sqllineage/core/parser/sqlfluff/utils.py
index 406b17d..c9b732c 100644
--- a/sqllineage/core/parser/sqlfluff/utils.py
+++ b/sqllineage/core/parser/sqlfluff/utils.py
@@ -48,7 +48,7 @@ def is_subquery(segment: BaseSegment) -> bool:
 
 def is_wildcard(segment: BaseSegment) -> bool:
     return segment.type == "wildcard_expression" or (
-        segment.type == "symbol" and segment.raw == "*"
+        segment.type == "symbol" and segment.raw == "*" and segment.get_type() == "star"
     )
 
 


Test patch:
diff --git a/tests/sql/column/test_column_select_expression.py b/tests/sql/column/test_column_select_expression.py
index f54c998..020e445 100644
--- a/tests/sql/column/test_column_select_expression.py
+++ b/tests/sql/column/test_column_select_expression.py
@@ -37,6 +37,21 @@ FROM tab2"""
     )
 
 
+def test_select_column_multiply_expression():
+    sql = """INSERT INTO tab1
+SELECT col1 * 1 AS col1
+FROM tab2"""
+    assert_column_lineage_equal(
+        sql,
+        [
+            (
+                ColumnQualifierTuple("col1", "tab2"),
+                ColumnQualifierTuple("col1", "tab1"),
+            ),
+        ],
+    )
+
+
 def test_select_column_using_expression_in_parenthesis():
     sql = """INSERT INTO tab1
 SELECT (col1 + col2)