Repository: duckdb/duckdb
File: tools/pythonpkg/tests/fast/pandas/test_df_object_resolution.py (Python)

Code before:
        pandas.testing.assert_frame_equal(duckdb_col, converted_col)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_list_correct(self, pandas, duckdb_cursor):
        x = pandas.DataFrame([{'0': [[5], [34], [-245]]}])
        duckdb_col = duckdb_cursor.sql("select [[5], [34], [-245]] as '0'").df()
        converted_col = duckdb_cursor.sql("select * from x").df()
        duckdb_cursor.sql("drop view if exists tbl")
        pandas.testing.assert_frame_equal(duckdb_col, converted_col)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_list_contains_null(self, pandas, duckdb_cursor):
        x = pandas.DataFrame([{'0': [[5], None, [-245]]}])
        duckdb_col = duckdb_cursor.sql("select [[5], NULL, [-245]] as '0'").df()
        converted_col = duckdb_cursor.sql("select * from x").df()
        duckdb_cursor.sql("drop view if exists tbl")
        pandas.testing.assert_frame_equal(duckdb_col, converted_col)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_list_starts_with_null(self, pandas, duckdb_cursor):
        x = pandas.DataFrame([{'0': [None, [5], [-245]]}])
        duckdb_col = duckdb_cursor.sql("select [NULL, [5], [-245]] as '0'").df()
        converted_col = duckdb_cursor.sql("select * from x").df()
        duckdb_cursor.sql("drop view if exists tbl")
        pandas.testing.assert_frame_equal(duckdb_col, converted_col)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_list_value_upgrade(self, pandas, duckdb_cursor):
        x = pandas.DataFrame([{'0': [['5'], [34], [-245]]}])
        duckdb_rel = duckdb_cursor.sql("select [['5'], ['34'], ['-245']] as '0'")
        duckdb_col = duckdb_rel.df()
        converted_col = duckdb_cursor.sql("select * from x").df()
        pandas.testing.assert_frame_equal(duckdb_col, converted_col)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_list_column_value_upgrade(self, pandas, duckdb_cursor):
        x = pandas.DataFrame(
            [
                [[1, 25, 300]],
                [[500, 345, 30]],
                [[50, 'a', 67]],
            ]
        )
        x.rename(columns={0: 'a'}, inplace=True)
        converted_col = duckdb_cursor.sql("select * from x").df()
        duckdb_cursor.sql(
            """
            CREATE TABLE tmp3(
                a VARCHAR[]
            );
        """

Review comment (suggestion):
@Mytherin I added this test, it contains some behavior tests for incompatible struct schemas inside nested types

(List, struct and map)

Code after:
        duckdb_col = duckdb_cursor.sql("SELECT {key: NULL, value: NULL} as '0'").df()
        pandas.testing.assert_frame_equal(duckdb_col, converted_col)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_map_fallback_nullkey(self, pandas, duckdb_cursor):
        x = pandas.DataFrame([[{'a': 4, None: 0, 'c': 4}], [{'a': 4, None: 0, 'd': 4}]])
        with pytest.raises(
            duckdb.InvalidInputException, match="Dict->Map conversion failed because 'key' list contains None"
        ):
            converted_col = duckdb_cursor.sql("select * from x").df()

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_map_fallback_nullkey_coverage(self, pandas, duckdb_cursor):
        x = pandas.DataFrame(
            [
                [{'key': None, 'value': None}],
                [{'key': None, None: 5}],
            ]
        )
        with pytest.raises(
            duckdb.InvalidInputException, match="Dict->Map conversion failed because 'key' list contains None"
        ):
            converted_col = duckdb_cursor.sql("select * from x").df()

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])
    def test_structs_in_nested_types(self, pandas, duckdb_cursor):
        # This test is testing a bug that occurred when type upgrades occurred inside nested types
        # STRUCT(key1 varchar) + STRUCT(key1 varchar, key2 varchar) turns into MAP
        # But when inside a nested structure, this upgrade did not happen properly

        pairs = {
            'v1': ObjectPair({'key1': 21}, {'key1': 21, 'key2': 42}),
            'v2': ObjectPair({'key1': 21}, {'key2': 21}),
            'v3': ObjectPair({'key1': 21, 'key2': 42}, {'key1': 21}),
            'v4': ObjectPair({}, {'key1': 21}),
        }

        for _, pair in pairs.items():
            check_struct_upgrade('MAP(VARCHAR, INTEGER)[]', construct_list, pair, pandas, duckdb_cursor)

        for key, pair in pairs.items():
            if key == 'v4':
                expected_type = 'MAP(VARCHAR, MAP(VARCHAR, INTEGER))'
            else:
                expected_type = 'STRUCT(v1 MAP(VARCHAR, INTEGER))'
            check_struct_upgrade(expected_type, construct_struct, pair, pandas, duckdb_cursor)

        for key, pair in pairs.items():
            check_struct_upgrade('MAP(VARCHAR, MAP(VARCHAR, INTEGER))', construct_map, pair, pandas, duckdb_cursor)

    @pytest.mark.parametrize('pandas', [NumpyPandas(), ArrowPandas()])