Repository: reata/sqllineage

Issue:
Target table is empty for BigQuery INSERT statement
**Describe the bug**
For BigQuery INSERT statement, 'INTO' is an optional keyword. When tried a parse a BigQuery INSERT statement without INTO keyword, the target table is empty.

https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#insert_statement

**SQL**
```
INSERT `project.dataset.table_2`
SELECT * FROM `project.dataset.table_1`;
```

**To Reproduce**
*Note here we refer to SQL provided in prior step as stored in a file named `test.sql`*

```shell
sqllineage -f test.sql --dialect=bigquery
```
Output
```
Statements(#): 1
Source Tables:
    project.dataset.table_1
Target Tables:
    
```

**Expected behavior**
Expected output
```
Statements(#): 1
Source Tables:
    project.dataset.table_1
Target Tables:
    project.dataset.table_2
```

**Python version (available via `python --version`)**
 - 3.12.3

**SQLLineage version (available via `sqllineage --version`):**
 - 1.5.4

**Additional context**
Add any other context about the problem here.



Discussion:
Thanks for reporting. This is bigquery specific and we can add support for it.



Patch:
diff --git a/sqllineage/core/parser/sqlfluff/extractors/create_insert.py b/sqllineage/core/parser/sqlfluff/extractors/create_insert.py
index 1873ab7e..1ca6aa20 100644
--- a/sqllineage/core/parser/sqlfluff/extractors/create_insert.py
+++ b/sqllineage/core/parser/sqlfluff/extractors/create_insert.py
@@ -87,8 +87,9 @@ def extract(
 
             elif segment.type == "keyword":
                 if segment.raw_upper in [
+                    "INSERT",  # bigquery supports INTO as optional keyword so INSERT is the indicator
                     "INTO",
-                    "OVERWRITE",
+                    "OVERWRITE",  # sparksql supports INSERT OVERWRITE with TABLE as optional keyword
                     "TABLE",
                     "VIEW",
                     "DIRECTORY",
diff --git a/sqllineage/core/parser/sqlparse/handlers/target.py b/sqllineage/core/parser/sqlparse/handlers/target.py
index e8597a1c..e9039fca 100644
--- a/sqllineage/core/parser/sqlparse/handlers/target.py
+++ b/sqllineage/core/parser/sqlparse/handlers/target.py
@@ -12,6 +12,7 @@ class TargetHandler(NextTokenBaseHandler):
     """Target Table Handler."""
 
     TARGET_TABLE_TOKENS = (
+        "INSERT",
         "INTO",
         "OVERWRITE",
         "TABLE",


Test patch:
diff --git a/tests/sql/table/test_insert_dialect_specific.py b/tests/sql/table/test_insert_dialect_specific.py
index cebb01e7..f3897028 100644
--- a/tests/sql/table/test_insert_dialect_specific.py
+++ b/tests/sql/table/test_insert_dialect_specific.py
@@ -59,6 +59,23 @@ def test_insert_into_with_keyword_table(dialect: str):
     )
 
 
+@pytest.mark.parametrize("dialect", ["bigquery", "mariadb", "mysql", "tsql"])
+def test_insert_without_into_keyword(dialect: str):
+    """
+    INTO is optional in INSERT statement of the following dialects:
+        bigquery: https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#insert_statement
+        mariadb: https://mariadb.com/kb/en/insert/
+        mysql: https://dev.mysql.com/doc/refman/8.4/en/insert.html
+        tsql: https://learn.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-ver16
+    """
+    assert_table_lineage_equal(
+        "INSERT tab1 SELECT * FROM tab2",
+        {"tab2"},
+        {"tab1"},
+        dialect=dialect,
+    )
+
+
 @pytest.mark.parametrize("dialect", ["databricks", "hive", "sparksql"])
 def test_insert_into_partitions(dialect: str):
     assert_table_lineage_equal(