apache-airflow-providers-common-sql 2.1.1__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/PKG-INFO +6 -6
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/README.rst +3 -3
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/changelog.rst +31 -0
- apache_airflow_providers_common_sql-2.2.0/docs/connections.rst +164 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/dialects.rst +6 -4
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/index.rst +3 -3
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/operators.rst +22 -1
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/provider.yaml +2 -1
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/pyproject.toml +3 -3
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/__init__.py +1 -1
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/config.py +16 -3
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/datafusion/engine.py +35 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/datafusion/object_storage_provider.py +58 -3
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/example_dags/example_analytics.py +13 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/sensors/sql.py +1 -1
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_sql_bulk_load.py +2 -2
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_sql_execute_query.py +2 -2
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_sql_insert_rows.py +2 -2
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_sql_threshold_check.py +2 -2
- apache_airflow_providers_common_sql-2.2.0/tests/unit/common/sql/datafusion/test_base.py +74 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/datafusion/test_engine.py +114 -1
- apache_airflow_providers_common_sql-2.2.0/tests/unit/common/sql/datafusion/test_exceptions.py +40 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/datafusion/test_format_handlers.py +1 -1
- apache_airflow_providers_common_sql-2.2.0/tests/unit/common/sql/datafusion/test_object_storage_provider.py +165 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/decorators/test_sql.py +2 -5
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/operators/test_sql.py +1 -2
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/sensors/test_sql.py +2 -3
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/test_config.py +29 -0
- apache_airflow_providers_common_sql-2.1.1/docs/connections.rst +0 -32
- apache_airflow_providers_common_sql-2.1.1/tests/unit/common/sql/datafusion/test_object_storage_provider.py +0 -74
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/.gitignore +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/LICENSE +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/NOTICE +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/.latest-doc-only-change.txt +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/commits.rst +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/conf.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/dataframes.rst +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/installing-providers-from-sources.rst +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/integration-logos/sql.png +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/security.rst +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/docs/supported-database-types.rst +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/README_API.md +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/datafusion/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/datafusion/base.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/datafusion/exceptions.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/datafusion/format_handlers.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/decorators/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/decorators/analytics.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/decorators/sql.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/dialects/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/dialects/dialect.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/dialects/dialect.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/doc/adr/0001-record-architecture-decisions.md +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/doc/adr/0002-return-common-data-structure-from-dbapihook-derived-hooks.md +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/doc/adr/0003-introduce-notion-of-dialects-in-dbapihook.md +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/example_dags/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/get_provider_info.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/get_provider_info.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/handlers.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/handlers.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/lineage.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/lineage.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/sql.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/hooks/sql.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/operators/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/operators/analytics.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/operators/generic_transfer.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/operators/generic_transfer.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/operators/sql.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/sensors/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/sensors/sql.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/triggers/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/triggers/sql.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/triggers/sql.pyi +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/src/airflow/providers/common/sql/version_compat.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/conftest.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_generic_transfer.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_sql_column_table_check.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/system/common/sql/example_sql_value_check.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/datafusion/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/decorators/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/decorators/test_analytics.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/dialects/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/dialects/test_dialect.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/hooks/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/hooks/test_dbapi.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/hooks/test_handlers.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/hooks/test_lineage.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/hooks/test_sql.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/hooks/test_sqlparse.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/operators/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/operators/test_analytics.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/operators/test_generic_transfer.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/operators/test_sql_execute.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/sensors/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/triggers/__init__.py +0 -0
- {apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/tests/unit/common/sql/triggers/test_sql.py +0 -0
{apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: apache-airflow-providers-common-sql
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Provider package apache-airflow-providers-common-sql for Apache Airflow
|
|
5
5
|
Keywords: airflow-provider,common.sql,airflow,integration
|
|
6
6
|
Author-email: Apache Software Foundation <dev@airflow.apache.org>
|
|
@@ -39,8 +39,8 @@ Requires-Dist: polars>=1.26.0 ; extra == "polars"
|
|
|
39
39
|
Requires-Dist: pyiceberg-core>=0.8.0 ; extra == "pyiceberg-core"
|
|
40
40
|
Requires-Dist: sqlalchemy>=1.4.54 ; extra == "sqlalchemy"
|
|
41
41
|
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
|
42
|
-
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
43
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
42
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0/changelog.html
|
|
43
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0
|
|
44
44
|
Project-URL: Mastodon, https://fosstodon.org/@airflow
|
|
45
45
|
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
|
46
46
|
Project-URL: Source Code, https://github.com/apache/airflow
|
|
@@ -83,7 +83,7 @@ Import-Namespace: airflow.providers.common
|
|
|
83
83
|
|
|
84
84
|
Package ``apache-airflow-providers-common-sql``
|
|
85
85
|
|
|
86
|
-
Release: ``2.
|
|
86
|
+
Release: ``2.2.0``
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
`Common SQL Provider <https://en.wikipedia.org/wiki/SQL>`__
|
|
@@ -96,7 +96,7 @@ This is a provider package for ``common.sql`` provider. All classes for this pro
|
|
|
96
96
|
are in ``airflow.providers.common.sql`` python package.
|
|
97
97
|
|
|
98
98
|
You can find package information and changelog for the provider
|
|
99
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
99
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0/>`_.
|
|
100
100
|
|
|
101
101
|
Installation
|
|
102
102
|
------------
|
|
@@ -158,5 +158,5 @@ Extra Dependencies
|
|
|
158
158
|
================== =======================================================================================================================================================================
|
|
159
159
|
|
|
160
160
|
The changelog for the provider package can be found in the
|
|
161
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
161
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0/changelog.html>`_.
|
|
162
162
|
|
{apache_airflow_providers_common_sql-2.1.1 → apache_airflow_providers_common_sql-2.2.0}/README.rst
RENAMED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
Package ``apache-airflow-providers-common-sql``
|
|
25
25
|
|
|
26
|
-
Release: ``2.
|
|
26
|
+
Release: ``2.2.0``
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
`Common SQL Provider <https://en.wikipedia.org/wiki/SQL>`__
|
|
@@ -36,7 +36,7 @@ This is a provider package for ``common.sql`` provider. All classes for this pro
|
|
|
36
36
|
are in ``airflow.providers.common.sql`` python package.
|
|
37
37
|
|
|
38
38
|
You can find package information and changelog for the provider
|
|
39
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
39
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0/>`_.
|
|
40
40
|
|
|
41
41
|
Installation
|
|
42
42
|
------------
|
|
@@ -98,4 +98,4 @@ Extra Dependencies
|
|
|
98
98
|
================== =======================================================================================================================================================================
|
|
99
99
|
|
|
100
100
|
The changelog for the provider package can be found in the
|
|
101
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
101
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0/changelog.html>`_.
|
|
@@ -25,6 +25,37 @@
|
|
|
25
25
|
Changelog
|
|
26
26
|
---------
|
|
27
27
|
|
|
28
|
+
2.2.0
|
|
29
|
+
.....
|
|
30
|
+
|
|
31
|
+
Features
|
|
32
|
+
~~~~~~~~
|
|
33
|
+
|
|
34
|
+
* ``Template every connection id accepted by provider operators (#73286)``
|
|
35
|
+
* ``Add GCS support to the DataFusion object storage layer (#73370)``
|
|
36
|
+
* ``Allow DataSourceConfig to represent a plain database table (#73273)``
|
|
37
|
+
|
|
38
|
+
Bug Fixes
|
|
39
|
+
~~~~~~~~~
|
|
40
|
+
|
|
41
|
+
* ``Read the legacy extra__google_cloud_platform__ prefix for GCS credentials (#73613)``
|
|
42
|
+
* ``Fix DataFusion storage-type error message on Python 3.11+ (#73413)``
|
|
43
|
+
* ``Reject a DataSourceConfig with no uri or format in DataFusionEngine (#73287)``
|
|
44
|
+
* ``Fix exception messages rendering as a tuple instead of the value (#73296)``
|
|
45
|
+
|
|
46
|
+
Doc-only
|
|
47
|
+
~~~~~~~~
|
|
48
|
+
|
|
49
|
+
* ``Rewrite the 'common.sql' connections guide and fix the dialect extra name (#73605)``
|
|
50
|
+
* ``Fix grammar in the dialects documentation (#73145)``
|
|
51
|
+
|
|
52
|
+
.. Below changes are excluded from the changelog. Move them to
|
|
53
|
+
appropriate section above if needed. Do not delete the lines(!):
|
|
54
|
+
* ``Revert "[main] Upgrade important CI environment (#73308)" (#73621)``
|
|
55
|
+
* ``[main] Upgrade important CI environment (#73308)``
|
|
56
|
+
* ``Add tests for DataFusion base classes (#71983)``
|
|
57
|
+
* ``Use common.compat.sdk for the remaining provider timezone imports (#71209)``
|
|
58
|
+
|
|
28
59
|
2.1.1
|
|
29
60
|
.....
|
|
30
61
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
.. Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
or more contributor license agreements. See the NOTICE file
|
|
3
|
+
distributed with this work for additional information
|
|
4
|
+
regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
to you under the Apache License, Version 2.0 (the
|
|
6
|
+
"License"); you may not use this file except in compliance
|
|
7
|
+
with the License. You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
.. http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
.. Unless required by applicable law or agreed to in writing,
|
|
12
|
+
software distributed under the License is distributed on an
|
|
13
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
KIND, either express or implied. See the License for the
|
|
15
|
+
specific language governing permissions and limitations
|
|
16
|
+
under the License.
|
|
17
|
+
|
|
18
|
+
.. _howto/connection:sql:
|
|
19
|
+
|
|
20
|
+
Connecting to SQL Databases
|
|
21
|
+
===========================
|
|
22
|
+
|
|
23
|
+
The common SQL provider has no connection type of its own. Its operators, sensor and
|
|
24
|
+
``@task.sql`` decorator work with a connection from a database provider: a ``postgres``
|
|
25
|
+
connection from the Postgres provider, a ``snowflake`` connection from the Snowflake provider,
|
|
26
|
+
and so on. Create the connection as the database provider documents it, then pass its ID to
|
|
27
|
+
the operator.
|
|
28
|
+
|
|
29
|
+
.. code-block:: python
|
|
30
|
+
|
|
31
|
+
from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator
|
|
32
|
+
|
|
33
|
+
create_table = SQLExecuteQueryOperator(
|
|
34
|
+
task_id="create_table",
|
|
35
|
+
conn_id="my_postgres",
|
|
36
|
+
sql="CREATE TABLE IF NOT EXISTS users (id INT, name TEXT)",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
The same task runs against MySQL, Snowflake or Trino by pointing ``conn_id`` at a connection
|
|
40
|
+
of that type. The SQL itself still has to be valid for that database.
|
|
41
|
+
|
|
42
|
+
Setting up the connection
|
|
43
|
+
-------------------------
|
|
44
|
+
|
|
45
|
+
1. Install the provider for your database, for example ``apache-airflow-providers-postgres``.
|
|
46
|
+
:doc:`supported-database-types` lists the providers that work with common SQL.
|
|
47
|
+
2. Create a connection of that provider's type. The fields and extras are described on the
|
|
48
|
+
provider's own connection page, such as
|
|
49
|
+
:doc:`the Postgres connection <apache-airflow-providers-postgres:connections/postgres>`.
|
|
50
|
+
3. Pass the connection ID as ``conn_id``. There is no default connection ID, so every task has
|
|
51
|
+
to name one. ``GenericTransfer`` takes two: ``source_conn_id`` and ``destination_conn_id``.
|
|
52
|
+
|
|
53
|
+
How the hook is chosen
|
|
54
|
+
----------------------
|
|
55
|
+
|
|
56
|
+
At run time, a common SQL operator looks up the connection and instantiates the hook class that
|
|
57
|
+
the database provider registered for that connection type. The hook has to be a subclass of
|
|
58
|
+
:class:`~airflow.providers.common.sql.hooks.sql.DbApiHook`, otherwise the task fails with an
|
|
59
|
+
error naming the hook class it found.
|
|
60
|
+
|
|
61
|
+
To see which hook a connection type resolves to, run:
|
|
62
|
+
|
|
63
|
+
.. code-block:: bash
|
|
64
|
+
|
|
65
|
+
airflow providers hooks
|
|
66
|
+
|
|
67
|
+
If a connection type is missing from that list, its provider is not installed in the
|
|
68
|
+
environment the task runs in. If the hook is listed but is not a ``DbApiHook``, that connection
|
|
69
|
+
type cannot be used with common SQL (an HTTP or cloud storage connection, for example).
|
|
70
|
+
|
|
71
|
+
Overriding connection settings per task
|
|
72
|
+
---------------------------------------
|
|
73
|
+
|
|
74
|
+
To change how one task connects without editing a connection other tasks share, set
|
|
75
|
+
``database`` or ``hook_params`` on the operator.
|
|
76
|
+
|
|
77
|
+
``database``
|
|
78
|
+
Runs the task against a different database from the one set in the connection. For most
|
|
79
|
+
connection types this replaces the connection's ``schema`` field, which is where Postgres,
|
|
80
|
+
MySQL and similar providers store the database name.
|
|
81
|
+
|
|
82
|
+
``hook_params``
|
|
83
|
+
A dictionary passed as keyword arguments to the hook's constructor. Which keys a hook
|
|
84
|
+
accepts is up to the database provider. For example, the Snowflake hook accepts
|
|
85
|
+
``warehouse`` and ``role``:
|
|
86
|
+
|
|
87
|
+
.. code-block:: python
|
|
88
|
+
|
|
89
|
+
SQLExecuteQueryOperator(
|
|
90
|
+
task_id="nightly_rollup",
|
|
91
|
+
conn_id="snowflake_default",
|
|
92
|
+
sql="CALL rollup_daily_sales()",
|
|
93
|
+
hook_params={"warehouse": "REPORTING_WH", "role": "REPORTER"},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
For operators in ``airflow.providers.common.sql.operators.sql``, the connection's extras are
|
|
97
|
+
merged into ``hook_params`` before the hook is built, and a key set in ``hook_params`` wins
|
|
98
|
+
over the same key in the extras. ``SQLSensor`` takes ``hook_params`` too, and
|
|
99
|
+
``GenericTransfer`` takes ``source_hook_params`` and ``destination_hook_params``.
|
|
100
|
+
|
|
101
|
+
Connection extras read by common SQL
|
|
102
|
+
------------------------------------
|
|
103
|
+
|
|
104
|
+
Every ``DbApiHook`` reads the following keys from the connection's extras, on top of whatever
|
|
105
|
+
the database provider documents. They control the SQL that ``insert_rows``,
|
|
106
|
+
``SQLInsertRowsOperator`` and ``GenericTransfer`` generate, and they matter most for generic
|
|
107
|
+
connection types such as ODBC and JDBC, where Airflow cannot tell from the connection alone
|
|
108
|
+
which database is on the other end.
|
|
109
|
+
|
|
110
|
+
.. list-table::
|
|
111
|
+
:header-rows: 1
|
|
112
|
+
:widths: 25 20 55
|
|
113
|
+
|
|
114
|
+
* - Extra
|
|
115
|
+
- Default
|
|
116
|
+
- Effect
|
|
117
|
+
* - ``placeholder``
|
|
118
|
+
- ``%s``
|
|
119
|
+
- Parameter placeholder in generated statements. Only ``%s`` and ``?`` are accepted; any
|
|
120
|
+
other value is ignored with a warning. Some hooks set their own default, such as ``?``
|
|
121
|
+
for SQLite.
|
|
122
|
+
* - ``sqlalchemy_scheme``
|
|
123
|
+
- none (``mssql+pyodbc`` for ODBC)
|
|
124
|
+
- SQLAlchemy scheme for connection types that have none of their own, such as ODBC and
|
|
125
|
+
JDBC. The :doc:`dialect <dialects>` is taken from it, so ``mssql+pyodbc`` selects the
|
|
126
|
+
``mssql`` dialect. It takes precedence over ``dialect``.
|
|
127
|
+
* - ``dialect``
|
|
128
|
+
- ``default``
|
|
129
|
+
- Name of the dialect to use, such as ``mssql`` or ``postgresql``. Read only when neither the
|
|
130
|
+
connection URI nor ``sqlalchemy_scheme`` gives a dialect, as with a JDBC connection whose
|
|
131
|
+
``host`` holds a ``jdbc:`` URL.
|
|
132
|
+
* - ``insert_statement_format``
|
|
133
|
+
- ``INSERT INTO {} {} VALUES ({})``
|
|
134
|
+
- Template for insert statements. The three fields are the table, the column list and the
|
|
135
|
+
placeholders.
|
|
136
|
+
* - ``replace_statement_format``
|
|
137
|
+
- ``REPLACE INTO {} {} VALUES ({})``
|
|
138
|
+
- Template for upsert statements, used when ``replace=True``.
|
|
139
|
+
* - ``escape_word_format``
|
|
140
|
+
- ``"{}"``
|
|
141
|
+
- How a column name is quoted when it needs escaping, for example ``[{}]`` for SQL Server.
|
|
142
|
+
* - ``escape_column_names``
|
|
143
|
+
- ``false``
|
|
144
|
+
- Quote every column name. By default only reserved words and names containing special
|
|
145
|
+
characters are quoted.
|
|
146
|
+
|
|
147
|
+
For example, an ODBC connection to SQL Server that uses pyodbc's ``?`` placeholders and
|
|
148
|
+
bracket-quoted column names can set:
|
|
149
|
+
|
|
150
|
+
.. code-block:: json
|
|
151
|
+
|
|
152
|
+
{
|
|
153
|
+
"sqlalchemy_scheme": "mssql+pyodbc",
|
|
154
|
+
"placeholder": "?",
|
|
155
|
+
"escape_word_format": "[{}]"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
``mssql+pyodbc`` is already the ODBC default. An ODBC connection to any other database has to
|
|
159
|
+
change it, because setting ``dialect`` alone keeps the ``mssql`` dialect.
|
|
160
|
+
|
|
161
|
+
The ``mssql`` and ``postgresql`` dialects are registered by the Microsoft SQL Server and Postgres
|
|
162
|
+
providers. If the provider for a dialect is not installed, the hook uses the default dialect
|
|
163
|
+
without a warning, so an upsert through ODBC to SQL Server generates ``REPLACE INTO`` instead of
|
|
164
|
+
``MERGE`` unless ``apache-airflow-providers-microsoft-mssql`` is installed.
|
|
@@ -25,7 +25,7 @@ and the :class:`~airflow.providers.jdbc.hooks.jdbc.JdbcHook` are generic hooks w
|
|
|
25
25
|
database that has a driver for it, it needed an abstraction layer which allows us to run specialized queries
|
|
26
26
|
depending of the database to which we connect and that's why dialects where introduced.
|
|
27
27
|
|
|
28
|
-
The default :class:`~airflow.providers.common.sql.dialects.dialect.Dialect` class has following operations
|
|
28
|
+
The default :class:`~airflow.providers.common.sql.dialects.dialect.Dialect` class has the following operations
|
|
29
29
|
available which underneath use SQLAlchemy to execute, but can be overloaded with specialized implementations
|
|
30
30
|
per database:
|
|
31
31
|
|
|
@@ -45,11 +45,13 @@ At the moment there are only 3 dialects available:
|
|
|
45
45
|
- ``mssql`` :class:`~airflow.providers.microsoft.mssql.dialects.mssql.MsSqlDialect` specialized for Microsoft SQL Server;
|
|
46
46
|
- ``postgresql`` :class:`~airflow.providers.postgres.dialects.postgres.PostgresDialect` specialized for PostgreSQL;
|
|
47
47
|
|
|
48
|
-
The dialect
|
|
49
|
-
the
|
|
48
|
+
The dialect is derived from the connection URI. ODBC and JDBC connections have no database in their connection type, so
|
|
49
|
+
set the ``sqlalchemy_scheme`` extra to name it, for example ``mssql+pyodbc``. The dialect is the part before the
|
|
50
|
+
``+``. The ``dialect`` extra is read only when neither the URI nor ``sqlalchemy_scheme`` gives a dialect, as with a JDBC
|
|
51
|
+
connection whose ``host`` holds a ``jdbc:`` URL:
|
|
50
52
|
|
|
51
53
|
.. code-block::
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
dialect: 'mssql'
|
|
54
56
|
|
|
55
57
|
If a specific dialect isn't available for a database, the default one will be used, same when a non-existing dialect name is specified.
|
|
@@ -79,7 +79,7 @@ apache-airflow-providers-common-sql package
|
|
|
79
79
|
`Common SQL Provider <https://en.wikipedia.org/wiki/SQL>`__
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
Release: 2.
|
|
82
|
+
Release: 2.2.0
|
|
83
83
|
|
|
84
84
|
Provider package
|
|
85
85
|
----------------
|
|
@@ -160,5 +160,5 @@ Downloading official packages
|
|
|
160
160
|
You can download officially released packages and verify their checksums and signatures from the
|
|
161
161
|
`Official Apache Download site <https://downloads.apache.org/airflow/providers/>`_
|
|
162
162
|
|
|
163
|
-
* `The apache-airflow-providers-common-sql 2.
|
|
164
|
-
* `The apache-airflow-providers-common-sql 2.
|
|
163
|
+
* `The apache-airflow-providers-common-sql 2.2.0 sdist package <https://downloads.apache.org/airflow/providers/apache_airflow_providers_common_sql-2.2.0.tar.gz>`_ (`asc <https://downloads.apache.org/airflow/providers/apache_airflow_providers_common_sql-2.2.0.tar.gz.asc>`__, `sha512 <https://downloads.apache.org/airflow/providers/apache_airflow_providers_common_sql-2.2.0.tar.gz.sha512>`__)
|
|
164
|
+
* `The apache-airflow-providers-common-sql 2.2.0 wheel package <https://downloads.apache.org/airflow/providers/apache_airflow_providers_common_sql-2.2.0-py3-none-any.whl>`_ (`asc <https://downloads.apache.org/airflow/providers/apache_airflow_providers_common_sql-2.2.0-py3-none-any.whl.asc>`__, `sha512 <https://downloads.apache.org/airflow/providers/apache_airflow_providers_common_sql-2.2.0-py3-none-any.whl.sha512>`__)
|
|
@@ -296,10 +296,11 @@ The Analytics Operator is ideal for performing efficient, high-performance analy
|
|
|
296
296
|
Supported Storage Systems
|
|
297
297
|
-------------------------
|
|
298
298
|
- S3
|
|
299
|
+
- GCS
|
|
299
300
|
- Local File System
|
|
300
301
|
|
|
301
302
|
.. note::
|
|
302
|
-
|
|
303
|
+
Azure, HTTP, Delta are not yet supported but will be added in the future.
|
|
303
304
|
|
|
304
305
|
|
|
305
306
|
|
|
@@ -341,6 +342,26 @@ S3 Storage
|
|
|
341
342
|
:start-after: [START howto_analytics_operator_with_s3]
|
|
342
343
|
:end-before: [END howto_analytics_operator_with_s3]
|
|
343
344
|
|
|
345
|
+
GCS Storage
|
|
346
|
+
-----------
|
|
347
|
+
Use a ``conn_id`` pointing to a ``google_cloud_platform`` connection. Credentials are
|
|
348
|
+
resolved in this order:
|
|
349
|
+
|
|
350
|
+
1. ``key_path`` -- a service account JSON file on disk
|
|
351
|
+
2. ``keyfile_dict`` -- the service account JSON contents, inline
|
|
352
|
+
3. the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable
|
|
353
|
+
4. Application Default Credentials -- the local ``gcloud`` CLI credentials file, or
|
|
354
|
+
the GCE/GKE metadata server
|
|
355
|
+
|
|
356
|
+
``key_path`` and ``keyfile_dict`` are mutually exclusive. ``key_secret_name``,
|
|
357
|
+
``credential_config_file``, and ``impersonation_chain`` are not supported.
|
|
358
|
+
|
|
359
|
+
.. exampleinclude:: /../../sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
|
|
360
|
+
:language: python
|
|
361
|
+
:dedent: 4
|
|
362
|
+
:start-after: [START howto_analytics_operator_with_gcs]
|
|
363
|
+
:end-before: [END howto_analytics_operator_with_gcs]
|
|
364
|
+
|
|
344
365
|
Local File System Storage
|
|
345
366
|
-------------------------
|
|
346
367
|
.. exampleinclude:: /../../sql/src/airflow/providers/common/sql/example_dags/example_analytics.py
|
|
@@ -23,12 +23,13 @@ description: |
|
|
|
23
23
|
|
|
24
24
|
state: ready
|
|
25
25
|
lifecycle: production
|
|
26
|
-
source-date-epoch:
|
|
26
|
+
source-date-epoch: 1790023613
|
|
27
27
|
# Note that those versions are maintained by release manager - do not update them manually
|
|
28
28
|
# with the exception of case where other provider in sources has >= new provider version.
|
|
29
29
|
# In such case adding >= NEW_VERSION and bumping to NEW_VERSION in a provider have
|
|
30
30
|
# to be done in the same PR
|
|
31
31
|
versions:
|
|
32
|
+
- 2.2.0
|
|
32
33
|
- 2.1.1
|
|
33
34
|
- 2.1.0
|
|
34
35
|
- 2.0.3
|
|
@@ -25,7 +25,7 @@ build-backend = "flit_core.buildapi"
|
|
|
25
25
|
|
|
26
26
|
[project]
|
|
27
27
|
name = "apache-airflow-providers-common-sql"
|
|
28
|
-
version = "2.
|
|
28
|
+
version = "2.2.0"
|
|
29
29
|
description = "Provider package apache-airflow-providers-common-sql for Apache Airflow"
|
|
30
30
|
readme = "README.rst"
|
|
31
31
|
license = "Apache-2.0"
|
|
@@ -148,8 +148,8 @@ apache-airflow-providers-common-sql = {workspace = true}
|
|
|
148
148
|
apache-airflow-providers-standard = {workspace = true}
|
|
149
149
|
|
|
150
150
|
[project.urls]
|
|
151
|
-
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
152
|
-
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.
|
|
151
|
+
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0"
|
|
152
|
+
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-common-sql/2.2.0/changelog.html"
|
|
153
153
|
"Bug Tracker" = "https://github.com/apache/airflow/issues"
|
|
154
154
|
"Source Code" = "https://github.com/apache/airflow"
|
|
155
155
|
"Slack Chat" = "https://s.apache.org/airflow-slack"
|
|
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
|
|
|
29
29
|
|
|
30
30
|
__all__ = ["__version__"]
|
|
31
31
|
|
|
32
|
-
__version__ = "2.
|
|
32
|
+
__version__ = "2.2.0"
|
|
33
33
|
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
|
35
35
|
"2.11.0"
|
|
@@ -47,6 +47,7 @@ class StorageType(str, Enum):
|
|
|
47
47
|
"""Storage types for Data Fusion."""
|
|
48
48
|
|
|
49
49
|
S3 = "s3"
|
|
50
|
+
GCS = "gcs"
|
|
50
51
|
LOCAL = "local"
|
|
51
52
|
|
|
52
53
|
|
|
@@ -62,6 +63,9 @@ class DataSourceConfig:
|
|
|
62
63
|
require ``uri`` or ``storage_type``; they use ``conn_id`` and format-specific
|
|
63
64
|
keys in ``options`` (e.g. ``catalog_table_name`` for Iceberg).
|
|
64
65
|
|
|
66
|
+
**Plain database tables** (neither ``uri`` nor ``format`` set) are not
|
|
67
|
+
object-store backed either; ``storage_type`` stays ``None`` and is not inferred.
|
|
68
|
+
|
|
65
69
|
:param conn_id: The connection ID to use for accessing the data source.
|
|
66
70
|
:param uri: The URI of the data source (e.g., file path, S3 bucket, etc.).
|
|
67
71
|
Not required for catalog-managed formats.
|
|
@@ -88,22 +92,31 @@ class DataSourceConfig:
|
|
|
88
92
|
return bool(self.format and self.format.lower() in TABLE_PROVIDERS)
|
|
89
93
|
|
|
90
94
|
def __post_init__(self):
|
|
95
|
+
if not self.table_name or not self.table_name.strip():
|
|
96
|
+
raise ValueError("Table name must be provided for storage type")
|
|
97
|
+
|
|
91
98
|
if self.is_table_provider:
|
|
92
99
|
if self.db_name is None:
|
|
93
100
|
raise ValueError(f"Database name must be provided for table providers {TABLE_PROVIDERS}")
|
|
94
101
|
return
|
|
95
102
|
|
|
103
|
+
if not self.format and not self.uri:
|
|
104
|
+
# Plain database table: no object store involved, so storage_type stays unset.
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
if not self.uri:
|
|
108
|
+
raise ValueError("URI must be provided when format is set")
|
|
109
|
+
|
|
96
110
|
if self.storage_type is None:
|
|
97
111
|
self.storage_type = self._extract_storage_type
|
|
98
112
|
|
|
99
|
-
if self.storage_type is not None and (not self.table_name or not self.table_name.strip()):
|
|
100
|
-
raise ValueError("Table name must be provided for storage type")
|
|
101
|
-
|
|
102
113
|
@property
|
|
103
114
|
def _extract_storage_type(self) -> StorageType | None:
|
|
104
115
|
"""Extract storage type."""
|
|
105
116
|
if self.uri.startswith("s3://"):
|
|
106
117
|
return StorageType.S3
|
|
118
|
+
if self.uri.startswith("gs://"):
|
|
119
|
+
return StorageType.GCS
|
|
107
120
|
if self.uri.startswith("file://"):
|
|
108
121
|
return StorageType.LOCAL
|
|
109
122
|
raise ValueError(f"Unsupported storage type for URI: {self.uri}")
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# under the License.
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import os
|
|
19
20
|
from typing import TYPE_CHECKING, Any
|
|
20
21
|
|
|
21
22
|
from datafusion import SessionContext
|
|
@@ -50,6 +51,12 @@ class DataFusionEngine(LoggingMixin):
|
|
|
50
51
|
if not isinstance(datasource_config, DataSourceConfig):
|
|
51
52
|
raise ValueError("datasource_config must be of type DataSourceConfig")
|
|
52
53
|
|
|
54
|
+
if not datasource_config.is_table_provider and datasource_config.storage_type is None:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"DataSourceConfig for table {datasource_config.table_name!r} has no uri or format; "
|
|
57
|
+
"DataFusionEngine only registers object-store or catalog-managed sources."
|
|
58
|
+
)
|
|
59
|
+
|
|
53
60
|
if not datasource_config.is_table_provider:
|
|
54
61
|
if datasource_config.storage_type == StorageType.LOCAL:
|
|
55
62
|
connection_config = None
|
|
@@ -146,6 +153,14 @@ class DataFusionEngine(LoggingMixin):
|
|
|
146
153
|
conf[key] = conn.extra_dejson[key]
|
|
147
154
|
return conf
|
|
148
155
|
|
|
156
|
+
def _get_gcp_extra_field(extra_dejson: dict[str, Any], field_name: str) -> Any:
|
|
157
|
+
# Older Airflow connection UIs wrote custom extra fields as
|
|
158
|
+
# extra__google_cloud_platform__<field_name> instead of the bare key; GoogleBaseHook
|
|
159
|
+
# still reads that legacy spelling as a fallback, so this must too.
|
|
160
|
+
if field_name in extra_dejson:
|
|
161
|
+
return extra_dejson[field_name]
|
|
162
|
+
return extra_dejson.get(f"extra__google_cloud_platform__{field_name}")
|
|
163
|
+
|
|
149
164
|
match conn.conn_type:
|
|
150
165
|
case "aws":
|
|
151
166
|
try:
|
|
@@ -169,6 +184,26 @@ class DataFusionEngine(LoggingMixin):
|
|
|
169
184
|
credentials = self._remove_none_values(credentials)
|
|
170
185
|
extra_config = _fetch_extra_configs(["region", "endpoint"])
|
|
171
186
|
|
|
187
|
+
case "google_cloud_platform":
|
|
188
|
+
extra_dejson = conn.extra_dejson
|
|
189
|
+
for unsupported_field in ("key_secret_name", "credential_config_file", "impersonation_chain"):
|
|
190
|
+
if _get_gcp_extra_field(extra_dejson, unsupported_field):
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"Connection field {unsupported_field!r} is not supported for DataFusion "
|
|
193
|
+
"GCS access; only key_path, keyfile_dict, GOOGLE_APPLICATION_CREDENTIALS, or "
|
|
194
|
+
"ambient credentials (gcloud ADC file / metadata server) are used."
|
|
195
|
+
)
|
|
196
|
+
key_path = _get_gcp_extra_field(extra_dejson, "key_path") or None
|
|
197
|
+
keyfile_dict = _get_gcp_extra_field(extra_dejson, "keyfile_dict") or None
|
|
198
|
+
if key_path and keyfile_dict:
|
|
199
|
+
raise ValueError(
|
|
200
|
+
"The `keyfile_dict` and `key_path` fields are mutually exclusive. "
|
|
201
|
+
"Please provide only one value."
|
|
202
|
+
)
|
|
203
|
+
if not key_path and not keyfile_dict:
|
|
204
|
+
key_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
|
|
205
|
+
credentials = self._remove_none_values({"key_path": key_path, "keyfile_dict": keyfile_dict})
|
|
206
|
+
|
|
172
207
|
case _:
|
|
173
208
|
raise ValueError(f"Unknown connection type {conn.conn_type}")
|
|
174
209
|
return credentials, extra_config
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
# under the License.
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
import json
|
|
20
|
+
import tempfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from datafusion.object_store import AmazonS3, GoogleCloud, LocalFileSystem
|
|
20
24
|
|
|
21
25
|
from airflow.providers.common.sql.config import ConnectionConfig, StorageType
|
|
22
26
|
from airflow.providers.common.sql.datafusion.base import ObjectStorageProvider
|
|
@@ -34,7 +38,7 @@ class S3ObjectStorageProvider(ObjectStorageProvider):
|
|
|
34
38
|
def create_object_store(self, path: str, connection_config: ConnectionConfig | None = None):
|
|
35
39
|
"""Create an S3 object store using DataFusion's AmazonS3."""
|
|
36
40
|
if connection_config is None:
|
|
37
|
-
raise ValueError("connection_config must be provided for
|
|
41
|
+
raise ValueError(f"connection_config must be provided for {self.get_storage_type.value}")
|
|
38
42
|
|
|
39
43
|
try:
|
|
40
44
|
credentials = connection_config.credentials
|
|
@@ -53,6 +57,56 @@ class S3ObjectStorageProvider(ObjectStorageProvider):
|
|
|
53
57
|
return "s3://"
|
|
54
58
|
|
|
55
59
|
|
|
60
|
+
class GCSObjectStorageProvider(ObjectStorageProvider):
|
|
61
|
+
"""GCS Object Storage Provider using DataFusion's GoogleCloud."""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def get_storage_type(self) -> StorageType:
|
|
65
|
+
"""Return the storage type."""
|
|
66
|
+
return StorageType.GCS
|
|
67
|
+
|
|
68
|
+
def create_object_store(self, path: str, connection_config: ConnectionConfig | None = None):
|
|
69
|
+
"""Create a GCS object store using DataFusion's GoogleCloud."""
|
|
70
|
+
if connection_config is None:
|
|
71
|
+
raise ValueError(f"connection_config must be provided for {self.get_storage_type.value}")
|
|
72
|
+
|
|
73
|
+
credentials = connection_config.credentials
|
|
74
|
+
key_path = credentials.get("key_path")
|
|
75
|
+
keyfile_dict = credentials.get("keyfile_dict")
|
|
76
|
+
temp_key_path: str | None = None
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
bucket = self.get_bucket(path)
|
|
80
|
+
|
|
81
|
+
if not key_path and keyfile_dict:
|
|
82
|
+
# GoogleCloud only accepts a file path, not inline JSON; safe to delete once
|
|
83
|
+
# constructed since the credentials are read only at construction time.
|
|
84
|
+
key_content = keyfile_dict if isinstance(keyfile_dict, str) else json.dumps(keyfile_dict)
|
|
85
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as key_file:
|
|
86
|
+
key_file.write(key_content)
|
|
87
|
+
temp_key_path = key_path = key_file.name
|
|
88
|
+
|
|
89
|
+
if key_path is not None and not Path(key_path).is_file():
|
|
90
|
+
raise FileNotFoundError(f"Service account key file not found: {key_path}")
|
|
91
|
+
|
|
92
|
+
gcs_store = GoogleCloud(bucket_name=bucket, service_account_path=key_path)
|
|
93
|
+
self.log.info("Created GCS object store for bucket %s", bucket)
|
|
94
|
+
|
|
95
|
+
return gcs_store
|
|
96
|
+
|
|
97
|
+
except BaseException as e:
|
|
98
|
+
# A bad key file panics as pyo3_runtime.PanicException, not a plain Exception.
|
|
99
|
+
raise ObjectStoreCreationException(f"Failed to create GCS object store: {e}")
|
|
100
|
+
|
|
101
|
+
finally:
|
|
102
|
+
if temp_key_path is not None:
|
|
103
|
+
Path(temp_key_path).unlink(missing_ok=True)
|
|
104
|
+
|
|
105
|
+
def get_scheme(self) -> str:
|
|
106
|
+
"""Return the scheme for GCS."""
|
|
107
|
+
return "gs://"
|
|
108
|
+
|
|
109
|
+
|
|
56
110
|
class LocalObjectStorageProvider(ObjectStorageProvider):
|
|
57
111
|
"""Local Object Storage Provider using DataFusion's LocalFileSystem."""
|
|
58
112
|
|
|
@@ -72,9 +126,10 @@ class LocalObjectStorageProvider(ObjectStorageProvider):
|
|
|
72
126
|
|
|
73
127
|
def get_object_storage_provider(storage_type: StorageType) -> ObjectStorageProvider:
|
|
74
128
|
"""Get an object storage provider based on the storage type."""
|
|
75
|
-
# TODO: Add support for
|
|
129
|
+
# TODO: Add support for Azure, HTTP: https://datafusion.apache.org/python/autoapi/datafusion/object_store/index.html
|
|
76
130
|
providers: dict[StorageType, type] = {
|
|
77
131
|
StorageType.S3: S3ObjectStorageProvider,
|
|
132
|
+
StorageType.GCS: GCSObjectStorageProvider,
|
|
78
133
|
StorageType.LOCAL: LocalObjectStorageProvider,
|
|
79
134
|
}
|
|
80
135
|
|
|
@@ -30,6 +30,10 @@ datasource_config_local = DataSourceConfig(
|
|
|
30
30
|
conn_id="", table_name="users_data", uri="file:///path/to/", format="parquet"
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
+
datasource_config_gcs = DataSourceConfig(
|
|
34
|
+
conn_id="google_cloud_default", table_name="users_data", uri="gs://bucket/path/", format="parquet"
|
|
35
|
+
)
|
|
36
|
+
|
|
33
37
|
datasource_config_iceberg = DataSourceConfig(
|
|
34
38
|
conn_id="iceberg_default",
|
|
35
39
|
table_name="users_data",
|
|
@@ -72,6 +76,15 @@ with DAG(
|
|
|
72
76
|
|
|
73
77
|
# [END howto_analytics_operator_with_s3]
|
|
74
78
|
|
|
79
|
+
# [START howto_analytics_operator_with_gcs]
|
|
80
|
+
analytics_with_gcs = AnalyticsOperator(
|
|
81
|
+
task_id="analytics_with_gcs",
|
|
82
|
+
datasource_configs=[datasource_config_gcs],
|
|
83
|
+
queries=["SELECT * FROM users_data", "SELECT count(*) FROM users_data"],
|
|
84
|
+
)
|
|
85
|
+
analytics_with_s3 >> analytics_with_gcs
|
|
86
|
+
# [END howto_analytics_operator_with_gcs]
|
|
87
|
+
|
|
75
88
|
# [START howto_analytics_operator_with_local]
|
|
76
89
|
analytics_with_local = AnalyticsOperator(
|
|
77
90
|
task_id="analytics_with_local",
|
|
@@ -57,7 +57,7 @@ class SqlSensor(BaseSensorOperator):
|
|
|
57
57
|
Should match the desired hook constructor params.
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
|
-
template_fields: Sequence[str] = ("sql", "hook_params", "parameters")
|
|
60
|
+
template_fields: Sequence[str] = ("sql", "hook_params", "parameters", "conn_id")
|
|
61
61
|
template_ext: Sequence[str] = (".hql", ".sql")
|
|
62
62
|
ui_color = "#7c7287"
|
|
63
63
|
|