From 6d6767f27330f532b9aa581ad9b95986133761d8 Mon Sep 17 00:00:00 2001 From: SDK Sync Bot Date: Fri, 23 Jan 2026 11:39:28 +0000 Subject: [PATCH] SDK sync: Nullable Columns Support Source commits: JIRA: N/A Generated by SDK Sync automation. --- .../model/declarative_attribute.py | 4 + .../model/declarative_column.py | 8 + .../model/declarative_fact.py | 4 + .../model/declarative_setting.py | 1 + .../physical_model/column.py | 2 + .../logical_model/dataset/dataset.py | 10 ++ .../tests/catalog/test_null_joins_setting.py | 36 +++++ .../tests/catalog/test_nullable_columns.py | 137 ++++++++++++++++++ 8 files changed, 202 insertions(+) create mode 100644 packages/gooddata-sdk/tests/catalog/test_null_joins_setting.py create mode 100644 packages/gooddata-sdk/tests/catalog/test_nullable_columns.py diff --git a/gooddata-api-client/gooddata_api_client/model/declarative_attribute.py b/gooddata-api-client/gooddata_api_client/model/declarative_attribute.py index 9e816757d..51f3429e1 100644 --- a/gooddata-api-client/gooddata_api_client/model/declarative_attribute.py +++ b/gooddata-api-client/gooddata_api_client/model/declarative_attribute.py @@ -137,6 +137,8 @@ def openapi_types(): 'sort_direction': (str,), # noqa: E501 'source_column_data_type': (str,), # noqa: E501 'tags': ([str],), # noqa: E501 + 'is_nullable': (bool,), # noqa: E501 + 'null_value': (str,), # noqa: E501 } @cached_property @@ -157,6 +159,8 @@ def discriminator(): 'sort_direction': 'sortDirection', # noqa: E501 'source_column_data_type': 'sourceColumnDataType', # noqa: E501 'tags': 'tags', # noqa: E501 + 'is_nullable': 'isNullable', # noqa: E501 + 'null_value': 'nullValue', # noqa: E501 } read_only_vars = { diff --git a/gooddata-api-client/gooddata_api_client/model/declarative_column.py b/gooddata-api-client/gooddata_api_client/model/declarative_column.py index 6428838db..46c744edb 100644 --- a/gooddata-api-client/gooddata_api_client/model/declarative_column.py +++ b/gooddata-api-client/gooddata_api_client/model/declarative_column.py @@ -109,6 +109,8 @@ def openapi_types(): 'is_primary_key': (bool,), # noqa: E501 'referenced_table_column': (str,), # noqa: E501 'referenced_table_id': (str,), # noqa: E501 + 'is_nullable': (bool,), # noqa: E501 + 'null_value': (str,), # noqa: E501 } @cached_property @@ -123,6 +125,8 @@ def discriminator(): 'is_primary_key': 'isPrimaryKey', # noqa: E501 'referenced_table_column': 'referencedTableColumn', # noqa: E501 'referenced_table_id': 'referencedTableId', # noqa: E501 + 'is_nullable': 'isNullable', # noqa: E501 + 'null_value': 'nullValue', # noqa: E501 } read_only_vars = { @@ -174,6 +178,8 @@ def _from_openapi_data(cls, data_type, name, *args, **kwargs): # noqa: E501 is_primary_key (bool): Is column part of primary key?. [optional] # noqa: E501 referenced_table_column (str): Referenced table (Foreign key). [optional] # noqa: E501 referenced_table_id (str): Referenced table (Foreign key). [optional] # noqa: E501 + is_nullable (bool): Is column nullable?. [optional] # noqa: E501 + null_value (str): Value to use for null values. [optional] # noqa: E501 """ _check_type = kwargs.pop('_check_type', True) @@ -269,6 +275,8 @@ def __init__(self, data_type, name, *args, **kwargs): # noqa: E501 is_primary_key (bool): Is column part of primary key?. [optional] # noqa: E501 referenced_table_column (str): Referenced table (Foreign key). [optional] # noqa: E501 referenced_table_id (str): Referenced table (Foreign key). [optional] # noqa: E501 + is_nullable (bool): Is column nullable?. [optional] # noqa: E501 + null_value (str): Value to use for null values. [optional] # noqa: E501 """ _check_type = kwargs.pop('_check_type', True) diff --git a/gooddata-api-client/gooddata_api_client/model/declarative_fact.py b/gooddata-api-client/gooddata_api_client/model/declarative_fact.py index 14f059876..c578af9d8 100644 --- a/gooddata-api-client/gooddata_api_client/model/declarative_fact.py +++ b/gooddata-api-client/gooddata_api_client/model/declarative_fact.py @@ -117,6 +117,8 @@ def openapi_types(): 'is_hidden': (bool,), # noqa: E501 'source_column_data_type': (str,), # noqa: E501 'tags': ([str],), # noqa: E501 + 'is_nullable': (bool,), # noqa: E501 + 'null_value': (str,), # noqa: E501 } @cached_property @@ -132,6 +134,8 @@ def discriminator(): 'is_hidden': 'isHidden', # noqa: E501 'source_column_data_type': 'sourceColumnDataType', # noqa: E501 'tags': 'tags', # noqa: E501 + 'is_nullable': 'isNullable', # noqa: E501 + 'null_value': 'nullValue', # noqa: E501 } read_only_vars = { diff --git a/gooddata-api-client/gooddata_api_client/model/declarative_setting.py b/gooddata-api-client/gooddata_api_client/model/declarative_setting.py index 84229410c..a340cb35a 100644 --- a/gooddata-api-client/gooddata_api_client/model/declarative_setting.py +++ b/gooddata-api-client/gooddata_api_client/model/declarative_setting.py @@ -102,6 +102,7 @@ class DeclarativeSetting(ModelNormal): 'SORT_CASE_SENSITIVE': "SORT_CASE_SENSITIVE", 'METRIC_FORMAT_OVERRIDE': "METRIC_FORMAT_OVERRIDE", 'ENABLE_AI_ON_DATA': "ENABLE_AI_ON_DATA", + 'ENABLE_NULL_JOINS': "ENABLE_NULL_JOINS", }, } diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/declarative_model/physical_model/column.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/declarative_model/physical_model/column.py index 95344c1a8..45ccb6a15 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/declarative_model/physical_model/column.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/data_source/declarative_model/physical_model/column.py @@ -16,6 +16,8 @@ class CatalogDeclarativeColumn(Base): is_primary_key: Optional[bool] = None referenced_table_id: Optional[str] = None referenced_table_column: Optional[str] = None + is_nullable: Optional[bool] = None + null_value: Optional[str] = None @staticmethod def client_class() -> type[DeclarativeColumn]: diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py index 744ca6ea7..6420b241c 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py @@ -79,6 +79,8 @@ class CatalogDeclarativeAttribute(Base): tags: Optional[list[str]] = None is_hidden: Optional[bool] = None locale: Optional[str] = None + is_nullable: Optional[bool] = None + null_value: Optional[str] = None @staticmethod def client_class() -> type[DeclarativeAttribute]: @@ -94,6 +96,8 @@ class CatalogDeclarativeFact(Base): description: Optional[str] = None tags: Optional[list[str]] = None is_hidden: Optional[bool] = None + is_nullable: Optional[bool] = None + null_value: Optional[str] = None @staticmethod def client_class() -> type[DeclarativeFact]: @@ -118,6 +122,8 @@ class CatalogDeclarativeAggregatedFact(Base): source_column_data_type: Optional[str] = None description: Optional[str] = None tags: Optional[list[str]] = None + is_nullable: Optional[bool] = None + null_value: Optional[str] = None @staticmethod def client_class() -> type[DeclarativeAggregatedFact]: @@ -167,6 +173,8 @@ class CatalogDeclarativeLabel(Base): is_hidden: Optional[bool] = None locale: Optional[str] = None translations: Optional[list[CatalogDeclarativeLabelTranslation]] = None + is_nullable: Optional[bool] = None + null_value: Optional[str] = None @staticmethod def client_class() -> type[DeclarativeLabel]: @@ -201,6 +209,8 @@ class CatalogDeclarativeReferenceSource(Base): column: str target: CatalogGrainIdentifier data_type: Optional[str] = None + is_nullable: Optional[bool] = None + null_value: Optional[str] = None @staticmethod def client_class() -> type[DeclarativeReferenceSource]: diff --git a/packages/gooddata-sdk/tests/catalog/test_null_joins_setting.py b/packages/gooddata-sdk/tests/catalog/test_null_joins_setting.py new file mode 100644 index 000000000..771fda06c --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/test_null_joins_setting.py @@ -0,0 +1,36 @@ +# (C) 2024 GoodData Corporation +"""Tests for ENABLE_NULL_JOINS setting support.""" + +import pytest +from gooddata_sdk.catalog.setting import CatalogDeclarativeSetting + + +def test_enable_null_joins_setting(): + """Test that ENABLE_NULL_JOINS setting can be created.""" + setting = CatalogDeclarativeSetting( + id="enable_null_joins_test", + type="ENABLE_NULL_JOINS", + content={"enabled": True} + ) + + assert setting.id == "enable_null_joins_test" + assert setting.type == "ENABLE_NULL_JOINS" + assert setting.content == {"enabled": True} + + # Test conversion to API object + api_obj = setting.to_api() + assert api_obj is not None + + # Test that the API object has the correct class + assert setting.client_class().__name__ == "DeclarativeSetting" + + +def test_enable_null_joins_setting_validation(): + """Test that the ENABLE_NULL_JOINS setting type is valid.""" + # This should not raise a validation error + setting = CatalogDeclarativeSetting( + id="enable_null_joins_validation_test", + type="ENABLE_NULL_JOINS" + ) + + assert setting.type == "ENABLE_NULL_JOINS" \ No newline at end of file diff --git a/packages/gooddata-sdk/tests/catalog/test_nullable_columns.py b/packages/gooddata-sdk/tests/catalog/test_nullable_columns.py new file mode 100644 index 000000000..d5596c209 --- /dev/null +++ b/packages/gooddata-sdk/tests/catalog/test_nullable_columns.py @@ -0,0 +1,137 @@ +# (C) 2024 GoodData Corporation +"""Tests for nullable columns support in declarative models.""" + +import pytest +from gooddata_sdk.catalog.data_source.declarative_model.physical_model.column import CatalogDeclarativeColumn +from gooddata_sdk.catalog.workspace.declarative_model.workspace.logical_model.dataset.dataset import ( + CatalogDeclarativeAttribute, + CatalogDeclarativeFact, + CatalogDeclarativeLabel, + CatalogDeclarativeAggregatedFact, + CatalogDeclarativeReferenceSource, +) +from gooddata_sdk.catalog.identifier import CatalogGrainIdentifier + + +def test_catalog_declarative_column_nullable_fields(): + """Test that CatalogDeclarativeColumn supports nullable fields.""" + column = CatalogDeclarativeColumn( + name="test_column", + data_type="STRING", + is_nullable=True, + null_value="NULL" + ) + + assert column.name == "test_column" + assert column.data_type == "STRING" + assert column.is_nullable is True + assert column.null_value == "NULL" + + # Test default values + column_default = CatalogDeclarativeColumn( + name="default_column", + data_type="INT" + ) + assert column_default.is_nullable is None + assert column_default.null_value is None + + +def test_catalog_declarative_fact_nullable_fields(): + """Test that CatalogDeclarativeFact supports nullable fields.""" + fact = CatalogDeclarativeFact( + id="test_fact", + title="Test Fact", + source_column="test_column", + is_nullable=True, + null_value="0" + ) + + assert fact.id == "test_fact" + assert fact.title == "Test Fact" + assert fact.source_column == "test_column" + assert fact.is_nullable is True + assert fact.null_value == "0" + + +def test_catalog_declarative_attribute_nullable_fields(): + """Test that CatalogDeclarativeAttribute supports nullable fields.""" + attribute = CatalogDeclarativeAttribute( + id="test_attribute", + title="Test Attribute", + source_column="test_column", + labels=[], + is_nullable=True, + null_value="UNKNOWN" + ) + + assert attribute.id == "test_attribute" + assert attribute.title == "Test Attribute" + assert attribute.source_column == "test_column" + assert attribute.is_nullable is True + assert attribute.null_value == "UNKNOWN" + + +def test_catalog_declarative_label_nullable_fields(): + """Test that CatalogDeclarativeLabel supports nullable fields.""" + label = CatalogDeclarativeLabel( + id="test_label", + title="Test Label", + source_column="test_column", + is_nullable=False, + null_value=None + ) + + assert label.id == "test_label" + assert label.title == "Test Label" + assert label.source_column == "test_column" + assert label.is_nullable is False + assert label.null_value is None + + +def test_catalog_declarative_aggregated_fact_nullable_fields(): + """Test that CatalogDeclarativeAggregatedFact supports nullable fields.""" + agg_fact = CatalogDeclarativeAggregatedFact( + id="test_agg_fact", + source_column="test_column", + is_nullable=True, + null_value="0.0" + ) + + assert agg_fact.id == "test_agg_fact" + assert agg_fact.source_column == "test_column" + assert agg_fact.is_nullable is True + assert agg_fact.null_value == "0.0" + + +def test_catalog_declarative_reference_source_nullable_fields(): + """Test that CatalogDeclarativeReferenceSource supports nullable fields.""" + target = CatalogGrainIdentifier(id="target_id", type="attribute") + + ref_source = CatalogDeclarativeReferenceSource( + column="ref_column", + target=target, + is_nullable=True, + null_value="EMPTY" + ) + + assert ref_source.column == "ref_column" + assert ref_source.target == target + assert ref_source.is_nullable is True + assert ref_source.null_value == "EMPTY" + + +def test_api_conversion(): + """Test that the models can convert to API objects.""" + column = CatalogDeclarativeColumn( + name="test_column", + data_type="STRING", + is_nullable=True, + null_value="NULL" + ) + + # This should not raise an error + api_obj = column.to_api() + assert api_obj is not None + + # Test that the API object has the correct class + assert column.client_class().__name__ == "DeclarativeColumn" \ No newline at end of file