This is an automated email from the ASF dual-hosted git repository.

pankajastro pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 23c8d820870 Add GCS support to the DataFusion object storage layer 
(#73370)
23c8d820870 is described below

commit 23c8d820870074f5756321bb812057556b3cb90d
Author: Pankaj Singh <[email protected]>
AuthorDate: Sun Sep 20 14:27:44 2026 +0530

    Add GCS support to the DataFusion object storage layer (#73370)
    
    DataFusion credential resolution only handled AWS connections, so
    registering a GCS-backed DataSourceConfig raised Unknown connection
    type instead of working, even though DataFusion's storage layer
    supports it. Add a StorageType.GCS, a GCSObjectStorageProvider using
    DataFusion's GoogleCloud, and a google_cloud_platform credential
    branch resolving the connection's key_path or keyfile_dict (falling
    back to Application Default Credentials when neither is set).
    
    DataFusion's GoogleCloud binding only accepts a file path, not inline
    JSON, so a keyfile_dict connection is materialized to a temp file for
    the object store's construction and deleted immediately after, since
    the underlying credentials are parsed once at construction time and
    never re-read.
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
---
 .../ai/docs/operators/llm_schema_compare.rst       |  2 +-
 providers/common/ai/docs/operators/llm_sql.rst     |  2 +-
 providers/common/ai/docs/toolsets.rst              |  2 +-
 providers/common/sql/docs/operators.rst            | 23 +++++-
 .../sql/src/airflow/providers/common/sql/config.py |  3 +
 .../providers/common/sql/datafusion/engine.py      | 21 ++++++
 .../sql/datafusion/object_storage_provider.py      | 59 ++++++++++++++-
 .../common/sql/example_dags/example_analytics.py   | 13 ++++
 .../unit/common/sql/datafusion/test_engine.py      | 86 +++++++++++++++++++++-
 .../sql/datafusion/test_object_storage_provider.py | 84 +++++++++++++++++++++
 .../sql/tests/unit/common/sql/test_config.py       |  1 +
 11 files changed, 289 insertions(+), 7 deletions(-)

diff --git a/providers/common/ai/docs/operators/llm_schema_compare.rst 
b/providers/common/ai/docs/operators/llm_schema_compare.rst
index 2efed391b01..e7145deea86 100644
--- a/providers/common/ai/docs/operators/llm_schema_compare.rst
+++ b/providers/common/ai/docs/operators/llm_schema_compare.rst
@@ -62,7 +62,7 @@ With Object Storage or a Database Table
 
 Use ``data_sources`` with
 :class:`~airflow.providers.common.sql.config.DataSourceConfig` to include
-object-storage sources (S3 Parquet, CSV, Iceberg, etc.) in the comparison.
+object-storage sources (S3 or GCS Parquet, CSV, Iceberg, etc.) in the 
comparison.
 These can be freely combined with ``db_conn_ids``. Whether an entry is
 introspected via ``DbApiHook`` or DataFusion depends on what its ``conn_id``
 resolves to, not on its ``uri``/``format`` fields — a ``DataSourceConfig``
diff --git a/providers/common/ai/docs/operators/llm_sql.rst 
b/providers/common/ai/docs/operators/llm_sql.rst
index ba1d05f36de..07fe793a09f 100644
--- a/providers/common/ai/docs/operators/llm_sql.rst
+++ b/providers/common/ai/docs/operators/llm_sql.rst
@@ -60,7 +60,7 @@ With Object Storage
 -------------------
 
 Use ``datasource_config`` to generate queries for data stored in object storage
-(e.g., S3, local filesystem) via `DataFusion 
<https://datafusion.apache.org/>`_.
+(e.g., S3, GCS, local filesystem) via `DataFusion 
<https://datafusion.apache.org/>`_.
 The operator uses 
:class:`~airflow.providers.common.sql.config.DataSourceConfig`
 to register the object storage source as a table so the LLM can include it in
 the schema context.
diff --git a/providers/common/ai/docs/toolsets.rst 
b/providers/common/ai/docs/toolsets.rst
index e89bed02b69..2fa9a5b7151 100644
--- a/providers/common/ai/docs/toolsets.rst
+++ b/providers/common/ai/docs/toolsets.rst
@@ -284,7 +284,7 @@ later request.
 Curated toolset wrapping
 :class:`~airflow.providers.common.sql.datafusion.engine.DataFusionEngine`
 with three tools — ``list_tables``, ``get_schema``, and ``query`` — for
-querying files on object stores (S3, local filesystem, Iceberg) via Apache 
DataFusion.
+querying files on object stores (S3, GCS, local filesystem, Iceberg) via 
Apache DataFusion.
 
 .. list-table::
    :header-rows: 1
diff --git a/providers/common/sql/docs/operators.rst 
b/providers/common/sql/docs/operators.rst
index 265832301c0..85c501e3679 100644
--- a/providers/common/sql/docs/operators.rst
+++ b/providers/common/sql/docs/operators.rst
@@ -296,10 +296,11 @@ The Analytics Operator is ideal for performing efficient, 
high-performance analy
 Supported Storage Systems
 -------------------------
 - S3
+- GCS
 - Local File System
 
 .. note::
-   GCS, Azure, HTTP, Delta, Iceberg are not yet supported but will be added in 
the future.
+   Azure, HTTP, Delta are not yet supported but will be added in the future.
 
 
 
@@ -341,6 +342,26 @@ S3 Storage
     :start-after: [START howto_analytics_operator_with_s3]
     :end-before: [END howto_analytics_operator_with_s3]
 
+GCS Storage
+-----------
+Use a ``conn_id`` pointing to a ``google_cloud_platform`` connection. 
Credentials are
+resolved in this order:
+
+1. ``key_path`` -- a service account JSON file on disk
+2. ``keyfile_dict`` -- the service account JSON contents, inline
+3. the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable
+4. Application Default Credentials -- the local ``gcloud`` CLI credentials 
file, or
+   the GCE/GKE metadata server
+
+``key_path`` and ``keyfile_dict`` are mutually exclusive. ``key_secret_name``,
+``credential_config_file``, and ``impersonation_chain`` are not supported.
+
+.. exampleinclude:: 
/../../sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_analytics_operator_with_gcs]
+    :end-before: [END howto_analytics_operator_with_gcs]
+
 Local File System Storage
 -------------------------
 .. exampleinclude:: 
/../../sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
diff --git a/providers/common/sql/src/airflow/providers/common/sql/config.py 
b/providers/common/sql/src/airflow/providers/common/sql/config.py
index 975bc58c9d4..a1a856a17f3 100644
--- a/providers/common/sql/src/airflow/providers/common/sql/config.py
+++ b/providers/common/sql/src/airflow/providers/common/sql/config.py
@@ -47,6 +47,7 @@ class StorageType(str, Enum):
     """Storage types for Data Fusion."""
 
     S3 = "s3"
+    GCS = "gcs"
     LOCAL = "local"
 
 
@@ -114,6 +115,8 @@ class DataSourceConfig:
         """Extract storage type."""
         if self.uri.startswith("s3://"):
             return StorageType.S3
+        if self.uri.startswith("gs://"):
+            return StorageType.GCS
         if self.uri.startswith("file://"):
             return StorageType.LOCAL
         raise ValueError(f"Unsupported storage type for URI: {self.uri}")
diff --git 
a/providers/common/sql/src/airflow/providers/common/sql/datafusion/engine.py 
b/providers/common/sql/src/airflow/providers/common/sql/datafusion/engine.py
index 21e0b72390b..207ab3e0f77 100644
--- a/providers/common/sql/src/airflow/providers/common/sql/datafusion/engine.py
+++ b/providers/common/sql/src/airflow/providers/common/sql/datafusion/engine.py
@@ -16,6 +16,7 @@
 # under the License.
 from __future__ import annotations
 
+import os
 from typing import TYPE_CHECKING, Any
 
 from datafusion import SessionContext
@@ -169,6 +170,26 @@ class DataFusionEngine(LoggingMixin):
                 credentials = self._remove_none_values(credentials)
                 extra_config = _fetch_extra_configs(["region", "endpoint"])
 
+            case "google_cloud_platform":
+                extra_dejson = conn.extra_dejson
+                for unsupported_field in ("key_secret_name", 
"credential_config_file", "impersonation_chain"):
+                    if extra_dejson.get(unsupported_field):
+                        raise ValueError(
+                            f"Connection field {unsupported_field!r} is not 
supported for DataFusion "
+                            "GCS access; only key_path, keyfile_dict, 
GOOGLE_APPLICATION_CREDENTIALS, or "
+                            "ambient credentials (gcloud ADC file / metadata 
server) are used."
+                        )
+                key_path = extra_dejson.get("key_path") or None
+                keyfile_dict = extra_dejson.get("keyfile_dict") or None
+                if key_path and keyfile_dict:
+                    raise ValueError(
+                        "The `keyfile_dict` and `key_path` fields are mutually 
exclusive. "
+                        "Please provide only one value."
+                    )
+                if not key_path and not keyfile_dict:
+                    key_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
+                credentials = self._remove_none_values({"key_path": key_path, 
"keyfile_dict": keyfile_dict})
+
             case _:
                 raise ValueError(f"Unknown connection type {conn.conn_type}")
         return credentials, extra_config
diff --git 
a/providers/common/sql/src/airflow/providers/common/sql/datafusion/object_storage_provider.py
 
b/providers/common/sql/src/airflow/providers/common/sql/datafusion/object_storage_provider.py
index 1ce917a1bb2..608a6959940 100644
--- 
a/providers/common/sql/src/airflow/providers/common/sql/datafusion/object_storage_provider.py
+++ 
b/providers/common/sql/src/airflow/providers/common/sql/datafusion/object_storage_provider.py
@@ -16,7 +16,11 @@
 # under the License.
 from __future__ import annotations
 
-from datafusion.object_store import AmazonS3, LocalFileSystem
+import json
+import tempfile
+from pathlib import Path
+
+from datafusion.object_store import AmazonS3, GoogleCloud, LocalFileSystem
 
 from airflow.providers.common.sql.config import ConnectionConfig, StorageType
 from airflow.providers.common.sql.datafusion.base import ObjectStorageProvider
@@ -53,6 +57,56 @@ class S3ObjectStorageProvider(ObjectStorageProvider):
         return "s3://"
 
 
+class GCSObjectStorageProvider(ObjectStorageProvider):
+    """GCS Object Storage Provider using DataFusion's GoogleCloud."""
+
+    @property
+    def get_storage_type(self) -> StorageType:
+        """Return the storage type."""
+        return StorageType.GCS
+
+    def create_object_store(self, path: str, connection_config: 
ConnectionConfig | None = None):
+        """Create a GCS object store using DataFusion's GoogleCloud."""
+        if connection_config is None:
+            raise ValueError(f"connection_config must be provided for 
{self.get_storage_type}")
+
+        credentials = connection_config.credentials
+        key_path = credentials.get("key_path")
+        keyfile_dict = credentials.get("keyfile_dict")
+        temp_key_path: str | None = None
+
+        try:
+            bucket = self.get_bucket(path)
+
+            if not key_path and keyfile_dict:
+                # GoogleCloud only accepts a file path, not inline JSON; safe 
to delete once
+                # constructed since the credentials are read only at 
construction time.
+                key_content = keyfile_dict if isinstance(keyfile_dict, str) 
else json.dumps(keyfile_dict)
+                with tempfile.NamedTemporaryFile(mode="w", suffix=".json", 
delete=False) as key_file:
+                    key_file.write(key_content)
+                temp_key_path = key_path = key_file.name
+
+            if key_path is not None and not Path(key_path).is_file():
+                raise FileNotFoundError(f"Service account key file not found: 
{key_path}")
+
+            gcs_store = GoogleCloud(bucket_name=bucket, 
service_account_path=key_path)
+            self.log.info("Created GCS object store for bucket %s", bucket)
+
+            return gcs_store
+
+        except BaseException as e:
+            # A bad key file panics as pyo3_runtime.PanicException, not a 
plain Exception.
+            raise ObjectStoreCreationException(f"Failed to create GCS object 
store: {e}")
+
+        finally:
+            if temp_key_path is not None:
+                Path(temp_key_path).unlink(missing_ok=True)
+
+    def get_scheme(self) -> str:
+        """Return the scheme for GCS."""
+        return "gs://"
+
+
 class LocalObjectStorageProvider(ObjectStorageProvider):
     """Local Object Storage Provider using DataFusion's LocalFileSystem."""
 
@@ -72,9 +126,10 @@ class LocalObjectStorageProvider(ObjectStorageProvider):
 
 def get_object_storage_provider(storage_type: StorageType) -> 
ObjectStorageProvider:
     """Get an object storage provider based on the storage type."""
-    # TODO: Add support for GCS, Azure, HTTP: 
https://datafusion.apache.org/python/autoapi/datafusion/object_store/index.html
+    # TODO: Add support for Azure, HTTP: 
https://datafusion.apache.org/python/autoapi/datafusion/object_store/index.html
     providers: dict[StorageType, type] = {
         StorageType.S3: S3ObjectStorageProvider,
+        StorageType.GCS: GCSObjectStorageProvider,
         StorageType.LOCAL: LocalObjectStorageProvider,
     }
 
diff --git 
a/providers/common/sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
 
b/providers/common/sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
index 07a23d41e81..0f80fc6100e 100644
--- 
a/providers/common/sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
+++ 
b/providers/common/sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
@@ -30,6 +30,10 @@ datasource_config_local = DataSourceConfig(
     conn_id="", table_name="users_data", uri="file:///path/to/", 
format="parquet"
 )
 
+datasource_config_gcs = DataSourceConfig(
+    conn_id="google_cloud_default", table_name="users_data", 
uri="gs://bucket/path/", format="parquet"
+)
+
 datasource_config_iceberg = DataSourceConfig(
     conn_id="iceberg_default",
     table_name="users_data",
@@ -72,6 +76,15 @@ with DAG(
 
     # [END howto_analytics_operator_with_s3]
 
+    # [START howto_analytics_operator_with_gcs]
+    analytics_with_gcs = AnalyticsOperator(
+        task_id="analytics_with_gcs",
+        datasource_configs=[datasource_config_gcs],
+        queries=["SELECT * FROM users_data", "SELECT count(*) FROM 
users_data"],
+    )
+    analytics_with_s3 >> analytics_with_gcs
+    # [END howto_analytics_operator_with_gcs]
+
     # [START howto_analytics_operator_with_local]
     analytics_with_local = AnalyticsOperator(
         task_id="analytics_with_local",
diff --git 
a/providers/common/sql/tests/unit/common/sql/datafusion/test_engine.py 
b/providers/common/sql/tests/unit/common/sql/datafusion/test_engine.py
index d48c705046f..21287cfa075 100644
--- a/providers/common/sql/tests/unit/common/sql/datafusion/test_engine.py
+++ b/providers/common/sql/tests/unit/common/sql/datafusion/test_engine.py
@@ -73,7 +73,12 @@ class TestDataFusionEngine:
 
     @pytest.mark.parametrize(
         ("storage_type", "format", "scheme"),
-        [("s3", "parquet", "s3"), ("s3", "csv", "s3"), ("s3", "avro", "s3")],
+        [
+            ("s3", "parquet", "s3"),
+            ("s3", "csv", "s3"),
+            ("s3", "avro", "s3"),
+            ("gcs", "parquet", "gs"),
+        ],
     )
     
@patch("airflow.providers.common.sql.datafusion.engine.get_object_storage_provider",
 autospec=True)
     @patch.object(DataFusionEngine, "_get_connection_config")
@@ -281,6 +286,85 @@ class TestDataFusionEngine:
         assert result.credentials == expected.credentials
         assert result.extra_config == expected.extra_config
 
+    def test_get_credentials_gcs_with_key_path(self):
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {"key_path": "/path/to/key.json"}
+        engine = DataFusionEngine()
+
+        credentials, extra_config = engine._get_credentials(mock_conn)
+
+        assert credentials == {"key_path": "/path/to/key.json"}
+        assert extra_config == {}
+
+    def test_get_credentials_gcs_with_keyfile_dict(self):
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {"keyfile_dict": '{"type": 
"service_account"}'}
+        engine = DataFusionEngine()
+
+        credentials, extra_config = engine._get_credentials(mock_conn)
+
+        assert credentials == {"keyfile_dict": '{"type": "service_account"}'}
+        assert extra_config == {}
+
+    def test_get_credentials_gcs_without_key_path_uses_ambient_auth(self, 
monkeypatch):
+        monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS", raising=False)
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {}
+        engine = DataFusionEngine()
+
+        credentials, extra_config = engine._get_credentials(mock_conn)
+
+        assert credentials == {}
+        assert extra_config == {}
+
+    def 
test_get_credentials_gcs_uses_google_application_credentials_env_var(self, 
monkeypatch):
+        monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS", "/env/key.json")
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {}
+        engine = DataFusionEngine()
+
+        credentials, extra_config = engine._get_credentials(mock_conn)
+
+        assert credentials == {"key_path": "/env/key.json"}
+        assert extra_config == {}
+
+    def 
test_get_credentials_gcs_connection_key_path_takes_priority_over_env_var(self, 
monkeypatch):
+        monkeypatch.setenv("GOOGLE_APPLICATION_CREDENTIALS", "/env/key.json")
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {"key_path": "/connection/key.json"}
+        engine = DataFusionEngine()
+
+        credentials, extra_config = engine._get_credentials(mock_conn)
+
+        assert credentials == {"key_path": "/connection/key.json"}
+        assert extra_config == {}
+
+    def 
test_get_credentials_gcs_key_path_and_keyfile_dict_mutually_exclusive(self):
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {"key_path": "/path/to/key.json", 
"keyfile_dict": "{}"}
+        engine = DataFusionEngine()
+
+        with pytest.raises(ValueError, match="mutually exclusive"):
+            engine._get_credentials(mock_conn)
+
+    @pytest.mark.parametrize(
+        "unsupported_field", ["key_secret_name", "credential_config_file", 
"impersonation_chain"]
+    )
+    def test_get_credentials_gcs_rejects_unsupported_identity_fields(self, 
unsupported_field):
+        mock_conn = MagicMock()
+        mock_conn.conn_type = "google_cloud_platform"
+        mock_conn.extra_dejson = {unsupported_field: "some-value"}
+        engine = DataFusionEngine()
+
+        with pytest.raises(ValueError, match=f"{unsupported_field!r} is not 
supported"):
+            engine._get_credentials(mock_conn)
+
     def test_get_credentials_unknown_type(self):
         mock_conn = MagicMock()
         mock_conn.conn_type = "dummy"
diff --git 
a/providers/common/sql/tests/unit/common/sql/datafusion/test_object_storage_provider.py
 
b/providers/common/sql/tests/unit/common/sql/datafusion/test_object_storage_provider.py
index 9b0ff756a1f..6c1f2524d72 100644
--- 
a/providers/common/sql/tests/unit/common/sql/datafusion/test_object_storage_provider.py
+++ 
b/providers/common/sql/tests/unit/common/sql/datafusion/test_object_storage_provider.py
@@ -16,6 +16,8 @@
 # under the License.
 from __future__ import annotations
 
+import json
+import os
 from unittest.mock import patch
 
 import pytest
@@ -23,6 +25,7 @@ import pytest
 from airflow.providers.common.sql.config import ConnectionConfig, StorageType
 from airflow.providers.common.sql.datafusion.exceptions import 
ObjectStoreCreationException
 from airflow.providers.common.sql.datafusion.object_storage_provider import (
+    GCSObjectStorageProvider,
     LocalObjectStorageProvider,
     S3ObjectStorageProvider,
     get_object_storage_provider,
@@ -58,6 +61,86 @@ class TestObjectStorageProvider:
             with pytest.raises(ObjectStoreCreationException, match="Failed to 
create S3 object store"):
                 provider.create_object_store("s3://demo-data/path", 
connection_config)
 
+    
@patch("airflow.providers.common.sql.datafusion.object_storage_provider.GoogleCloud")
+    def test_gcs_provider_success_with_key_path(self, mock_gcs, tmp_path):
+        key_path = tmp_path / "key.json"
+        key_path.write_text("{}")
+        provider = GCSObjectStorageProvider()
+        connection_config = ConnectionConfig(
+            conn_id="google_cloud_default",
+            credentials={"key_path": str(key_path)},
+        )
+
+        store = provider.create_object_store("gs://demo-data/path", 
connection_config)
+
+        mock_gcs.assert_called_once_with(bucket_name="demo-data", 
service_account_path=str(key_path))
+        assert store == mock_gcs.return_value
+        assert provider.get_storage_type == StorageType.GCS
+        assert provider.get_scheme() == "gs://"
+
+    
@patch("airflow.providers.common.sql.datafusion.object_storage_provider.GoogleCloud")
+    def test_gcs_provider_success_with_keyfile_dict(self, mock_gcs):
+        provider = GCSObjectStorageProvider()
+        keyfile_dict = {"type": "service_account", "private_key": "fake"}
+        connection_config = ConnectionConfig(
+            conn_id="google_cloud_default",
+            credentials={"keyfile_dict": keyfile_dict},
+        )
+
+        provider.create_object_store("gs://demo-data/path", connection_config)
+
+        mock_gcs.assert_called_once()
+        temp_path = mock_gcs.call_args.kwargs["service_account_path"]
+        assert not os.path.exists(temp_path), "temp key file should be deleted 
after use"
+
+    
@patch("airflow.providers.common.sql.datafusion.object_storage_provider.GoogleCloud")
+    def test_gcs_provider_writes_keyfile_dict_content_before_cleanup(self, 
mock_gcs):
+        written_content = {}
+
+        def _capture_path(*, bucket_name, service_account_path):
+            with open(service_account_path) as f:
+                written_content["data"] = json.load(f)
+
+        mock_gcs.side_effect = _capture_path
+        provider = GCSObjectStorageProvider()
+        keyfile_dict = {"type": "service_account", "private_key": "fake"}
+        connection_config = ConnectionConfig(
+            conn_id="google_cloud_default",
+            credentials={"keyfile_dict": keyfile_dict},
+        )
+
+        provider.create_object_store("gs://demo-data/path", connection_config)
+
+        assert written_content["data"] == keyfile_dict
+
+    def test_gcs_provider_failure(self):
+        provider = GCSObjectStorageProvider()
+        connection_config = ConnectionConfig(conn_id="google_cloud_default")
+
+        with patch(
+            
"airflow.providers.common.sql.datafusion.object_storage_provider.GoogleCloud",
+            side_effect=Exception("Error"),
+        ):
+            with pytest.raises(ObjectStoreCreationException, match="Failed to 
create GCS object store"):
+                provider.create_object_store("gs://demo-data/path", 
connection_config)
+
+    def test_gcs_provider_missing_key_file_raises_clear_error(self):
+        """Uses the real GoogleCloud binding, not a mock, since it's the one 
that panics."""
+        provider = GCSObjectStorageProvider()
+        connection_config = ConnectionConfig(
+            conn_id="google_cloud_default",
+            credentials={"key_path": "/nonexistent/key.json"},
+        )
+
+        with pytest.raises(ObjectStoreCreationException, match="Failed to 
create GCS object store"):
+            provider.create_object_store("gs://demo-data/path", 
connection_config)
+
+    def test_gcs_provider_requires_connection_config(self):
+        provider = GCSObjectStorageProvider()
+
+        with pytest.raises(ValueError, match="connection_config must be 
provided for gcs"):
+            provider.create_object_store("gs://demo-data/path")
+
     
@patch("airflow.providers.common.sql.datafusion.object_storage_provider.LocalFileSystem")
     def test_local_provider(self, mock_local):
         provider = LocalObjectStorageProvider()
@@ -68,6 +151,7 @@ class TestObjectStorageProvider:
 
     def test_get_object_storage_provider(self):
         assert isinstance(get_object_storage_provider(StorageType.S3), 
S3ObjectStorageProvider)
+        assert isinstance(get_object_storage_provider(StorageType.GCS), 
GCSObjectStorageProvider)
         assert isinstance(get_object_storage_provider(StorageType.LOCAL), 
LocalObjectStorageProvider)
 
         with pytest.raises(ValueError, match="Unsupported storage type"):
diff --git a/providers/common/sql/tests/unit/common/sql/test_config.py 
b/providers/common/sql/tests/unit/common/sql/test_config.py
index 5478b5a2e44..14f3827c6a0 100644
--- a/providers/common/sql/tests/unit/common/sql/test_config.py
+++ b/providers/common/sql/tests/unit/common/sql/test_config.py
@@ -33,6 +33,7 @@ class TestDataSourceConfig:
         ("uri", "expected_type"),
         [
             ("s3://bucket/path", StorageType.S3),
+            ("gs://bucket/path", StorageType.GCS),
             ("file:///path/to/file", StorageType.LOCAL),
         ],
     )

Reply via email to