Airflow Summit 2026 is coming August 31 - September 2 in Austin, TX. Register now to secure your spot!

Source code for airflow.providers.ibm.db2.dialects.db2

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations

from airflow.providers.common.sql.dialects.dialect import Dialect


[docs] class Db2Dialect(Dialect): """ Db2-specific SQL dialect implementation. Provides Db2-specific SQL generation, particularly for MERGE (upsert) operations. """
[docs] def get_column_names( self, table: str, schema: str | None = None, predicate=None, **kwargs, ) -> list[str] | None: """ Return column names for a table, excluding identity columns. Db2 marks auto-generated identity columns with ``autoincrement=True`` in the SQLAlchemy reflection result. Excluding them here mirrors the hook-level behaviour that was previously implemented on ``Db2Hook``, and delegates the actual inspector call to the base ``Dialect.get_column_names`` predicate API. :param table: Table name (may include schema prefix as ``schema.table``) :param schema: Optional schema name; takes precedence over a schema prefix in *table* :return: Column names with identity (``autoincrement``) columns removed """ return super().get_column_names( table, schema, predicate=lambda col: not col.get("autoincrement", False), )
[docs] def generate_replace_sql(self, table, values, target_fields, **kwargs) -> str: """ Generate MERGE SQL statement for Db2. Db2 doesn't support REPLACE INTO syntax. Instead, it uses MERGE statements for upsert operations. :param table: Name of the target table :param values: The row to insert/update :param target_fields: The names of the columns to fill in the table :param kwargs: Additional parameters, including replace_index: list of column names to use for matching (defaults to primary keys) :return: The generated MERGE SQL statement Example generated SQL:: MERGE INTO table AS t USING (VALUES (?, ?, ?)) AS s(col1, col2, col3) ON t.pk = s.pk WHEN MATCHED THEN UPDATE SET t.col1 = s.col1, t.col2 = s.col2 WHEN NOT MATCHED THEN INSERT (col1, col2, col3) VALUES (s.col1, s.col2, s.col3) """ # Validate target_fields if not target_fields: raise ValueError("Db2 MERGE syntax requires column names") # Get the columns to use for matching (ON clause) replace_index = kwargs.get("replace_index") or self.get_primary_keys(table) if not replace_index: raise ValueError( f"Cannot generate MERGE statement for table '{table}': " "no primary key found and no replace_index provided. " "Specify replace_index parameter with column names to use for matching." ) # Convert string to list if needed if isinstance(replace_index, str): replace_index = [replace_index] # Escape column names escaped_target_fields = [self.escape_word(field) for field in target_fields] escaped_replace_index = [self.escape_word(field) for field in replace_index] # Build placeholders for VALUES clause placeholders = self._joined_placeholders(values) # Build column list for USING clause columns = ", ".join(escaped_target_fields) # Build ON clause (matching condition) on_conditions = [f"t.{col} = s.{col}" for col in escaped_replace_index] on_clause = " AND ".join(on_conditions) # Build UPDATE SET clause (exclude primary key columns from update) update_fields = [field for field in escaped_target_fields if field not in escaped_replace_index] if update_fields: update_assignments = [f"t.{field} = s.{field}" for field in update_fields] update_clause = ", ".join(update_assignments) when_matched = f"WHEN MATCHED THEN UPDATE SET {update_clause}" else: # If all columns are in the primary key, no update needed when_matched = "" # Build INSERT clause insert_cols = ", ".join(escaped_target_fields) insert_vals = ", ".join([f"s.{field}" for field in escaped_target_fields]) # Construct the full MERGE statement sql = f"MERGE INTO {table} AS t USING (VALUES ({placeholders})) AS s({columns}) ON {on_clause}" if when_matched: sql += f" {when_matched}" sql += f" WHEN NOT MATCHED THEN INSERT ({insert_cols}) VALUES ({insert_vals})" return sql

Was this entry helpful?