Daniele Varrazzo [Wed, 24 Feb 2021 02:05:15 +0000 (03:05 +0100)]
Make the row_factory attribute non-nullable
Added a `tuple_row()` factory for completeness. Note that it returns None, not
a callable, and the row_maker on the Transformer hasn't changed. The
signature of RowFactory now allows that.
This
makes simpler to specify the row_factory option on `conn.cursor()`: None
means default (the connection `row_factory`), specifying `tuple_row()`
overrides it to the normal tuplish behaviour.
Denis Laxalde [Fri, 12 Feb 2021 09:31:30 +0000 (10:31 +0100)]
Add row_factory as connection attribute and connect argument
When passing 'row_factory' to connect(), respective attribute will be
set on the connection instance. This will be used as default at cursor
creation and can be overridden with conn.cursor(row_factory=...) or
conn.execute(row_factory=...).
We use a '_null_row_factory' marker to handle None-value passed to
.cursor() or .execute() for disabling the default row factory.
Daniele Varrazzo [Fri, 12 Feb 2021 02:21:35 +0000 (03:21 +0100)]
Set up row maker and loaders only once in a server-side cursor lifetime
It wasn't happening once per movement, as I was fearing, but it was happening
exactly twice: once on DECLARE, once on describe_portal(). We actually
don't care about the DECLARE result: it was being set on the cursor only
to detect errors, so now that's done manually.
Daniele Varrazzo [Fri, 12 Feb 2021 01:23:16 +0000 (02:23 +0100)]
Don't recalculate loaders when not needed
The case it's not needed is when the new result is guaranteed to have
the same fields as the previous one. This happens querying in single-row
mode and on server-side cursors fetch.
Denis Laxalde [Thu, 11 Feb 2021 11:22:32 +0000 (12:22 +0100)]
Move the row maker as a Transformer attribute
Instead of carrying the row maker (_make_row attribute) on the cursor
and possibly calling it to transform each row in cursor methods, we
define a 'make_row' attribute on Transformer that is possibly used in
load_row() and load_rows().
In the Python implementation of Transformer.load_rows(), we use tuple as
as make_row() when the attribute is unset.
In the Cython implementation, we make 'make_row' a plain property with a
'_make_row' attribute under the hood. We finally transform individual or
list of records using self.make_row().
Denis Laxalde [Thu, 11 Feb 2021 15:15:23 +0000 (16:15 +0100)]
Use no row factory in TypeInfo.fetch*()
TypeInfo.fetch() and TypeInfo.fetch_async() delegate results processing
to TypeInfo._fetch() which only handle results as tuples. Therefore, we
build the cursor with no row factory to guarantee this. Still we need to
add a type hint on 'recs' variable because this cannot be inferred (this
fixes the mypy error introduced in previous commits).
Denis Laxalde [Wed, 10 Feb 2021 16:58:42 +0000 (17:58 +0100)]
Make row factory optional
We change the default value of row_factory argument in
connection.cursor() to None and thus use a keyword argument.
On cursor side, we only set the '_make_row' attribute if a 'row_factory'
got passed and we guard all possible calls to _make_row() by an
'if self._make_row' to avoid a Python call per row. Note that, on the
other hand, we now need to cast 'row' values to the 'Row' type in order
to satisfy type checking.
The default_row_factory() is now useless and thus dropped.
Denis Laxalde [Tue, 9 Feb 2021 15:51:11 +0000 (16:51 +0100)]
Introduce row_factory option in connection.cursor()
We add a row_factory keyword argument in connection.cursor() and cursor
classes that will be used to produce individual rows of the result set.
A RowFactory can be implemented as a class with a __call__ method
accepting raw values and initialized with a cursor instance; the
RowFactory instance is created when results are available. Type
definitions for RowFactory (and its respective RowMaker) are defined as
callback protocols so as to allow user to define a row factory without
the need for writing a class.
Daniele Varrazzo [Fri, 22 Jan 2021 21:30:08 +0000 (22:30 +0100)]
Dump time and timestamp naive and tz-aware with the respective oids
Using the tz version of the data types is ok enough for most uses,
because Postgres can downgrade the type to naive, but ranges don't have
an implicit cast and the exact type of the range subtype is necessary.
Daniele Varrazzo [Thu, 21 Jan 2021 03:05:33 +0000 (04:05 +0100)]
Have a single Range class, not one per subtype
There are two problems here, so this doesn't work. One, which will be
solved, is to have a registry of types attached to an adaptation
context: a TODO in already several places, so this will be solved.
Before that we don't really have a way to find back e.g. the oid of a
custom range on strings starting from Range('a', 'b').
The second problem, more serious, is that Postgres doesn't cast
int4range <-> int8range and tsrange <-> tstzrange. The latter pair can
be solved with a two steps dumper choosing between tz aware and not. The
first I don't have in mind how to solve it: Given Range(1,2) I wouldn't
know if int4 or int8 should be used, and Postgres doesn't seem very
forgiving. Probably we should go unknown.
Daniele Varrazzo [Wed, 20 Jan 2021 12:23:06 +0000 (13:23 +0100)]
Don't require a Transformer roundtrip to choose the specific int dumper
They are static object, they require no customisation with the
connection. I don't expect anyone wanting to subclass them; if they
do they will deal with the whole 5 objects.
Daniele Varrazzo [Wed, 20 Jan 2021 01:46:45 +0000 (02:46 +0100)]
Adapt the dumper used to the value of the objects
Added a second dispatch to allow a dumper to upgrade to a specialised
version. Currently used to dump int to the smallest Postgres type
holding that value and to dump lists of object into typed arrays.
This change allows to write queries more naturally as no ``::int`` cast
should be needed anymore e.g. in date + int or jsonb ->> int.
Only Python implementation; C version to be implemented yet.
Daniele Varrazzo [Sat, 16 Jan 2021 02:06:56 +0000 (03:06 +0100)]
Declare all loaders to receive an object supporting the buffer interface
There isn't in mypy such an object, so just use `Union[bytes, bytearray,
memoryview]`
Avoid a memory copy passing data from the Transformer to the loaders.
I had started exercising this code path by adding copy tests, but after
dropping the extra copy the same path is now exercised by any select, so
I've stopped doing that.
Daniele Varrazzo [Wed, 13 Jan 2021 14:46:49 +0000 (15:46 +0100)]
Choose automatically text or binary format using %s placeholders
A format can be enforced using %b or %t placeholders.
Use binary formats dumpers if available, otherwise use text dumpers.
Python str are an exception: prefer text dumper, because it will dump
with format 0, so Postgres can cast the value to any format.
Special-case empty lists to be always dumped as textual unknown '{}' so
that Postgres can cast them into whatever array it fancies.