Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement cross-type cast tests for all combinations within JSON as_XYZ() operators #11074

Open
zzzeek opened this issue Mar 1, 2024 · 5 comments
Labels
json things to do with JSON sqlite use case not really a feature or a bug; can be support for new DB features or user use cases not anticipated
Milestone

Comments

@zzzeek
Copy link
Member

zzzeek commented Mar 1, 2024

as_string() should be tested against all of int / bool / float to always return a string on all backends. as_integer() should be tested to always return an int for a JSON int, float as well as a string that contains an integer, using CAST, same for as_float(), as_boolean().

this was never the expected contract for these methods but it is the apparent behavior for most dialects except SQLite right now.

@zzzeek zzzeek added sqlite use case not really a feature or a bug; can be support for new DB features or user use cases not anticipated json things to do with JSON labels Mar 1, 2024
@zzzeek zzzeek added this to the 2.1 milestone Mar 1, 2024
@zzzeek
Copy link
Member Author

zzzeek commented Mar 1, 2024

see also #11065

@zzzeek
Copy link
Member Author

zzzeek commented Mar 1, 2024

consider adding a boolean to the methods "force_cast=True" something like that, indicating this is potentially a type cross-cast

@zzzeek
Copy link
Member Author

zzzeek commented Mar 1, 2024

probably overthinking it though

@zzzeek
Copy link
Member Author

zzzeek commented Mar 1, 2024

here's a test suite we can start with to see if this is even feasible. these tests pass 100% for PostgreSQL, where we are using CAST most consistently. the suite fails for mysql and sqlite as there are many casting combinations not accommodated:

    @testing.combinations(
        ("string",),
        ("integer",),
        ("float",),
        ("numeric",),
        ("boolean",),
        argnames="cross_cast",
    )
    @testing.combinations(
        ("boolean", True, {"string"}),
        ("boolean", False, {"string"}),
        ("boolean", None, {"all"}),
        ("string", "45", {"integer", "float", "numeric"}),
        ("string", "45.684", {"float", "numeric"}),
        ("string", "some string", {"string"}),
        ("string", None, {"all"}),
        ("string", "réve illé", {"string"}),
        ("integer", 15, {"string", "numeric", "float"}),
        ("integer", 1, {"all"}),
        ("integer", 0, {"all"}),
        ("integer", None, {"all"}),
        ("float", None, {"all"}),
        ("float", 1234567.89, {"string", "numeric"}),
        ("numeric", 1234567.89, {"string", "float"}),
        argnames="datatype, value, allowed_targets",
    )
    def test_index_cross_casts(
        self, datatype, value, allowed_targets, cross_cast
    ):
        data_table = self.tables.data_table
        data_element = {"key1": value}

        with config.db.begin() as conn:
            datatype, compare_value, p_s = self._json_value_insert(
                conn, datatype, value, data_element
            )

            expr = data_table.c.data["key1"]

            if cross_cast == "numeric":
                expr = getattr(expr, "as_%s" % cross_cast)(10, 2)
            else:
                expr = getattr(expr, "as_%s" % cross_cast)()

            if (
                cross_cast != datatype
                and "all" not in allowed_targets
                and cross_cast not in allowed_targets
            ):
                with expect_raises(Exception):
                    conn.scalar(select(expr))
                return
            else:
                roundtrip = conn.scalar(select(expr))

            if value is None:
                eq_(roundtrip, None)
            elif cross_cast == "string":
                assert isinstance(roundtrip, str)
            elif cross_cast == "integer":
                assert isinstance(roundtrip, int)
            elif cross_cast == "float":
                assert isinstance(roundtrip, float)
            elif cross_cast == "numeric":
                assert isinstance(roundtrip, decimal.Decimal)
            elif cross_cast == "boolean":
                assert isinstance(roundtrip, bool)
            else:
                assert False

@CaselIT
Copy link
Member

CaselIT commented Mar 1, 2024

We can probably also add

("string", "true", {"boolean"}),
("string", "false", {"boolean"}),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
json things to do with JSON sqlite use case not really a feature or a bug; can be support for new DB features or user use cases not anticipated
Projects
None yet
Development

No branches or pull requests

2 participants