Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kcKDY-000574-1m for psycopg@arkaria.postgresql.org; Tue, 10 Nov 2020 03:21:12 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1kcKDX-00067W-0u for psycopg@arkaria.postgresql.org; Tue, 10 Nov 2020 03:21:11 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kcKDW-00067P-Pf for psycopg@lists.postgresql.org; Tue, 10 Nov 2020 03:21:10 +0000 Received: from mail-lj1-x230.google.com ([2a00:1450:4864:20::230]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1kcKDR-0000XG-CW for psycopg@lists.postgresql.org; Tue, 10 Nov 2020 03:21:10 +0000 Received: by mail-lj1-x230.google.com with SMTP id x9so565170ljc.7 for ; Mon, 09 Nov 2020 19:21:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=s/JBTs+9l6HJ+R6gnM7IKoN5v3fvyP35LEWE1DNDyQw=; b=LWGUFo1i6dPE73YWrwzm+fUU8Xi1V9rne8mBAn5ZYPUnQOpa3koe7A8znFH4+MmTtt DVNUivinqX/Cw1kEWyboUYXZefjvGUGVXvnUFHwfKkDY4t0Q6ns29GFs/P99ZN0VJrTK 8wBLN5eY4XsRjZToJxlfFVu5cSzG5f9bE+XfWEgJ81nRMNpGMZKassWSp7+2w19Udmj0 eX2TBWpma7OZ3W2JCErurN9kiHngs7/n7hVuAHWo4+I2rGds24TCM/SmnzbFbZEulpwf XFlHClR4NfSByKKdeSTNBa1K4grtSX3IQKtI5tHeBguyyWANNDbvZrDuY0o+8dpBRbd5 mz8w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=s/JBTs+9l6HJ+R6gnM7IKoN5v3fvyP35LEWE1DNDyQw=; b=dgaucg4zf8Uw69Ts22t8bDy+yNYFQF8NJf61b8jhzuODFluvZw0TlzPrxGKGrUtErk RKGUhNXTkxGvTajXHiiauSvDlHrdGJO3yDI2S1OBIqkVLFulKd2cwOP88nLrcxKXAz6j IV1Z/EBOsXB5Yr8su0hiBds7ffHcXTTk/6SSfwwVhgb8nrVQwWyqpNsJlhJXe66ut+GK y3euz9uJfvRsVvsLcdmsefVnATBeeSWgSEcN7M+6BR8jrV70Arj1LyYQ8YN0wcsKZysn DuSWwSybvknjsd36fEf5wnRmaVnwdu+/bDdjkxnP39zZWZugSQUwIJVpDaCrq8lsiwLw nWdg== X-Gm-Message-State: AOAM532pkbSORomFH0rUjNmkj6QjjcK+aUhZAur3gpcANgADHm3EzydG N3+2LRWDzEWRKAOOif1bK8d4PRDGfdmwLaGyLns= X-Google-Smtp-Source: ABdhPJxYB2Z2jgA5d/MgzV6H625K9PORPIgXW9LtLAh9WGCe0QIKTv7Qb/uWFzz/ZF4SGTsVmbJ4CYy37cxUHkyo2D4= X-Received: by 2002:a2e:96c4:: with SMTP id d4mr6053912ljj.37.1604978463577; Mon, 09 Nov 2020 19:21:03 -0800 (PST) MIME-Version: 1.0 References: <2b9859f0-0964-2baa-b6bc-13f975ae0f67@aklaver.com> <88fb7a7e-a182-a816-c1a7-8a1f54b65215@aklaver.com> <4830fb8d-fa57-e0f8-0e4f-a96ed040dede@dndg.it> <1546497c-ff41-9bd0-b4d7-931bd305caac@dndg.it> In-Reply-To: From: Daniele Varrazzo Date: Tue, 10 Nov 2020 03:20:51 +0000 Message-ID: Subject: Re: psycopg3 and adaptation choices To: Vladimir Ryabtsev Cc: Federico Di Gregorio , psycopg@lists.postgresql.org Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk On Tue, 10 Nov 2020 at 01:06, Vladimir Ryabtsev wrote: > May I ask you again about using 'unknown' for numbers? Could you recap > all the downsides of this approach? After this useful conversation, I've updated the article (https://www.varrazzo.com/blog/2020/11/07/psycopg3-adaptation/) and I've added comparison tables with the choices available. The integer one is the following: Choices to cast Python ``int`` type: To ``unknown``: * +1: can express the full range of the unbounded Python ``int`` * +2: can be passed without cast to most data types and functions * -2: it round-trips back to string, error on PostgreSQL < 10 To ``numeric``: * +1: can express the full range of the unbounded Python ``int`` * +1: can be passed without cast to most data types * -1: requires a cast for some functions * DELETED [-1: it round-trips to 'Decimal'`] * +0.5: **it can round-trip back to int**, with additional care - more to follow To ``int8``: * -0.5: can't express values not fitting in 64 bits (relatively limited use case: if the target column is ``numeric`` then it would be wise for the user to pass a ``Decimal``) * +1: can be passed without cast to most data types * -1: requires a cast for some functions * +1: it round-trips back to ``int`` To ``int4``: * -1: limited range: couldn't be used to pass a value not fitting into 32 bytes from Python to a ``bigint``. * +2: can be passed without cast to most data types and functions * +1: it round-trips back to ``int`` What about the DELETED entry on ``numeric``? If we dump ``int`` -> ``numeric`` to the db, and load back ``numeric`` -> ``Decimal`` from it, we end up with integers round-tripping to ``Decimal``, which could easily create errors in Python contexts which are not ready to deal with fixed-point arithmetic. However, upon receiving a ``numeric`` from the database, we can check what number it is: if it has no decimal digit it can be returned to Python as ``int``, if it has any decimal digit it must be returned as ``Decimal``. This mechanism can be as trivial as `looking if there is a '.'`__ in the data returned by the database; however in many cases the is job made simpler (or at least more efficient) by the presence of the ``numeric`` modifiers: ``numeric`` comes in three flavours: - ``numeric``: arbitrary precision (number of digits) and scale (number of digits after the decimal point), - ``numeric(n)``: limited precision, no digit after the decimal point (equivalent to ``numeric(n, 0)``), - ``numeric(n, m)``: limited precision, fixed number of digits after the decimal point. .. __: https://github.com/psycopg/psycopg3/commit/5ced659f4838cf72c1981518ae2804942ebbd07b The modifier information is returned in many contexts (among which the most important: selecting data from tables): if we know the scale `we can decide upfront`__ to load the entire column as ``int`` if the scale is 0, ``Decimal`` otherwise. If the modifier is unknown we can look at the presence of the dot. .. __: https://github.com/psycopg/psycopg3/commit/a9444144f7d0581284ccab198ad0355436e6822a Returning an ``int`` in a context where ``Decimal`` are expected doesn't seem a big problem: throwing an integer to a fixed-point calculation doesn't wreak havoc has it does throwing a ``Decimal`` in a context of ``flaot`` calculations. Running the entire Django test suite after this change caused no test to fail, which gives me some comfort.