codeant-ai-for-open-source[bot] commented on code in PR #43000:
URL: https://github.com/apache/superset/pull/43000#discussion_r3748775033


##########
superset/db_engine_specs/clickhouse.py:
##########
@@ -419,6 +424,125 @@ def get_datatype(cls, type_code: str) -> str:
         # keep it lowercase, as ClickHouse types aren't typical SHOUTCASE ANSI 
SQL
         return type_code
 
+    @classmethod
+    def df_to_sql(
+        cls,
+        database: Database,
+        table: Table,
+        df: Any,
+        to_sql_kwargs: dict[str, Any],
+    ) -> None:
+        """Upload a DataFrame to ClickHouse.
+
+        ClickHouse requires every table to declare a table engine, which the
+        `CREATE TABLE` that pandas' ``to_sql`` emits does not — so instead of
+        letting pandas create the table we create it ourselves with a MergeTree
+        engine and then append the rows.
+
+        The engine can be tuned per database through its ``extra`` JSON::
+
+            {"clickhouse_file_upload": {
+                "order_by": ["col1", "col2"],
+                "partition_by": "toYYYYMM(col1)",
+                "primary_key": "col1",
+                "settings": {"index_granularity": 8192}
+            }}
+
+        ``order_by`` defaults to ``tuple()`` (no sort key) so any upload works
+        without configuration.
+        """
+        # pylint: disable=import-outside-toplevel, import-error
+        import pandas as pd
+        from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree
+        from sqlalchemy import (
+            Column,
+            inspect,
+            MetaData,
+            Table as SqlaTable,
+            text,
+            types as sqltypes,
+        )
+
+        if_exists = to_sql_kwargs.get("if_exists", "fail")
+
+        if to_sql_kwargs.get("index"):
+            # Fold the index into columns so the table we create matches what
+            # gets inserted; we always append with index=False below.
+            df = df.reset_index()
+
+        config = (database.get_extra() or {}).get("clickhouse_file_upload", {})
+        order_by = config.get("order_by")
+        if order_by:
+            key_columns = set(
+                order_by if isinstance(order_by, (list, tuple)) else [order_by]
+            )
+            engine_kwargs: dict[str, Any] = {"order_by": order_by}
+        else:
+            # MergeTree still requires an ORDER BY; an empty tuple means 
"none".
+            key_columns = set()
+            engine_kwargs = {"order_by": text("tuple()")}
+        for key in ("partition_by", "primary_key", "settings"):
+            if config.get(key):
+                engine_kwargs[key] = config[key]
+
+        def _column_type(dtype: Any) -> sqltypes.TypeEngine:
+            if pd.api.types.is_bool_dtype(dtype):
+                return sqltypes.Boolean()
+            if pd.api.types.is_integer_dtype(dtype):
+                return sqltypes.BigInteger()
+            if pd.api.types.is_float_dtype(dtype):
+                return sqltypes.Float()
+            if pd.api.types.is_datetime64_any_dtype(dtype):
+                return sqltypes.DateTime()
+            return sqltypes.String()

Review Comment:
   **Suggestion:** The fallback maps every unsupported pandas dtype to 
`String`, including object columns containing dates, decimals, UUIDs, bytes, or 
other native values. This silently creates an incompatible or semantically 
incorrect ClickHouse schema, so date and numeric operations can no longer work 
after upload and some native values may fail during insertion. Type inference 
should preserve the actual values or explicitly support the relevant 
pandas/object dtypes. [type error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Parquet uploads can lose decimal or UUID semantics.
   - ⚠️ Date and numeric queries require string conversion.
   - ⚠️ Native values may fail during ClickHouse insertion.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=350ed4a7703c49a6bd58d1631d7f1eb4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=350ed4a7703c49a6bd58d1631d7f1eb4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/db_engine_specs/clickhouse.py
   **Line:** 488:497
   **Comment:**
        *Type Error: The fallback maps every unsupported pandas dtype to 
`String`, including object columns containing dates, decimals, UUIDs, bytes, or 
other native values. This silently creates an incompatible or semantically 
incorrect ClickHouse schema, so date and numeric operations can no longer work 
after upload and some native values may fail during insertion. Type inference 
should preserve the actual values or explicitly support the relevant 
pandas/object dtypes.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43000&comment_hash=f505ac22b6d22b02e2370dd5ca32b55d426ad1869ea9f36b9023eef6e6e13070&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43000&comment_hash=f505ac22b6d22b02e2370dd5ca32b55d426ad1869ea9f36b9023eef6e6e13070&reaction=dislike'>👎</a>



##########
superset/db_engine_specs/clickhouse.py:
##########
@@ -419,6 +424,125 @@ def get_datatype(cls, type_code: str) -> str:
         # keep it lowercase, as ClickHouse types aren't typical SHOUTCASE ANSI 
SQL
         return type_code
 
+    @classmethod
+    def df_to_sql(
+        cls,
+        database: Database,
+        table: Table,
+        df: Any,
+        to_sql_kwargs: dict[str, Any],
+    ) -> None:
+        """Upload a DataFrame to ClickHouse.
+
+        ClickHouse requires every table to declare a table engine, which the
+        `CREATE TABLE` that pandas' ``to_sql`` emits does not — so instead of
+        letting pandas create the table we create it ourselves with a MergeTree
+        engine and then append the rows.
+
+        The engine can be tuned per database through its ``extra`` JSON::
+
+            {"clickhouse_file_upload": {
+                "order_by": ["col1", "col2"],
+                "partition_by": "toYYYYMM(col1)",
+                "primary_key": "col1",
+                "settings": {"index_granularity": 8192}
+            }}
+
+        ``order_by`` defaults to ``tuple()`` (no sort key) so any upload works
+        without configuration.
+        """
+        # pylint: disable=import-outside-toplevel, import-error
+        import pandas as pd
+        from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree
+        from sqlalchemy import (
+            Column,
+            inspect,
+            MetaData,
+            Table as SqlaTable,
+            text,
+            types as sqltypes,
+        )
+
+        if_exists = to_sql_kwargs.get("if_exists", "fail")
+
+        if to_sql_kwargs.get("index"):
+            # Fold the index into columns so the table we create matches what
+            # gets inserted; we always append with index=False below.
+            df = df.reset_index()
+

Review Comment:
   **Suggestion:** When `dataframe_index` is enabled, `reset_index()` creates 
the index column using the DataFrame's existing index name or pandas' default 
name, but removing `index_label` before `to_sql` discards the uploader's 
requested column name. An upload with `index_label='foo'` therefore creates and 
inserts a column such as `index` instead of `foo`, breaking the upload 
contract. Preserve the requested label when folding the index into the 
DataFrame. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Indexed uploads expose the wrong index column name.
   - ⚠️ Downstream queries expecting configured labels fail.
   - ⚠️ Affected CSV, Excel, and columnar upload workflows.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=06253dda250c446281c6dc9b44bf0aeb&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=06253dda250c446281c6dc9b44bf0aeb&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/db_engine_specs/clickhouse.py
   **Line:** 468:472
   **Comment:**
        *Api Mismatch: When `dataframe_index` is enabled, `reset_index()` 
creates the index column using the DataFrame's existing index name or pandas' 
default name, but removing `index_label` before `to_sql` discards the 
uploader's requested column name. An upload with `index_label='foo'` therefore 
creates and inserts a column such as `index` instead of `foo`, breaking the 
upload contract. Preserve the requested label when folding the index into the 
DataFrame.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43000&comment_hash=3df971b9595c269580129935e937fb8fa58e2f1404fe693dd9e276197670c731&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43000&comment_hash=3df971b9595c269580129935e937fb8fa58e2f1404fe693dd9e276197670c731&reaction=dislike'>👎</a>



##########
superset/db_engine_specs/clickhouse.py:
##########
@@ -419,6 +424,125 @@ def get_datatype(cls, type_code: str) -> str:
         # keep it lowercase, as ClickHouse types aren't typical SHOUTCASE ANSI 
SQL
         return type_code
 
+    @classmethod
+    def df_to_sql(
+        cls,
+        database: Database,
+        table: Table,
+        df: Any,
+        to_sql_kwargs: dict[str, Any],
+    ) -> None:
+        """Upload a DataFrame to ClickHouse.
+
+        ClickHouse requires every table to declare a table engine, which the
+        `CREATE TABLE` that pandas' ``to_sql`` emits does not — so instead of
+        letting pandas create the table we create it ourselves with a MergeTree
+        engine and then append the rows.
+
+        The engine can be tuned per database through its ``extra`` JSON::
+
+            {"clickhouse_file_upload": {
+                "order_by": ["col1", "col2"],
+                "partition_by": "toYYYYMM(col1)",
+                "primary_key": "col1",
+                "settings": {"index_granularity": 8192}
+            }}
+
+        ``order_by`` defaults to ``tuple()`` (no sort key) so any upload works
+        without configuration.
+        """
+        # pylint: disable=import-outside-toplevel, import-error
+        import pandas as pd
+        from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree
+        from sqlalchemy import (
+            Column,
+            inspect,
+            MetaData,
+            Table as SqlaTable,
+            text,
+            types as sqltypes,
+        )
+
+        if_exists = to_sql_kwargs.get("if_exists", "fail")
+
+        if to_sql_kwargs.get("index"):
+            # Fold the index into columns so the table we create matches what
+            # gets inserted; we always append with index=False below.
+            df = df.reset_index()
+
+        config = (database.get_extra() or {}).get("clickhouse_file_upload", {})
+        order_by = config.get("order_by")
+        if order_by:
+            key_columns = set(
+                order_by if isinstance(order_by, (list, tuple)) else [order_by]
+            )
+            engine_kwargs: dict[str, Any] = {"order_by": order_by}
+        else:
+            # MergeTree still requires an ORDER BY; an empty tuple means 
"none".
+            key_columns = set()
+            engine_kwargs = {"order_by": text("tuple()")}
+        for key in ("partition_by", "primary_key", "settings"):
+            if config.get(key):
+                engine_kwargs[key] = config[key]
+
+        def _column_type(dtype: Any) -> sqltypes.TypeEngine:
+            if pd.api.types.is_bool_dtype(dtype):
+                return sqltypes.Boolean()
+            if pd.api.types.is_integer_dtype(dtype):
+                return sqltypes.BigInteger()
+            if pd.api.types.is_float_dtype(dtype):
+                return sqltypes.Float()
+            if pd.api.types.is_datetime64_any_dtype(dtype):
+                return sqltypes.DateTime()
+            return sqltypes.String()
+
+        with cls.get_engine(
+            database, catalog=table.catalog, schema=table.schema
+        ) as engine:
+            has_table = inspect(engine).has_table(table.table, 
schema=table.schema)
+            if has_table and if_exists == "fail":
+                # Raise ValueError so the uploader surfaces its friendly
+                # "table already exists" message (see UploadCommand).
+                raise ValueError(f"Table {table.table} already exists.")
+            if has_table and if_exists == "replace":
+                SqlaTable(table.table, MetaData(), schema=table.schema).drop(
+                    engine, checkfirst=True
+                )
+                has_table = False

Review Comment:
   **Suggestion:** Table existence checking, replacement, creation, and 
insertion are separate non-atomic operations. Concurrent uploads can both 
observe a missing table and race during `CREATE TABLE`, while any failure after 
the `replace` drop leaves the original table absent. Because ClickHouse DDL is 
not rolled back by the uploader's metadata transaction, this can cause failed 
uploads or destructive data loss; use an atomic/staged replacement strategy and 
coordinate concurrent creates. [race condition]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Concurrent uploads can fail nondeterministically.
   - ❌ Replace failures can leave tables deleted.
   - ⚠️ Upload requests can lose previously available data.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=067540f2f2a74e12a2b198efcba51e42&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=067540f2f2a74e12a2b198efcba51e42&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/db_engine_specs/clickhouse.py
   **Line:** 499:511
   **Comment:**
        *Race Condition: Table existence checking, replacement, creation, and 
insertion are separate non-atomic operations. Concurrent uploads can both 
observe a missing table and race during `CREATE TABLE`, while any failure after 
the `replace` drop leaves the original table absent. Because ClickHouse DDL is 
not rolled back by the uploader's metadata transaction, this can cause failed 
uploads or destructive data loss; use an atomic/staged replacement strategy and 
coordinate concurrent creates.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43000&comment_hash=c5cdd72853c20b354ec20f053ab3c7f7ba5eb6406876302e6ab1e7fd8bd00889&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43000&comment_hash=c5cdd72853c20b354ec20f053ab3c7f7ba5eb6406876302e6ab1e7fd8bd00889&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to