diff --git a/docs/api/types.rst b/docs/api/types.rst index 136d93a4..15264f24 100644 --- a/docs/api/types.rst +++ b/docs/api/types.rst @@ -22,7 +22,7 @@ The table below shows the correspondence between EdgeDB and Python types. | ``anytuple`` | :py:class:`edgedb.Tuple` or | | | :py:class:`edgedb.NamedTuple` | +----------------------------+-----------------------------------------------------+ -| ``anyenum`` | :py:class:`str ` | +| ``anyenum`` | :py:class:`edgedb.EnumValue` | +----------------------------+-----------------------------------------------------+ | ``Object`` | :py:class:`edgedb.Object` | +----------------------------+-----------------------------------------------------+ @@ -81,20 +81,7 @@ Sets .. py:class:: Set() - A representation of an immutable set of values returned by a query. - - The :py:meth:`AsyncIOClient.query() ` and - :py:meth:`Client.query() ` - methods return an instance of this type. Nested sets in the result are - also returned as ``Set`` objects. - - .. describe:: len(s) - - Return the number of fields in set *s*. - - .. describe:: iter(s) - - Return an iterator over the *values* of the set *s*. + This is :py:class:`list ` since version 1.0. .. _edgedb-python-types-object: @@ -106,6 +93,19 @@ Objects An immutable representation of an object instance returned from a query. + .. versionchanged:: 1.0 + + ``edgedb.Object`` instances are dataclass-compatible since version 1.0, + for example, ``dataclasses.is_dataclass()`` will return ``True``, and + ``dataclasses.asdict()`` will work on ``edgedb.Object`` instances. + + .. versionchanged:: 1.0 + + ``edgedb.Object.__hash__`` is just ``object.__hash__` in version 1.0. + Similarly, ``==`` is equivalent to the ``is`` operator comparing + ``edgedb.Object`` instances, and ``<``, ``<=``, ``>``, ``>=`` are not + allowed on ``edgedb.Object`` instances. + The value of an object property or a link can be accessed through a corresponding attribute: @@ -181,24 +181,7 @@ Tuples .. py:class:: Tuple() - An immutable value representing an EdgeDB tuple value. - - Instances of ``edgedb.Tuple`` generally behave exactly like - standard Python tuples: - - .. code-block:: pycon - - >>> import edgedb - >>> client = edgedb.create_client() - >>> r = client.query_single('''SELECT (1, 'a', [3])''') - >>> r - (1, 'a', [3]) - >>> len(r) - 3 - >>> r[1] - 'a' - >>> r == (1, 'a', [3]) - True + This is :py:class:`tuple ` since version 1.0. Named Tuples @@ -208,6 +191,12 @@ Named Tuples An immutable value representing an EdgeDB named tuple value. + .. versionchanged:: 1.0 + + ``edgedb.NamedTuple`` is a subclass of :py:class:`tuple ` + and is duck-type compatible with ``collections.namedtuple`` since + version 1.0. + Instances of ``edgedb.NamedTuple`` generally behave similarly to :py:func:`namedtuple `: @@ -224,6 +213,8 @@ Named Tuples 1 >>> r == (1, 'a', [3]) True + >>> r._fields + ('a', 'b', 'c') Arrays @@ -231,28 +222,15 @@ Arrays .. py:class:: Array() - An immutable value representing an EdgeDB array value. + This is :py:class:`list ` since version 1.0. - .. code-block:: pycon - - >>> import edgedb - >>> client = edgedb.create_client() - >>> r = client.query_single('''SELECT [1, 2, 3]''') - >>> r - [1, 2, 3] - >>> len(r) - 3 - >>> r[1] - 2 - >>> r == [1, 2, 3] - True RelativeDuration ================ .. py:class:: RelativeDuration() - An immutable value represeting an EdgeDB ``cal::relative_duration`` value. + An immutable value representing an EdgeDB ``cal::relative_duration`` value. .. code-block:: pycon @@ -274,7 +252,7 @@ DateDuration .. py:class:: DateDuration() - An immutable value represeting an EdgeDB ``cal::date_duration`` value. + An immutable value representing an EdgeDB ``cal::date_duration`` value. .. code-block:: pycon @@ -287,3 +265,32 @@ DateDuration 12 >>> r.days 2 + + +EnumValue +========= + +.. py:class:: EnumValue() + + An immutable value representing an EdgeDB enum value. + + .. versionchanged:: 1.0 + + Since version 1.0, ``edgedb.EnumValue`` is a subclass of + :py:class:`enum.Enum `. Actual enum values are + instances of ad-hoc enum classes created by the codecs to represent + the actual members defined in your EdgeDB schema. + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single("""SELECT 'red'""") + >>> r + + >>> str(r) + 'red' + >>> r.value # added in 1.0 + 'red' + >>> r.name # added in 1.0, simply str.upper() of r.value + 'RED' diff --git a/edgedb/__init__.py b/edgedb/__init__.py index 1123a15f..7d20ef83 100644 --- a/edgedb/__init__.py +++ b/edgedb/__init__.py @@ -21,11 +21,11 @@ from ._version import __version__ -from edgedb.datatypes import Range from edgedb.datatypes.datatypes import ( Tuple, NamedTuple, EnumValue, RelativeDuration, DateDuration, ConfigMemory ) from edgedb.datatypes.datatypes import Set, Object, Array, Link, LinkSet +from edgedb.datatypes.range import Range from .abstract import ( Executor, AsyncIOExecutor, ReadOnlyExecutor, AsyncIOReadOnlyExecutor diff --git a/edgedb/abstract.py b/edgedb/abstract.py index cf4a3c5d..b119e005 100644 --- a/edgedb/abstract.py +++ b/edgedb/abstract.py @@ -2,7 +2,6 @@ import typing from . import options -from .datatypes import datatypes from .protocol import protocol __all__ = ( @@ -103,7 +102,7 @@ class ReadOnlyExecutor(BaseReadOnlyExecutor): def _query(self, query_context: QueryContext): ... - def query(self, query: str, *args, **kwargs) -> datatypes.Set: + def query(self, query: str, *args, **kwargs) -> list: return self._query(QueryContext( query=QueryWithArgs(query, args, kwargs), cache=self._get_query_cache(), @@ -186,7 +185,7 @@ class AsyncIOReadOnlyExecutor(BaseReadOnlyExecutor): async def _query(self, query_context: QueryContext): ... - async def query(self, query: str, *args, **kwargs) -> datatypes.Set: + async def query(self, query: str, *args, **kwargs) -> list: return await self._query(QueryContext( query=QueryWithArgs(query, args, kwargs), cache=self._get_query_cache(), diff --git a/edgedb/datatypes/__init__.py b/edgedb/datatypes/__init__.py index bb7c59ed..73609285 100644 --- a/edgedb/datatypes/__init__.py +++ b/edgedb/datatypes/__init__.py @@ -15,6 +15,3 @@ # See the License for the specific language governing permissions and # limitations under the License. # - - -from .range import Range # noqa diff --git a/edgedb/datatypes/array.c b/edgedb/datatypes/array.c deleted file mode 100644 index ede55253..00000000 --- a/edgedb/datatypes/array.c +++ /dev/null @@ -1,264 +0,0 @@ -/* -* This source file is part of the EdgeDB open source project. -* -* Copyright 2016-present MagicStack Inc. and the EdgeDB authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - - -#include "datatypes.h" -#include "freelist.h" -#include "internal.h" - - -static int init_type_called = 0; -static Py_hash_t base_hash = -1; - - -EDGE_SETUP_FREELIST( - EDGE_ARRAY, - EdgeArrayObject, - EDGE_ARRAY_FREELIST_MAXSAVE, - EDGE_ARRAY_FREELIST_SIZE) - - -#define EdgeArray_GET_ITEM(op, i) \ - (((EdgeArrayObject *)(op))->ob_item[i]) -#define EdgeArray_SET_ITEM(op, i, v) \ - (((EdgeArrayObject *)(op))->ob_item[i] = v) - - -PyObject * -EdgeArray_New(Py_ssize_t size) -{ - assert(init_type_called); - - EdgeArrayObject *obj = NULL; - - EDGE_NEW_WITH_FREELIST(EDGE_ARRAY, EdgeArrayObject, - &EdgeArray_Type, obj, size) - assert(obj != NULL); - assert(EdgeArray_Check(obj)); - assert(Py_SIZE(obj) == size); - - obj->cached_hash = -1; - obj->weakreflist = NULL; - - PyObject_GC_Track(obj); - return (PyObject *)obj; -} - - -int -EdgeArray_SetItem(PyObject *ob, Py_ssize_t i, PyObject *el) -{ - assert(EdgeArray_Check(ob)); - EdgeArrayObject *o = (EdgeArrayObject *)ob; - assert(i >= 0); - assert(i < Py_SIZE(o)); - Py_INCREF(el); - EdgeArray_SET_ITEM(o, i, el); - return 0; -} - - -static void -array_dealloc(EdgeArrayObject *o) -{ - o->cached_hash = -1; - PyObject_GC_UnTrack(o); - if (o->weakreflist != NULL) { - PyObject_ClearWeakRefs((PyObject*)o); - } - Py_TRASHCAN_SAFE_BEGIN(o) - EDGE_DEALLOC_WITH_FREELIST(EDGE_ARRAY, EdgeArrayObject, o); - Py_TRASHCAN_SAFE_END(o) -} - - -static Py_hash_t -array_hash(EdgeArrayObject *o) -{ - if (o->cached_hash == -1) { - o->cached_hash = _EdgeGeneric_HashWithBase( - base_hash, o->ob_item, Py_SIZE(o)); - } - return o->cached_hash; -} - - -static int -array_traverse(EdgeArrayObject *o, visitproc visit, void *arg) -{ - Py_ssize_t i; - for (i = Py_SIZE(o); --i >= 0;) { - if (o->ob_item[i] != NULL) { - Py_VISIT(o->ob_item[i]); - } - } - return 0; -} - - -static PyObject * -array_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - PyObject *iterable = NULL; - EdgeArrayObject *o; - - if (type != &EdgeArray_Type) { - PyErr_BadInternalCall(); - return NULL; - } - - if (!_Edge_NoKeywords("edgedb.Array", kwargs) || - !PyArg_UnpackTuple(args, "edgedb.Array", 0, 1, &iterable)) - { - return NULL; - } - - if (iterable == NULL) { - return EdgeArray_New(0); - } - - PyObject *tup = PySequence_Tuple(iterable); - if (tup == NULL) { - return NULL; - } - - o = (EdgeArrayObject *)EdgeArray_New(Py_SIZE(tup)); - if (o == NULL) { - Py_DECREF(tup); - return NULL; - } - - for (Py_ssize_t i = 0; i < Py_SIZE(tup); i++) { - PyObject *el = PyTuple_GET_ITEM(tup, i); - Py_INCREF(el); - EdgeArray_SET_ITEM(o, i, el); - } - Py_DECREF(tup); - return (PyObject *)o; -} - - -static Py_ssize_t -array_length(EdgeArrayObject *o) -{ - return Py_SIZE(o); -} - - -static PyObject * -array_getitem(EdgeArrayObject *o, Py_ssize_t i) -{ - if (i < 0 || i >= Py_SIZE(o)) { - PyErr_SetString(PyExc_IndexError, "array index out of range"); - return NULL; - } - PyObject *el = EdgeArray_GET_ITEM(o, i); - Py_INCREF(el); - return el; -} - - -static PyObject * -array_richcompare(EdgeArrayObject *v, PyObject *w, int op) -{ - if (EdgeArray_Check(w)) { - return _EdgeGeneric_RichCompareValues( - v->ob_item, Py_SIZE(v), - ((EdgeArrayObject *)w)->ob_item, Py_SIZE(w), - op); - } - - if (PyList_CheckExact(w)) { - return _EdgeGeneric_RichCompareValues( - v->ob_item, Py_SIZE(v), - _PyList_ITEMS(w), Py_SIZE(w), - op); - } - - Py_RETURN_NOTIMPLEMENTED; -} - - -static PyObject * -array_repr(EdgeArrayObject *o) -{ - _PyUnicodeWriter writer; - _PyUnicodeWriter_Init(&writer); - writer.overallocate = 1; - - if (_PyUnicodeWriter_WriteChar(&writer, '[') < 0) { - goto error; - } - - if (_EdgeGeneric_RenderValues(&writer, - (PyObject *)o, o->ob_item, Py_SIZE(o)) < 0) - { - goto error; - } - - if (_PyUnicodeWriter_WriteChar(&writer, ']') < 0) { - goto error; - } - - return _PyUnicodeWriter_Finish(&writer); - -error: - _PyUnicodeWriter_Dealloc(&writer); - return NULL; -} - - -static PySequenceMethods array_as_sequence = { - .sq_length = (lenfunc)array_length, - .sq_item = (ssizeargfunc)array_getitem, -}; - - -PyTypeObject EdgeArray_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "edgedb.Array", - .tp_basicsize = sizeof(EdgeArrayObject) - sizeof(PyObject *), - .tp_itemsize = sizeof(PyObject *), - .tp_dealloc = (destructor)array_dealloc, - .tp_as_sequence = &array_as_sequence, - .tp_hash = (hashfunc)array_hash, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)array_traverse, - .tp_new = array_new, - .tp_richcompare = (richcmpfunc)array_richcompare, - .tp_free = PyObject_GC_Del, - .tp_repr = (reprfunc)array_repr, - .tp_weaklistoffset = offsetof(EdgeArrayObject, weakreflist), -}; - - -PyObject * -EdgeArray_InitType(void) -{ - if (PyType_Ready(&EdgeArray_Type) < 0) { - return NULL; - } - - base_hash = _EdgeGeneric_HashString("edgedb.Array"); - if (base_hash == -1) { - return NULL; - } - - init_type_called = 1; - return (PyObject *)&EdgeArray_Type; -} diff --git a/edgedb/datatypes/datatypes.h b/edgedb/datatypes/datatypes.h index 54e24dbc..bf5ad82e 100644 --- a/edgedb/datatypes/datatypes.h +++ b/edgedb/datatypes/datatypes.h @@ -86,28 +86,6 @@ PyObject * EdgeRecordDesc_List(PyObject *, uint8_t, uint8_t); PyObject * EdgeRecordDesc_GetDataclassFields(PyObject *); - -/* === edgedb.Tuple ========================================= */ - -#define EDGE_TUPLE_FREELIST_SIZE 500 -#define EDGE_TUPLE_FREELIST_MAXSAVE 20 - -extern PyTypeObject EdgeTuple_Type; - -#define EdgeTuple_Check(d) (Py_TYPE(d) == &EdgeTuple_Type) - -typedef struct { - PyObject_VAR_HEAD - PyObject *weakreflist; - PyObject *ob_item[1]; -} EdgeTupleObject; - -PyObject * EdgeTuple_InitType(void); -PyObject * EdgeTuple_New(Py_ssize_t size); -int EdgeTuple_SetItem(PyObject *, Py_ssize_t, PyObject *); - - - /* === edgedb.NamedTuple ==================================== */ #define EDGE_NAMEDTUPLE_FREELIST_SIZE 500 @@ -115,18 +93,9 @@ int EdgeTuple_SetItem(PyObject *, Py_ssize_t, PyObject *); extern PyTypeObject EdgeNamedTuple_Type; -#define EdgeNamedTuple_Check(d) (Py_TYPE(d) == &EdgeNamedTuple_Type) - -typedef struct { - PyObject_VAR_HEAD - PyObject *desc; - PyObject *weakreflist; - PyObject *ob_item[1]; -} EdgeNamedTupleObject; - PyObject * EdgeNamedTuple_InitType(void); +PyObject * EdgeNamedTuple_Type_New(PyObject *); PyObject * EdgeNamedTuple_New(PyObject *); -int EdgeNamedTuple_SetItem(PyObject *, Py_ssize_t, PyObject *); /* === edgedb.Object ======================================== */ @@ -156,50 +125,6 @@ PyObject * EdgeObject_GetItem(PyObject *, Py_ssize_t); PyObject * EdgeObject_GetID(PyObject *ob); -/* === edgedb.Set =========================================== */ - -extern PyTypeObject EdgeSet_Type; - -#define EdgeSet_Check(d) (Py_TYPE(d) == &EdgeSet_Type) - -typedef struct { - PyObject_HEAD - Py_hash_t cached_hash; - PyObject *weakreflist; - PyObject *els; -} EdgeSetObject; - -PyObject * EdgeSet_InitType(void); -PyObject * EdgeSet_New(Py_ssize_t); - -int EdgeSet_SetItem(PyObject *, Py_ssize_t, PyObject *); -PyObject * EdgeSet_GetItem(PyObject *, Py_ssize_t); - -int EdgeSet_AppendItem(PyObject *, PyObject *); -Py_ssize_t EdgeSet_Len(PyObject *); - - -/* === edgedb.Array ========================================= */ - -#define EDGE_ARRAY_FREELIST_SIZE 500 -#define EDGE_ARRAY_FREELIST_MAXSAVE 10 - -extern PyTypeObject EdgeArray_Type; - -#define EdgeArray_Check(d) (Py_TYPE(d) == &EdgeArray_Type) - -typedef struct { - PyObject_VAR_HEAD - PyObject *weakreflist; - Py_hash_t cached_hash; - PyObject *ob_item[1]; -} EdgeArrayObject; - -PyObject * EdgeArray_InitType(void); -PyObject * EdgeArray_New(Py_ssize_t size); -int EdgeArray_SetItem(PyObject *, Py_ssize_t, PyObject *); - - /* === edgedb.Link ========================================== */ extern PyTypeObject EdgeLink_Type; diff --git a/edgedb/datatypes/datatypes.pxd b/edgedb/datatypes/datatypes.pxd index 12de4318..12fd0769 100644 --- a/edgedb/datatypes/datatypes.pxd +++ b/edgedb/datatypes/datatypes.pxd @@ -23,7 +23,6 @@ cimport cpython include "./relative_duration.pxd" include "./date_duration.pxd" -include "./enum.pxd" include "./config_memory.pxd" @@ -47,29 +46,15 @@ cdef extern from "datatypes.h": EdgeFieldCardinality EdgeRecordDesc_PointerCardinality( object, Py_ssize_t pos) - object EdgeTuple_InitType() - object EdgeTuple_New(Py_ssize_t) - int EdgeTuple_SetItem(object, Py_ssize_t, object) except -1 - object EdgeNamedTuple_InitType() object EdgeNamedTuple_New(object) - int EdgeNamedTuple_SetItem(object, Py_ssize_t, object) except -1 + object EdgeNamedTuple_Type_New(object) object EdgeObject_InitType() object EdgeObject_New(object); int EdgeObject_SetItem(object, Py_ssize_t, object) except -1 object EdgeObject_GetRecordDesc(object) - bint EdgeSet_Check(object) - object EdgeSet_InitType() - object EdgeSet_New(Py_ssize_t); - int EdgeSet_SetItem(object, Py_ssize_t, object) except -1 - int EdgeSet_AppendItem(object, object) except -1 - - object EdgeArray_InitType() - object EdgeArray_New(Py_ssize_t); - int EdgeArray_SetItem(object, Py_ssize_t, object) except -1 - object EdgeLink_InitType() object EdgeLinkSet_InitType() @@ -78,15 +63,7 @@ cdef extern from "datatypes.h": cdef record_desc_new(object names, object flags, object cards) cdef record_desc_pointer_name(object desc, Py_ssize_t pos) cdef record_desc_pointer_card(object desc, Py_ssize_t pos) -cdef tuple_new(Py_ssize_t size) -cdef tuple_set(object tuple, Py_ssize_t pos, object elem) -cdef namedtuple_new(object desc) -cdef namedtuple_set(object tuple, Py_ssize_t pos, object elem) +cdef namedtuple_new(object namedtuple_type) +cdef namedtuple_type_new(object desc) cdef object_new(object desc) cdef object_set(object tuple, Py_ssize_t pos, object elem) -cdef bint set_check(object set) -cdef set_new(Py_ssize_t size) -cdef set_set(object set, Py_ssize_t pos, object elem) -cdef set_append(object set, object elem) -cdef array_new(Py_ssize_t size) -cdef array_set(object array, Py_ssize_t pos, object elem) diff --git a/edgedb/datatypes/datatypes.pyx b/edgedb/datatypes/datatypes.pyx index d77e511d..4faa20ec 100644 --- a/edgedb/datatypes/datatypes.pyx +++ b/edgedb/datatypes/datatypes.pyx @@ -27,11 +27,11 @@ include "./config_memory.pyx" _RecordDescriptor = EdgeRecordDesc_InitType() -Tuple = EdgeTuple_InitType() +Tuple = tuple NamedTuple = EdgeNamedTuple_InitType() Object = EdgeObject_InitType() -Set = EdgeSet_InitType() -Array = EdgeArray_InitType() +Set = list +Array = list Link = EdgeLink_InitType() LinkSet = EdgeLinkSet_InitType() @@ -46,8 +46,11 @@ def get_object_descriptor(obj): def create_object_factory(**pointers): + import dataclasses + flags = () names = () + fields = {} for pname, ptype in pointers.items(): names += (pname,) @@ -68,9 +71,14 @@ def create_object_factory(**pointers): raise ValueError(f'unknown pointer type {pt}') flags += (flag,) + field = dataclasses.field() + field.name = pname + field._field_type = dataclasses._FIELD + fields[pname] = field desc = EdgeRecordDesc_New(names, flags, NULL) size = len(pointers) + desc.set_dataclass_fields_func(lambda: fields) def factory(*items): if len(items) != size: @@ -97,20 +105,12 @@ cdef record_desc_pointer_card(object desc, Py_ssize_t pos): return EdgeRecordDesc_PointerCardinality(desc, pos) -cdef tuple_new(Py_ssize_t size): - return EdgeTuple_New(size) - - -cdef tuple_set(object tuple, Py_ssize_t pos, object elem): - EdgeTuple_SetItem(tuple, pos, elem) - - -cdef namedtuple_new(object desc): - return EdgeNamedTuple_New(desc) +cdef namedtuple_new(object namedtuple_type): + return EdgeNamedTuple_New(namedtuple_type) -cdef namedtuple_set(object tuple, Py_ssize_t pos, object elem): - EdgeNamedTuple_SetItem(tuple, pos, elem) +cdef namedtuple_type_new(object desc): + return EdgeNamedTuple_Type_New(desc) cdef object_new(object desc): @@ -119,27 +119,3 @@ cdef object_new(object desc): cdef object_set(object obj, Py_ssize_t pos, object elem): EdgeObject_SetItem(obj, pos, elem) - - -cdef bint set_check(object set): - return EdgeSet_Check(set) - - -cdef set_new(Py_ssize_t size): - return EdgeSet_New(size) - - -cdef set_set(object set, Py_ssize_t pos, object elem): - EdgeSet_SetItem(set, pos, elem) - - -cdef set_append(object set, object elem): - EdgeSet_AppendItem(set, elem) - - -cdef array_new(Py_ssize_t size): - return EdgeArray_New(size) - - -cdef array_set(object array, Py_ssize_t pos, object elem): - EdgeArray_SetItem(array, pos, elem) diff --git a/edgedb/datatypes/enum.pxd b/edgedb/datatypes/enum.pxd deleted file mode 100644 index ce2cc3ce..00000000 --- a/edgedb/datatypes/enum.pxd +++ /dev/null @@ -1,34 +0,0 @@ -# -# This source file is part of the EdgeDB open source project. -# -# Copyright 2019-present MagicStack Inc. and the EdgeDB authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - - -cdef class EnumDescriptor: - cdef: - object tid - dict index - tuple labels - - cdef get_index(self, EnumValue v) - - -cdef class EnumValue: - cdef: - EnumDescriptor desc - str label - - cdef get_index(self) diff --git a/edgedb/datatypes/enum.pyx b/edgedb/datatypes/enum.pyx index 010153e6..a3fa6705 100644 --- a/edgedb/datatypes/enum.pyx +++ b/edgedb/datatypes/enum.pyx @@ -16,81 +16,44 @@ # limitations under the License. # +import enum -cdef class EnumDescriptor: - def __init__(self, object tid, tuple labels): - self.tid = tid - index = {} - for i, l in enumerate(labels): - index[l] = i - self.index = index - self.labels = labels - - cdef get_index(self, EnumValue v): - return self.index[v.label] - - -cdef class EnumValue: - - def __init__(self, EnumDescriptor desc, str label): - self.desc = desc - self.label = label - - cdef get_index(self): - return self.desc.get_index(self) +class EnumValue(enum.Enum): def __str__(self): - return self.label + return self._value_ def __repr__(self): - return f'' - - property __tid__: - def __get__(self): - return self.desc.tid - - def __eq__(self, other): - if not isinstance(other, EnumValue): - return NotImplemented - if self.desc.tid != (other).desc.tid: - return NotImplemented - return self.label == (other).label - - def __ne__(self, other): - if not isinstance(other, EnumValue): - return NotImplemented - if self.desc.tid != (other).desc.tid: - return NotImplemented - return self.label != (other).label + return f'' def __lt__(self, other): if not isinstance(other, EnumValue): return NotImplemented - if self.desc.tid != (other).desc.tid: + if self.__tid__ != other.__tid__: return NotImplemented - return self.get_index() < (other).get_index() + return self._index_ < other._index_ def __gt__(self, other): if not isinstance(other, EnumValue): return NotImplemented - if self.desc.tid != (other).desc.tid: + if self.__tid__ != other.__tid__: return NotImplemented - return self.get_index() > (other).get_index() + return self._index_ > other._index_ def __le__(self, other): if not isinstance(other, EnumValue): return NotImplemented - if self.desc.tid != (other).desc.tid: + if self.__tid__ != other.__tid__: return NotImplemented - return self.get_index() <= (other).get_index() + return self._index_ <= other._index_ def __ge__(self, other): if not isinstance(other, EnumValue): return NotImplemented - if self.desc.tid != (other).desc.tid: + if self.__tid__ != other.__tid__: return NotImplemented - return self.get_index() >= (other).get_index() + return self._index_ >= other._index_ def __hash__(self): - return hash((self.desc.tid, self.label)) + return hash((self.__tid__, self._value_)) diff --git a/edgedb/datatypes/hash.c b/edgedb/datatypes/hash.c index 83e40ac5..e6070d4c 100644 --- a/edgedb/datatypes/hash.c +++ b/edgedb/datatypes/hash.c @@ -14,9 +14,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -* -* Portions Copyright 2001-2018 Python Software Foundation. -* See also https://github.com/python/cpython/blob/master/LICENSE. */ #include "Python.h" @@ -24,83 +21,6 @@ #include "datatypes.h" -#if PY_VERSION_HEX >= 0x03080000 - -#if SIZEOF_PY_UHASH_T > 4 -#define _PyHASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL) -#define _PyHASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL) -#define _PyHASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL) -#define _PyHASH_XXROTATE(x) ((x << 31) | (x >> 33)) /* Rotate left 31 bits */ -#else -#define _PyHASH_XXPRIME_1 ((Py_uhash_t)2654435761UL) -#define _PyHASH_XXPRIME_2 ((Py_uhash_t)2246822519UL) -#define _PyHASH_XXPRIME_5 ((Py_uhash_t)374761393UL) -#define _PyHASH_XXROTATE(x) ((x << 13) | (x >> 19)) /* Rotate left 13 bits */ -#endif - -Py_hash_t -_EdgeGeneric_Hash(PyObject **els, Py_ssize_t len) -{ - /* Python's tuple hash algorithm. Hashes of edgedb.Tuple and - edgedb.NamedTuple must be equal to hashes of Python't tuples - with the same elements */ - - Py_ssize_t i; - Py_uhash_t acc = _PyHASH_XXPRIME_5; - for (i = 0; i < len; i++) { - Py_uhash_t lane = PyObject_Hash(els[i]); - if (lane == (Py_uhash_t)-1) { - return -1; - } - acc += lane * _PyHASH_XXPRIME_2; - acc = _PyHASH_XXROTATE(acc); - acc *= _PyHASH_XXPRIME_1; - } - - /* Add input length, mangled to keep the historical value of hash(()). */ - acc += len ^ (_PyHASH_XXPRIME_5 ^ 3527539UL); - - if (acc == (Py_uhash_t)-1) { - return 1546275796; - } - return (Py_hash_t)acc; -} - -#else - -Py_hash_t -_EdgeGeneric_Hash(PyObject **els, Py_ssize_t len) -{ - /* Python's tuple hash algorithm. Hashes of edgedb.Tuple and - edgedb.NamedTuple must be equal to hashes of Python't tuples - with the same elements */ - - Py_uhash_t x; /* Unsigned for defined overflow behavior. */ - PyObject **p = els; - Py_hash_t y; - Py_uhash_t mult; - - mult = _PyHASH_MULTIPLIER; - x = 0x345678UL; - while (--len >= 0) { - y = PyObject_Hash(*p++); - if (y == -1) { - return -1; - } - x = (x ^ (Py_uhash_t)y) * mult; - /* the cast might truncate len; that doesn't change hash stability */ - mult += (Py_uhash_t)(82520UL + (size_t)len + (size_t)len); - } - x += 97531UL; - if (x == (Py_uhash_t)-1) { - x = (Py_uhash_t)-2; - } - return (Py_hash_t)x; -} - -#endif - - Py_hash_t _EdgeGeneric_HashString(const char *s) { @@ -113,31 +33,3 @@ _EdgeGeneric_HashString(const char *s) Py_DECREF(o); return res; } - - -Py_hash_t -_EdgeGeneric_HashWithBase(Py_hash_t base_hash, PyObject **els, Py_ssize_t len) -{ - /* Roughly equivalent to calling `hash((base_hash, *els))` in Python */ - - assert(base_hash != -1); - - Py_hash_t els_hash = _EdgeGeneric_Hash(els, len); - if (els_hash == -1) { - return -1; - } - - Py_uhash_t x = 0x345678UL; - Py_uhash_t mult = _PyHASH_MULTIPLIER; - - x = (x ^ (Py_uhash_t)base_hash) * mult; - mult += (Py_uhash_t)(82520UL + (size_t)4); - x = (x ^ (Py_uhash_t)els_hash) * mult; - x += 97531UL; - - Py_hash_t res = (Py_hash_t)x; - if (res == -1) { - res = -2; - } - return res; -} diff --git a/edgedb/datatypes/internal.h b/edgedb/datatypes/internal.h index 67963b41..c6754f60 100644 --- a/edgedb/datatypes/internal.h +++ b/edgedb/datatypes/internal.h @@ -28,18 +28,8 @@ #include "datatypes.h" -#define _Edge_IsContainer(o) \ - (EdgeTuple_Check(o) || \ - EdgeNamedTuple_Check(o) || \ - EdgeObject_Check(o) || \ - EdgeSet_Check(o) || \ - EdgeArray_Check(o)) - - int _Edge_NoKeywords(const char *, PyObject *); -Py_hash_t _EdgeGeneric_Hash(PyObject **, Py_ssize_t); -Py_hash_t _EdgeGeneric_HashWithBase(Py_hash_t, PyObject **, Py_ssize_t); Py_hash_t _EdgeGeneric_HashString(const char *); PyObject * _EdgeGeneric_RenderObject(PyObject *obj); @@ -64,4 +54,12 @@ PyObject * _EdgeGeneric_RichCompareValues(PyObject **, Py_ssize_t, # define _PyList_ITEMS(op) (_PyList_CAST(op)->ob_item) #endif +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 8 +# define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_BEGIN(op, dealloc) +# define CPy_TRASHCAN_END(op) Py_TRASHCAN_END +#else +# define CPy_TRASHCAN_BEGIN(op, dealloc) Py_TRASHCAN_SAFE_BEGIN(op) +# define CPy_TRASHCAN_END(op) Py_TRASHCAN_SAFE_END(op) +#endif + #endif diff --git a/edgedb/datatypes/linkset.c b/edgedb/datatypes/linkset.c index 5e166657..c6db6ef6 100644 --- a/edgedb/datatypes/linkset.c +++ b/edgedb/datatypes/linkset.c @@ -39,11 +39,11 @@ EdgeLinkSet_New(PyObject *name, PyObject *source, PyObject *targets) return NULL; } - if (!EdgeSet_Check(targets)) { + if (!PyList_Check(targets)) { PyErr_SetString( PyExc_TypeError, "cannot construct a Link object; targets is expected " - "to be an edgedb.Set"); + "to be a list"); return NULL; } @@ -172,13 +172,12 @@ linkset_repr(EdgeLinkSetObject *o) goto error; } - for (Py_ssize_t i = 0; i < EdgeSet_Len(o->targets); i++) { - PyObject *el = EdgeSet_GetItem(o->targets, i); + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(o->targets); i++) { + PyObject *el = PyList_GET_ITEM(o->targets, i); PyObject *item_repr = NULL; if (EdgeObject_Check(el)) { /* should always be the case */ PyObject *id = EdgeObject_GetID(el); - Py_DECREF(el); if (id == NULL) { goto error; } @@ -187,7 +186,6 @@ linkset_repr(EdgeLinkSetObject *o) } else { item_repr = _EdgeGeneric_RenderObject(el); - Py_DECREF(el); } if (item_repr == NULL) { @@ -200,7 +198,7 @@ linkset_repr(EdgeLinkSetObject *o) } Py_DECREF(item_repr); - if (i < EdgeSet_Len(o->targets) - 1) { + if (i < PyList_GET_SIZE(o->targets) - 1) { if (_PyUnicodeWriter_WriteASCIIString(&writer, ", ", 2) < 0) { goto error; } @@ -279,21 +277,20 @@ linkset_richcompare(EdgeLinkSetObject *v, EdgeLinkSetObject *w, int op) static Py_ssize_t linkset_length(EdgeLinkSetObject *o) { - assert(EdgeSet_Check(o->targets)); - return EdgeSet_Len(o->targets); + assert(PyList_Check(o->targets)); + return PyList_GET_SIZE(o->targets); } static PyObject * linkset_getitem(EdgeLinkSetObject *o, Py_ssize_t i) { - PyObject *target = EdgeSet_GetItem(o->targets, i); + PyObject *target = PyList_GetItem(o->targets, i); if (target == NULL) { return NULL; } PyObject *link = EdgeLink_New(o->name, o->source, target); - Py_DECREF(target); return link; } diff --git a/edgedb/datatypes/namedtuple.c b/edgedb/datatypes/namedtuple.c index b6cc3571..ea009529 100644 --- a/edgedb/datatypes/namedtuple.c +++ b/edgedb/datatypes/namedtuple.c @@ -20,35 +20,38 @@ #include "datatypes.h" #include "freelist.h" #include "internal.h" +#include "structmember.h" EDGE_SETUP_FREELIST( EDGE_NAMED_TUPLE, - EdgeNamedTupleObject, + PyTupleObject, EDGE_NAMEDTUPLE_FREELIST_MAXSAVE, EDGE_NAMEDTUPLE_FREELIST_SIZE) -#define EdgeNamedTuple_GET_ITEM(op, i) \ - (((EdgeNamedTupleObject *)(op))->ob_item[i]) -#define EdgeNamedTuple_SET_ITEM(op, i, v) \ - (((EdgeNamedTupleObject *)(op))->ob_item[i] = v) +#define EdgeNamedTuple_Type_DESC(type) \ + *(PyObject **)(((char *)type) + Py_TYPE(type)->tp_basicsize) static int init_type_called = 0; PyObject * -EdgeNamedTuple_New(PyObject *desc) +EdgeNamedTuple_New(PyObject *type) { assert(init_type_called); + PyObject *desc = EdgeNamedTuple_Type_DESC(type); if (desc == NULL || !EdgeRecordDesc_Check(desc)) { PyErr_BadInternalCall(); return NULL; } Py_ssize_t size = EdgeRecordDesc_GetSize(desc); + if (size < 0) { + return NULL; + } if (size > EDGE_MAX_TUPLE_SIZE) { PyErr_Format( @@ -58,64 +61,72 @@ EdgeNamedTuple_New(PyObject *desc) return NULL; } - EdgeNamedTupleObject *nt = NULL; - EDGE_NEW_WITH_FREELIST(EDGE_NAMED_TUPLE, EdgeNamedTupleObject, - &EdgeNamedTuple_Type, nt, size); + PyTupleObject *nt = NULL; + // Expand the new_with_freelist because we need to incref the type + if ( + _EDGE_NAMED_TUPLE_FL_MAX_SAVE_SIZE && + size < _EDGE_NAMED_TUPLE_FL_MAX_SAVE_SIZE && + (nt = _EDGE_NAMED_TUPLE_FL[size]) != NULL + ) { + if (size == 0) { + Py_INCREF(nt); + } else { + _EDGE_NAMED_TUPLE_FL[size] = (PyTupleObject *) nt->ob_item[0]; + _EDGE_NAMED_TUPLE_FL_NUM_FREE[size]--; + _Py_NewReference((PyObject *)nt); + Py_INCREF(type); + Py_TYPE(nt) = type; + } + } else { + if ( + (size_t)size > ( + (size_t)PY_SSIZE_T_MAX - sizeof(PyTupleObject *) - sizeof(PyObject *) + ) / sizeof(PyObject *) + ) { + PyErr_NoMemory(); + return NULL; + } + nt = PyObject_GC_NewVar(PyTupleObject, type, size); + if (nt == NULL) { + return NULL; + } +#if PY_VERSION_HEX < 0x03080000 + // Workaround for Python issue 35810; no longer necessary in Python 3.8 + Py_INCREF(type); +#endif + } assert(nt != NULL); - assert(EdgeNamedTuple_Check(nt)); assert(Py_SIZE(nt) == size); - nt->weakreflist = NULL; - - Py_INCREF(desc); - nt->desc = desc; - + for (Py_ssize_t i = 0; i < size; i++) { + nt->ob_item[i] = NULL; + } PyObject_GC_Track(nt); return (PyObject *)nt; } -int -EdgeNamedTuple_SetItem(PyObject *ob, Py_ssize_t i, PyObject *el) -{ - assert(EdgeNamedTuple_Check(ob)); - EdgeNamedTupleObject *o = (EdgeNamedTupleObject *)ob; - assert(i >= 0); - assert(i < Py_SIZE(o)); - Py_INCREF(el); - EdgeNamedTuple_SET_ITEM(o, i, el); - return 0; -} - - static void -namedtuple_dealloc(EdgeNamedTupleObject *o) +namedtuple_dealloc(PyTupleObject *o) { + PyTypeObject *tp; PyObject_GC_UnTrack(o); - if (o->weakreflist != NULL) { - PyObject_ClearWeakRefs((PyObject*)o); - } - Py_CLEAR(o->desc); - Py_TRASHCAN_SAFE_BEGIN(o) - EDGE_DEALLOC_WITH_FREELIST(EDGE_NAMED_TUPLE, EdgeNamedTupleObject, o); - Py_TRASHCAN_SAFE_END(o) -} - - -static Py_hash_t -namedtuple_hash(EdgeNamedTupleObject *v) -{ - return _EdgeGeneric_Hash(v->ob_item, Py_SIZE(v)); + CPy_TRASHCAN_BEGIN(o, namedtuple_dealloc) + tp = Py_TYPE(o); + EDGE_DEALLOC_WITH_FREELIST(EDGE_NAMED_TUPLE, PyTupleObject, o); + Py_DECREF(tp); + CPy_TRASHCAN_END(o) } static int -namedtuple_traverse(EdgeNamedTupleObject *o, visitproc visit, void *arg) +namedtuple_traverse(PyTupleObject *o, visitproc visit, void *arg) { - Py_VISIT(o->desc); - - Py_ssize_t i; - for (i = Py_SIZE(o); --i >= 0;) { +#if PY_VERSION_HEX >= 0x03090000 + // This was not needed before Python 3.9 (Python issue 35810 and 40217) + Py_VISIT(Py_TYPE(o)); +#endif + for (Py_ssize_t i = Py_SIZE(o); --i >= 0;) { if (o->ob_item[i] != NULL) { Py_VISIT(o->ob_item[i]); } @@ -124,16 +135,19 @@ namedtuple_traverse(EdgeNamedTupleObject *o, visitproc visit, void *arg) } +static PyObject * +namedtuple_derived_new(PyTypeObject *type, PyObject *args, PyObject *kwargs); + + static PyObject * namedtuple_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - EdgeNamedTupleObject *o = NULL; + PyTupleObject *o = NULL; PyObject *keys_tup = NULL; PyObject *kwargs_iter = NULL; PyObject *desc = NULL; if (type != &EdgeNamedTuple_Type) { - PyErr_BadInternalCall(); - goto fail; + return namedtuple_derived_new(type, args, kwargs); } if (args != NULL && PyTuple_GET_SIZE(args) > 0) { @@ -185,7 +199,10 @@ namedtuple_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { goto fail; } - o = (EdgeNamedTupleObject *)EdgeNamedTuple_New(desc); + type = EdgeNamedTuple_Type_New(desc); + o = (PyTupleObject *)EdgeNamedTuple_New(type); + Py_CLEAR(type); // the type is now referenced by the object + if (o == NULL) { goto fail; } @@ -204,7 +221,7 @@ namedtuple_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { } } Py_INCREF(val); - EdgeNamedTuple_SET_ITEM(o, i, val); + PyTuple_SET_ITEM(o, i, val); } Py_CLEAR(keys_tup); @@ -220,11 +237,88 @@ namedtuple_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { static PyObject * -namedtuple_getattr(EdgeNamedTupleObject *o, PyObject *name) +namedtuple_derived_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { + PyTupleObject *o = (PyTupleObject *)EdgeNamedTuple_New(type); + if (o == NULL) { + goto fail; + } + + PyObject *desc = EdgeNamedTuple_Type_DESC(type); + Py_ssize_t size = EdgeRecordDesc_GetSize(desc); + if (size < 0) { + goto fail; + } + Py_ssize_t args_size = 0; + PyObject *val; + + if (args != NULL) { + args_size = PyTuple_GET_SIZE(args); + if (args_size > size) { + PyErr_Format( + PyExc_ValueError, + "edgedb.NamedTuple only needs %zd arguments, %zd given", + size, args_size); + goto fail; + } + for (Py_ssize_t i = 0; i < args_size; i++) { + val = PyTuple_GET_ITEM(args, i); + Py_INCREF(val); + PyTuple_SET_ITEM(o, i, val); + } + } + if (kwargs == NULL || !PyDict_CheckExact(kwargs)) { + if (size == args_size) { + return (PyObject *)o; + } else { + PyErr_Format( + PyExc_ValueError, + "edgedb.NamedTuple requires %zd arguments, %zd given", + size, args_size); + goto fail; + } + } + if (PyDict_Size(kwargs) > size - args_size) { + PyErr_SetString( + PyExc_ValueError, + "edgedb.NamedTuple got extra keyword arguments"); + goto fail; + } + for (Py_ssize_t i = args_size; i < size; i++) { + PyObject *key = EdgeRecordDesc_PointerName(desc, i); + if (key == NULL) { + goto fail; + } + PyObject *val = PyDict_GetItem(kwargs, key); + if (val == NULL) { + if (PyErr_Occurred()) { + Py_CLEAR(key); + goto fail; + } else { + PyErr_Format( + PyExc_ValueError, + "edgedb.NamedTuple missing required argument: %U", + key); + Py_CLEAR(key); + goto fail; + } + } + Py_CLEAR(key); + Py_INCREF(val); + PyTuple_SET_ITEM(o, i, val); + } + return (PyObject *)o; +fail: + Py_CLEAR(o); + return NULL; +} + + +static PyObject * +namedtuple_getattr(PyTupleObject *o, PyObject *name) { Py_ssize_t pos; edge_attr_lookup_t ret = EdgeRecordDesc_Lookup( - (PyObject *)o->desc, name, &pos); + EdgeNamedTuple_Type_DESC(Py_TYPE(o)), name, &pos); switch (ret) { case L_ERROR: return NULL; @@ -239,7 +333,7 @@ namedtuple_getattr(EdgeNamedTupleObject *o, PyObject *name) return NULL; case L_PROPERTY: { - PyObject *val = EdgeNamedTuple_GET_ITEM(o, pos); + PyObject *val = PyTuple_GET_ITEM(o, pos); Py_INCREF(val); return val; } @@ -250,50 +344,8 @@ namedtuple_getattr(EdgeNamedTupleObject *o, PyObject *name) } -static Py_ssize_t -namedtuple_length(EdgeNamedTupleObject *o) -{ - return Py_SIZE(o); -} - - -static PyObject * -namedtuple_getitem(EdgeNamedTupleObject *o, Py_ssize_t i) -{ - if (i < 0 || i >= Py_SIZE(o)) { - PyErr_SetString(PyExc_IndexError, "namedtuple index out of range"); - return NULL; - } - PyObject *el = EdgeNamedTuple_GET_ITEM(o, i); - Py_INCREF(el); - return el; -} - - -static PyObject * -namedtuple_richcompare(EdgeNamedTupleObject *v, - PyObject *w, int op) -{ - if (EdgeNamedTuple_Check(w)) { - return _EdgeGeneric_RichCompareValues( - v->ob_item, Py_SIZE(v), - ((EdgeNamedTupleObject *)w)->ob_item, Py_SIZE(w), - op); - } - - if (PyTuple_CheckExact(w)) { - return _EdgeGeneric_RichCompareValues( - v->ob_item, Py_SIZE(v), - ((PyTupleObject *)w)->ob_item, Py_SIZE(w), - op); - } - - Py_RETURN_NOTIMPLEMENTED; -} - - static PyObject * -namedtuple_repr(EdgeNamedTupleObject *o) +namedtuple_repr(PyTupleObject *o) { _PyUnicodeWriter writer; _PyUnicodeWriter_Init(&writer); @@ -304,7 +356,8 @@ namedtuple_repr(EdgeNamedTupleObject *o) } if (_EdgeGeneric_RenderItems(&writer, - (PyObject *)o, o->desc, + (PyObject *)o, + EdgeNamedTuple_Type_DESC(Py_TYPE(o)), o->ob_item, Py_SIZE(o), 0, 0) < 0) { goto error; @@ -323,10 +376,10 @@ namedtuple_repr(EdgeNamedTupleObject *o) static PyObject * -namedtuple_dir(EdgeNamedTupleObject *o, PyObject *args) +namedtuple_dir(PyTupleObject *o, PyObject *args) { return EdgeRecordDesc_List( - o->desc, + EdgeNamedTuple_Type_DESC(Py_TYPE(o)), 0xFF, 0xFF); } @@ -338,35 +391,104 @@ static PyMethodDef namedtuple_methods[] = { }; -static PySequenceMethods namedtuple_as_sequence = { - .sq_length = (lenfunc)namedtuple_length, - .sq_item = (ssizeargfunc)namedtuple_getitem, +// This is not a property on namedtuple objects, we are only using this member +// to allocate additional space on the **type object** to store a fast-access +// pointer to the `desc`. It's not visible to users with a Py_SIZE hack below. +static PyMemberDef namedtuple_members[] = { + {"__desc__", T_OBJECT_EX, 0, READONLY}, + {NULL} /* Sentinel */ +}; + + +static PyType_Slot namedtuple_slots[] = { + {Py_tp_traverse, (traverseproc)namedtuple_traverse}, + {Py_tp_dealloc, (destructor)namedtuple_dealloc}, + {Py_tp_members, namedtuple_members}, + {0, 0} +}; + + +static PyType_Spec namedtuple_spec = { + "edgedb.DerivedNamedTuple", + sizeof(PyTupleObject) - sizeof(PyObject *), + sizeof(PyObject *), + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + namedtuple_slots, }; PyTypeObject EdgeNamedTuple_Type = { PyVarObject_HEAD_INIT(NULL, 0) "edgedb.NamedTuple", - .tp_basicsize = sizeof(EdgeNamedTupleObject) - sizeof(PyObject *), + .tp_basicsize = sizeof(PyTupleObject) - sizeof(PyObject *), .tp_itemsize = sizeof(PyObject *), - .tp_methods = namedtuple_methods, - .tp_dealloc = (destructor)namedtuple_dealloc, - .tp_as_sequence = &namedtuple_as_sequence, - .tp_hash = (hashfunc)namedtuple_hash, - .tp_getattro = (getattrofunc)namedtuple_getattr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_richcompare = (richcmpfunc)namedtuple_richcompare, - .tp_traverse = (traverseproc)namedtuple_traverse, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_new = namedtuple_new, - .tp_free = PyObject_GC_Del, .tp_repr = (reprfunc)namedtuple_repr, - .tp_weaklistoffset = offsetof(EdgeNamedTupleObject, weakreflist), + .tp_methods = namedtuple_methods, + .tp_getattro = (getattrofunc)namedtuple_getattr, }; +PyObject * +EdgeNamedTuple_Type_New(PyObject *desc) +{ + assert(init_type_called); + + if (desc == NULL || !EdgeRecordDesc_Check(desc)) { + PyErr_BadInternalCall(); + return NULL; + } + + PyTypeObject *rv = (PyTypeObject *)PyType_FromSpecWithBases( + &namedtuple_spec, PyTuple_Pack(1, &EdgeNamedTuple_Type) + ); + if (rv == NULL) { + return NULL; + } + + // The tp_members give the type object extra space to store `desc` pointer + assert(Py_TYPE(rv)->tp_itemsize > sizeof(PyObject *)); + EdgeNamedTuple_Type_DESC(rv) = desc; // store the fast-access pointer + + Py_SIZE(rv) = 0; // hack the size so the member is not visible to user + + // desc is also stored in tp_dict for refcount. + if (PyDict_SetItemString(rv->tp_dict, "__desc__", desc) < 0) { + goto fail; + } + + // store `_fields` for collections.namedtuple duck-typing + Py_ssize_t size = EdgeRecordDesc_GetSize(desc); + PyTupleObject *fields = PyTuple_New(size); + if (fields == NULL) { + goto fail; + } + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *name = EdgeRecordDesc_PointerName(desc, i); + if (name == NULL) { + Py_CLEAR(fields); + goto fail; + } + PyTuple_SET_ITEM(fields, i, name); + } + if (PyDict_SetItemString(rv->tp_dict, "_fields", fields) < 0) { + goto fail; + } + + return rv; + +fail: + Py_DECREF(rv); + return NULL; +} + + PyObject * EdgeNamedTuple_InitType(void) { + EdgeNamedTuple_Type.tp_base = &PyTuple_Type; + if (PyType_Ready(&EdgeNamedTuple_Type) < 0) { return NULL; } diff --git a/edgedb/datatypes/object.c b/edgedb/datatypes/object.c index 83e91073..09263ffc 100644 --- a/edgedb/datatypes/object.c +++ b/edgedb/datatypes/object.c @@ -23,7 +23,6 @@ static int init_type_called = 0; -static Py_hash_t base_hash = -1; EDGE_SETUP_FREELIST( @@ -175,17 +174,6 @@ object_traverse(EdgeObject *o, visitproc visit, void *arg) } -static Py_hash_t -object_hash(EdgeObject *o) -{ - if (o->cached_hash == -1) { - o->cached_hash = _EdgeGeneric_HashWithBase( - base_hash, o->ob_item, Py_SIZE(o)); - } - return o->cached_hash; -} - - static PyObject * object_getattr(EdgeObject *o, PyObject *name) { @@ -250,7 +238,7 @@ object_getitem(EdgeObject *o, PyObject *name) case L_LINK: { PyObject *val = EdgeObject_GET_ITEM(o, pos); - if (EdgeSet_Check(val)) { + if (PyList_Check(val)) { return EdgeLinkSet_New(name, (PyObject *)o, val); } else if (val == Py_None) { @@ -268,36 +256,6 @@ object_getitem(EdgeObject *o, PyObject *name) } -static PyObject * -object_richcompare(EdgeObject *v, EdgeObject *w, int op) -{ - if (!EdgeObject_Check(v) || !EdgeObject_Check(w)) { - Py_RETURN_NOTIMPLEMENTED; - } - - Py_ssize_t v_id_pos = EdgeRecordDesc_IDPos(v->desc); - Py_ssize_t w_id_pos = EdgeRecordDesc_IDPos(w->desc); - - if (v_id_pos < 0 || w_id_pos < 0 || - v_id_pos >= Py_SIZE(v) || w_id_pos >= Py_SIZE(w)) - { - PyErr_SetString( - PyExc_TypeError, "invalid object ID field offset"); - return NULL; - } - - PyObject *v_id = EdgeObject_GET_ITEM(v, v_id_pos); - PyObject *w_id = EdgeObject_GET_ITEM(w, w_id_pos); - - Py_INCREF(v_id); - Py_INCREF(w_id); - PyObject *ret = PyObject_RichCompare(v_id, w_id, op); - Py_DECREF(v_id); - Py_DECREF(w_id); - return ret; -} - - static PyObject * object_repr(EdgeObject *o) { @@ -355,12 +313,10 @@ PyTypeObject EdgeObject_Type = { .tp_basicsize = sizeof(EdgeObject) - sizeof(PyObject *), .tp_itemsize = sizeof(PyObject *), .tp_dealloc = (destructor)object_dealloc, - .tp_hash = (hashfunc)object_hash, .tp_methods = object_methods, .tp_as_mapping = &object_as_mapping, .tp_getattro = (getattrofunc)object_getattr, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_richcompare = (richcmpfunc)object_richcompare, .tp_traverse = (traverseproc)object_traverse, .tp_free = PyObject_GC_Del, .tp_repr = (reprfunc)object_repr, @@ -385,11 +341,6 @@ EdgeObject_InitType(void) EdgeObject_Type.tp_dict, "__dataclass_fields__", default_fields ); - base_hash = _EdgeGeneric_HashString("edgedb.Object"); - if (base_hash == -1) { - return NULL; - } - init_type_called = 1; return (PyObject *)&EdgeObject_Type; } diff --git a/edgedb/datatypes/set.c b/edgedb/datatypes/set.c deleted file mode 100644 index cee915cc..00000000 --- a/edgedb/datatypes/set.c +++ /dev/null @@ -1,340 +0,0 @@ -/* -* This source file is part of the EdgeDB open source project. -* -* Copyright 2016-present MagicStack Inc. and the EdgeDB authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - - -#include "datatypes.h" -#include "internal.h" - - -static int init_type_called = 0; -static Py_hash_t base_hash = -1; - - -PyObject * -EdgeSet_New(Py_ssize_t size) -{ - assert(init_type_called); - - PyObject *l = PyList_New(size); - if (l == NULL) { - return NULL; - } - - EdgeSetObject *o = PyObject_GC_New(EdgeSetObject, &EdgeSet_Type); - if (o == NULL) { - Py_DECREF(l); - return NULL; - } - - o->els = l; - o->cached_hash = -1; - o->weakreflist = NULL; - - PyObject_GC_Track(o); - return (PyObject *)o; -} - - -int -EdgeSet_SetItem(PyObject *ob, Py_ssize_t pos, PyObject *el) -{ - assert(EdgeSet_Check(ob)); - EdgeSetObject *o = (EdgeSetObject *)ob; - Py_INCREF(el); - return PyList_SetItem(o->els, pos, el); -} - - -PyObject * -EdgeSet_GetItem(PyObject *ob, Py_ssize_t pos) -{ - assert(EdgeSet_Check(ob)); - EdgeSetObject *o = (EdgeSetObject *)ob; - PyObject *el = PyList_GetItem(o->els, pos); - Py_XINCREF(el); - return el; -} - - -int -EdgeSet_AppendItem(PyObject *ob, PyObject *el) -{ - assert(EdgeSet_Check(ob)); - EdgeSetObject *o = (EdgeSetObject *)ob; - return PyList_Append(o->els, el); -} - -Py_ssize_t -EdgeSet_Len(PyObject *ob) -{ - assert(EdgeSet_Check(ob)); - EdgeSetObject *o = (EdgeSetObject *)ob; - return PyList_GET_SIZE(o->els); -} - - -static PyObject * -set_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - if (args == NULL || - PyTuple_Size(args) != 1 || - (kwds != NULL && PyDict_Size(kwds))) - { - PyErr_SetString( - PyExc_TypeError, - "edgedb.Set accepts only one positional argument"); - return NULL; - } - - EdgeSetObject *o = (EdgeSetObject *)EdgeSet_New(0); - if (o == NULL) { - return NULL; - } - - PyObject *res = _PyList_Extend((PyListObject *)o->els, - PyTuple_GET_ITEM(args, 0)); - if (res == NULL) { - Py_DECREF(o); - return NULL; - } - Py_DECREF(res); - - return (PyObject *)o; -} - - -static int -set_traverse(EdgeSetObject *o, visitproc visit, void *arg) -{ - Py_VISIT(o->els); - return 0; -} - - -static void -set_dealloc(EdgeSetObject *o) -{ - PyObject_GC_UnTrack(o); - if (o->weakreflist != NULL) { - PyObject_ClearWeakRefs((PyObject*)o); - } - Py_TRASHCAN_SAFE_BEGIN(o) - o->cached_hash = -1; - Py_CLEAR(o->els); - Py_TRASHCAN_SAFE_END(o) - Py_TYPE(o)->tp_free((PyObject *)o); -} - - -static Py_hash_t -set_hash(EdgeSetObject *o) -{ - if (o->cached_hash == -1) { - o->cached_hash = _EdgeGeneric_HashWithBase( - base_hash, - _PyList_ITEMS(o->els), - PyList_GET_SIZE(o->els)); - } - return o->cached_hash; -} - - -static Py_ssize_t -set_length(EdgeSetObject *o) -{ - return PyList_GET_SIZE(o->els); -} - - -static PyObject * -set_getitem(EdgeSetObject *o, Py_ssize_t i) -{ - if (i < 0 || i >= PyList_GET_SIZE(o->els)) { - PyErr_SetString(PyExc_IndexError, "edgedb.Set index out of range"); - return NULL; - } - PyObject *val = PyList_GetItem(o->els, i); - Py_INCREF(val); - return val; -} - - -static PyObject * -set_richcompare(EdgeSetObject *v, PyObject *ww, int op) -{ - if (op != Py_EQ && op != Py_NE) { - goto not_imp; - } - - if (PyList_CheckExact(ww)) { - return PyObject_RichCompare(v->els, ww, op); - } - - if (!EdgeSet_Check(ww)) { - goto not_imp; - } - - EdgeSetObject *w = (EdgeSetObject *)ww; - - int res = -1; - Py_ssize_t vlen = PyList_Size(v->els); - - if (vlen != PyList_Size(w->els)) { - res = 0; - goto done; - } - - if (vlen == 1) { - res = PyObject_RichCompareBool(v->els, w->els, Py_EQ); - if (res < 0) { - return NULL; - } - goto done; - } - - PyObject *left = NULL; - PyObject *right = NULL; - - left = PyList_GetSlice(v->els, 0, vlen); - if (left == NULL) { - goto error; - } - - right = PyList_GetSlice(w->els, 0, vlen); - if (right == NULL) { - goto error; - } - - if (PyList_Sort(left) < 0) { - goto error; - } - - if (PyList_Sort(right) < 0) { - goto error; - } - - res = PyObject_RichCompareBool(left, right, Py_EQ); - Py_CLEAR(left); - Py_CLEAR(right); - if (res < 0) { - goto error; - } - goto done; - -error: - Py_XDECREF(left); - Py_XDECREF(right); - return NULL; - -not_imp: - Py_RETURN_NOTIMPLEMENTED; - -done: - assert(res != -1); - - if (op == Py_NE) { - res = !res; - } - - if (res) { - Py_RETURN_TRUE; - } - else { - Py_RETURN_FALSE; - } -} - - -static PyObject * -set_iter(EdgeSetObject *o) -{ - return Py_TYPE(o->els)->tp_iter(o->els); -} - - -static PyObject * -set_repr(EdgeSetObject *o) -{ - _PyUnicodeWriter writer; - _PyUnicodeWriter_Init(&writer); - writer.overallocate = 1; - - if (_PyUnicodeWriter_WriteASCIIString(&writer, "Set{", 4) < 0) { - goto error; - } - - if (_EdgeGeneric_RenderValues(&writer, (PyObject *)o, - _PyList_ITEMS(o->els), - PyList_GET_SIZE(o->els)) < 0) - { - goto error; - } - - if (_PyUnicodeWriter_WriteChar(&writer, '}') < 0) { - goto error; - } - - return _PyUnicodeWriter_Finish(&writer); - -error: - _PyUnicodeWriter_Dealloc(&writer); - return NULL; -} - - - -static PySequenceMethods set_as_sequence = { - .sq_length = (lenfunc)set_length, - .sq_item = (ssizeargfunc)set_getitem, -}; - - -PyTypeObject EdgeSet_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "edgedb.Set", - .tp_basicsize = sizeof(EdgeSetObject), - .tp_dealloc = (destructor)set_dealloc, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)set_traverse, - .tp_new = set_tp_new, - .tp_hash = (hashfunc)set_hash, - .tp_as_sequence = &set_as_sequence, - .tp_richcompare = (richcmpfunc)set_richcompare, - .tp_iter = (getiterfunc)set_iter, - .tp_repr = (reprfunc)set_repr, - .tp_free = PyObject_GC_Del, - .tp_weaklistoffset = offsetof(EdgeSetObject, weakreflist), -}; - - -PyObject * -EdgeSet_InitType(void) -{ - if (PyType_Ready(&EdgeSet_Type) < 0) { - return NULL; - } - - base_hash = _EdgeGeneric_HashString("edgedb.Set"); - if (base_hash == -1) { - return NULL; - } - - init_type_called = 1; - return (PyObject *)&EdgeSet_Type; -} diff --git a/edgedb/datatypes/tuple.c b/edgedb/datatypes/tuple.c deleted file mode 100644 index a02e925d..00000000 --- a/edgedb/datatypes/tuple.c +++ /dev/null @@ -1,260 +0,0 @@ -/* -* This source file is part of the EdgeDB open source project. -* -* Copyright 2016-present MagicStack Inc. and the EdgeDB authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - - -#include "datatypes.h" -#include "freelist.h" -#include "internal.h" - - -EDGE_SETUP_FREELIST( - EDGE_TUPLE, - EdgeTupleObject, - EDGE_TUPLE_FREELIST_MAXSAVE, - EDGE_TUPLE_FREELIST_SIZE) - - -#define EdgeTuple_GET_ITEM(op, i) \ - (((EdgeTupleObject *)(op))->ob_item[i]) -#define EdgeTuple_SET_ITEM(op, i, v) \ - (((EdgeTupleObject *)(op))->ob_item[i] = v) - - -static int init_type_called = 0; - - -PyObject * -EdgeTuple_New(Py_ssize_t size) -{ - assert(init_type_called); - - if (size > EDGE_MAX_TUPLE_SIZE) { - PyErr_Format( - PyExc_ValueError, - "Cannot create Tuple with more than %d elements", - EDGE_MAX_TUPLE_SIZE); - return NULL; - } - - EdgeTupleObject *obj = NULL; - - EDGE_NEW_WITH_FREELIST(EDGE_TUPLE, EdgeTupleObject, - &EdgeTuple_Type, obj, size) - assert(obj != NULL); - assert(EdgeTuple_Check(obj)); - assert(Py_SIZE(obj) == size); - - obj->weakreflist = NULL; - - PyObject_GC_Track(obj); - return (PyObject *)obj; -} - - -int -EdgeTuple_SetItem(PyObject *ob, Py_ssize_t i, PyObject *el) -{ - assert(EdgeTuple_Check(ob)); - EdgeTupleObject *o = (EdgeTupleObject *)ob; - assert(i >= 0); - assert(i < Py_SIZE(o)); - Py_INCREF(el); - EdgeTuple_SET_ITEM(o, i, el); - return 0; -} - - -static void -tuple_dealloc(EdgeTupleObject *o) -{ - PyObject_GC_UnTrack(o); - if (o->weakreflist != NULL) { - PyObject_ClearWeakRefs((PyObject*)o); - } - Py_TRASHCAN_SAFE_BEGIN(o) - EDGE_DEALLOC_WITH_FREELIST(EDGE_TUPLE, EdgeTupleObject, o); - Py_TRASHCAN_SAFE_END(o) -} - - -static Py_hash_t -tuple_hash(EdgeTupleObject *o) -{ - return _EdgeGeneric_Hash(o->ob_item, Py_SIZE(o)); -} - - -static int -tuple_traverse(EdgeTupleObject *o, visitproc visit, void *arg) -{ - Py_ssize_t i; - for (i = Py_SIZE(o); --i >= 0;) { - if (o->ob_item[i] != NULL) { - Py_VISIT(o->ob_item[i]); - } - } - return 0; -} - - -static PyObject * -tuple_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - PyObject *iterable = NULL; - EdgeTupleObject *o; - - if (type != &EdgeTuple_Type) { - PyErr_BadInternalCall(); - return NULL; - } - - if (!_Edge_NoKeywords("edgedb.Tuple", kwargs) || - !PyArg_UnpackTuple(args, "edgedb.Tuple", 0, 1, &iterable)) - { - return NULL; - } - - if (iterable == NULL) { - return EdgeTuple_New(0); - } - - PyObject *tup = PySequence_Tuple(iterable); - if (tup == NULL) { - return NULL; - } - - o = (EdgeTupleObject *)EdgeTuple_New(Py_SIZE(tup)); - if (o == NULL) { - Py_DECREF(tup); - return NULL; - } - - for (Py_ssize_t i = 0; i < Py_SIZE(tup); i++) { - PyObject *el = PyTuple_GET_ITEM(tup, i); - Py_INCREF(el); - EdgeTuple_SET_ITEM(o, i, el); - } - Py_DECREF(tup); - return (PyObject *)o; -} - - -static Py_ssize_t -tuple_length(EdgeTupleObject *o) -{ - return Py_SIZE(o); -} - - -static PyObject * -tuple_getitem(EdgeTupleObject *o, Py_ssize_t i) -{ - if (i < 0 || i >= Py_SIZE(o)) { - PyErr_SetString(PyExc_IndexError, "tuple index out of range"); - return NULL; - } - PyObject *el = EdgeTuple_GET_ITEM(o, i); - Py_INCREF(el); - return el; -} - - -static PyObject * -tuple_richcompare(EdgeTupleObject *v, PyObject *w, int op) -{ - if (EdgeTuple_Check(w)) { - return _EdgeGeneric_RichCompareValues( - v->ob_item, Py_SIZE(v), - ((EdgeTupleObject *)w)->ob_item, Py_SIZE(w), - op); - } - - if (PyTuple_CheckExact(w)) { - return _EdgeGeneric_RichCompareValues( - v->ob_item, Py_SIZE(v), - ((PyTupleObject *)w)->ob_item, Py_SIZE(w), - op); - } - - Py_RETURN_NOTIMPLEMENTED; -} - - -static PyObject * -tuple_repr(EdgeTupleObject *o) -{ - _PyUnicodeWriter writer; - _PyUnicodeWriter_Init(&writer); - writer.overallocate = 1; - - if (_PyUnicodeWriter_WriteChar(&writer, '(') < 0) { - goto error; - } - - if (_EdgeGeneric_RenderValues(&writer, - (PyObject *)o, o->ob_item, Py_SIZE(o)) < 0) - { - goto error; - } - - if (_PyUnicodeWriter_WriteChar(&writer, ')') < 0) { - goto error; - } - - return _PyUnicodeWriter_Finish(&writer); - -error: - _PyUnicodeWriter_Dealloc(&writer); - return NULL; -} - - -static PySequenceMethods tuple_as_sequence = { - .sq_length = (lenfunc)tuple_length, - .sq_item = (ssizeargfunc)tuple_getitem, -}; - - -PyTypeObject EdgeTuple_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "edgedb.Tuple", - .tp_basicsize = sizeof(EdgeTupleObject) - sizeof(PyObject *), - .tp_itemsize = sizeof(PyObject *), - .tp_dealloc = (destructor)tuple_dealloc, - .tp_as_sequence = &tuple_as_sequence, - .tp_hash = (hashfunc)tuple_hash, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)tuple_traverse, - .tp_new = tuple_new, - .tp_richcompare = (richcmpfunc)tuple_richcompare, - .tp_free = PyObject_GC_Del, - .tp_repr = (reprfunc)tuple_repr, - .tp_weaklistoffset = offsetof(EdgeTupleObject, weakreflist), -}; - - -PyObject * -EdgeTuple_InitType(void) -{ - if (PyType_Ready(&EdgeTuple_Type) < 0) { - return NULL; - } - - init_type_called = 1; - return (PyObject *)&EdgeTuple_Type; -} diff --git a/edgedb/protocol/codecs/array.pxd b/edgedb/protocol/codecs/array.pxd index 32159c0b..44de315b 100644 --- a/edgedb/protocol/codecs/array.pxd +++ b/edgedb/protocol/codecs/array.pxd @@ -23,11 +23,6 @@ cdef class BaseArrayCodec(BaseCodec): BaseCodec sub_codec int32_t cardinality - cdef _new_collection(self, Py_ssize_t size) - - cdef _set_collection_item(self, object collection, Py_ssize_t i, - object element) - cdef _decode_array(self, FRBuffer *buf) diff --git a/edgedb/protocol/codecs/array.pyx b/edgedb/protocol/codecs/array.pyx index e3e8997d..1a10c7dd 100644 --- a/edgedb/protocol/codecs/array.pyx +++ b/edgedb/protocol/codecs/array.pyx @@ -30,13 +30,6 @@ cdef class BaseArrayCodec(BaseCodec): self.sub_codec = None self.cardinality = -1 - cdef _new_collection(self, Py_ssize_t size): - raise NotImplementedError - - cdef _set_collection_item(self, object collection, Py_ssize_t i, - object element): - raise NotImplementedError - cdef encode(self, WriteBuffer buf, object obj): cdef: WriteBuffer elem_data @@ -102,7 +95,7 @@ cdef class BaseArrayCodec(BaseCodec): raise RuntimeError('only 1-dimensional arrays are supported') if ndims == 0: - return self._new_collection(0) + return [] assert ndims == 1 @@ -115,7 +108,7 @@ cdef class BaseArrayCodec(BaseCodec): frb_read(buf, 4) # Ignore the lower bound information - result = self._new_collection(elem_count) + result = cpython.PyList_New(elem_count) for i in range(elem_count): elem_len = hton.unpack_int32(frb_read(buf, 4)) if elem_len == -1: @@ -128,7 +121,8 @@ cdef class BaseArrayCodec(BaseCodec): f'unexpected trailing data in buffer after ' f'array element decoding: {frb_get_len(&elem_buf)}') - self._set_collection_item(result, i, elem) + cpython.Py_INCREF(elem) + cpython.PyList_SET_ITEM(result, i, elem) return result @@ -139,13 +133,6 @@ cdef class BaseArrayCodec(BaseCodec): @cython.final cdef class ArrayCodec(BaseArrayCodec): - cdef _new_collection(self, Py_ssize_t size): - return datatypes.array_new(size) - - cdef _set_collection_item(self, object collection, Py_ssize_t i, - object element): - datatypes.array_set(collection, i, element) - @staticmethod cdef BaseCodec new(bytes tid, BaseCodec sub_codec, int32_t cardinality): cdef: diff --git a/edgedb/protocol/codecs/base.pyx b/edgedb/protocol/codecs/base.pyx index c7007ae2..83819e33 100644 --- a/edgedb/protocol/codecs/base.pyx +++ b/edgedb/protocol/codecs/base.pyx @@ -110,7 +110,7 @@ cdef class EmptyTupleCodec(BaseCodec): f'got {elem_count}') if self.empty_tup is None: - self.empty_tup = datatypes.tuple_new(0) + self.empty_tup = cpython.PyTuple_New(0) return self.empty_tup diff --git a/edgedb/protocol/codecs/enum.pxd b/edgedb/protocol/codecs/enum.pxd index 08d9dee3..d65c0933 100644 --- a/edgedb/protocol/codecs/enum.pxd +++ b/edgedb/protocol/codecs/enum.pxd @@ -21,7 +21,7 @@ cdef class EnumCodec(BaseCodec): cdef: - object descriptor + object cls @staticmethod cdef BaseCodec new(bytes tid, tuple enum_labels) diff --git a/edgedb/protocol/codecs/enum.pyx b/edgedb/protocol/codecs/enum.pyx index 8959f203..3b47b996 100644 --- a/edgedb/protocol/codecs/enum.pyx +++ b/edgedb/protocol/codecs/enum.pyx @@ -16,20 +16,22 @@ # limitations under the License. # +import enum + @cython.final cdef class EnumCodec(BaseCodec): cdef encode(self, WriteBuffer buf, object obj): - if not isinstance(obj, (datatypes.EnumValue, str)): + if not isinstance(obj, (self.cls, str)): raise TypeError( - f'a str or edgedb.EnumValue is expected as a valid ' - f'enum argument, got {type(obj).__name__}') + f'a str or edgedb.EnumValue(__tid__={self.cls.__tid__}) is ' + f'expected as a valid enum argument, got {type(obj).__name__}') pgproto.text_encode(DEFAULT_CODEC_CONTEXT, buf, str(obj)) cdef decode(self, FRBuffer *buf): label = pgproto.text_decode(DEFAULT_CODEC_CONTEXT, buf) - return datatypes.EnumValue(self.descriptor, label) + return self.cls(label) @staticmethod cdef BaseCodec new(bytes tid, tuple enum_labels): @@ -40,7 +42,16 @@ cdef class EnumCodec(BaseCodec): codec.tid = tid codec.name = 'Enum' - codec.descriptor = datatypes.EnumDescriptor( - pgproto.UUID(tid), enum_labels) + cls = "DerivedEnumValue" + bases = (datatypes.EnumValue,) + classdict = enum.EnumMeta.__prepare__(cls, bases) + classdict["__module__"] = "edgedb" + classdict["__qualname__"] = "edgedb.DerivedEnumValue" + classdict["__tid__"] = pgproto.UUID(tid) + for label in enum_labels: + classdict[label.upper()] = label + codec.cls = enum.EnumMeta(cls, bases, classdict) + for index, label in enumerate(enum_labels): + codec.cls(label)._index_ = index return codec diff --git a/edgedb/protocol/codecs/namedtuple.pxd b/edgedb/protocol/codecs/namedtuple.pxd index 6d06e09f..6f2563ed 100644 --- a/edgedb/protocol/codecs/namedtuple.pxd +++ b/edgedb/protocol/codecs/namedtuple.pxd @@ -19,6 +19,7 @@ @cython.final cdef class NamedTupleCodec(BaseNamedRecordCodec): + cdef object namedtuple_type @staticmethod cdef BaseCodec new(bytes tid, tuple fields_names, tuple fields_codecs) diff --git a/edgedb/protocol/codecs/namedtuple.pyx b/edgedb/protocol/codecs/namedtuple.pyx index 0a9f6e68..ef3b18fa 100644 --- a/edgedb/protocol/codecs/namedtuple.pyx +++ b/edgedb/protocol/codecs/namedtuple.pyx @@ -17,9 +17,6 @@ # -from edgedb import errors - - @cython.final cdef class NamedTupleCodec(BaseNamedRecordCodec): @@ -40,7 +37,7 @@ cdef class NamedTupleCodec(BaseNamedRecordCodec): f'cannot decode NamedTuple: expected {len(fields_codecs)} ' f'elements, got {elem_count}') - result = datatypes.namedtuple_new(self.descriptor) + result = datatypes.namedtuple_new(self.namedtuple_type) for i in range(elem_count): frb_read(buf, 4) # reserved @@ -57,7 +54,8 @@ cdef class NamedTupleCodec(BaseNamedRecordCodec): f'unexpected trailing data in buffer after named ' f'tuple element decoding: {frb_get_len(&elem_buf)}') - datatypes.namedtuple_set(result, i, elem) + cpython.Py_INCREF(elem) + cpython.PyTuple_SET_ITEM(result, i, elem) return result @@ -73,5 +71,6 @@ cdef class NamedTupleCodec(BaseNamedRecordCodec): codec.descriptor = datatypes.record_desc_new( fields_names, NULL, NULL) codec.fields_codecs = fields_codecs + codec.namedtuple_type = datatypes.namedtuple_type_new(codec.descriptor) return codec diff --git a/edgedb/protocol/codecs/range.pyx b/edgedb/protocol/codecs/range.pyx index d5cb07af..62768d63 100644 --- a/edgedb/protocol/codecs/range.pyx +++ b/edgedb/protocol/codecs/range.pyx @@ -17,7 +17,7 @@ # -from edgedb import datatypes +from edgedb.datatypes import range as range_mod cdef uint8_t RANGE_EMPTY = 0x01 @@ -129,7 +129,7 @@ cdef class RangeCodec(BaseCodec): f'unexpected trailing data in buffer after ' f'range bound decoding: {frb_get_len(&sub_buf)}') - return datatypes.Range( + return range_mod.Range( lower, upper, inc_lower=inc_lower, diff --git a/edgedb/protocol/codecs/set.pyx b/edgedb/protocol/codecs/set.pyx index bda8f9da..f1f43712 100644 --- a/edgedb/protocol/codecs/set.pyx +++ b/edgedb/protocol/codecs/set.pyx @@ -20,13 +20,6 @@ @cython.final cdef class SetCodec(BaseArrayCodec): - cdef _new_collection(self, Py_ssize_t size): - return datatypes.set_new(size) - - cdef _set_collection_item(self, object collection, Py_ssize_t i, - object element): - datatypes.set_set(collection, i, element) - @staticmethod cdef BaseCodec new(bytes tid, BaseCodec sub_codec): cdef: @@ -66,7 +59,7 @@ cdef class SetCodec(BaseArrayCodec): if ndims == 0: # Special case for an empty set. - return self._new_collection(0) + return [] elif ndims > 1: raise RuntimeError('expected a two-dimensional array for a ' 'set of arrays') @@ -74,7 +67,7 @@ cdef class SetCodec(BaseArrayCodec): elem_count = hton.unpack_int32(frb_read(buf, 4)) frb_read(buf, 4) # Ignore the lower bound information - result = self._new_collection(elem_count) + result = cpython.PyList_New(elem_count) for i in range(elem_count): frb_read(buf, 4) # ignore array element size @@ -97,6 +90,7 @@ cdef class SetCodec(BaseArrayCodec): raise RuntimeError( f'unexpected trailing data in buffer after ' f'set element decoding: {frb_get_len(&elem_buf)}') - self._set_collection_item(result, i, elem) + cpython.Py_INCREF(elem) + cpython.PyList_SET_ITEM(result, i, elem) return result diff --git a/edgedb/protocol/codecs/tuple.pyx b/edgedb/protocol/codecs/tuple.pyx index baa76370..43927be4 100644 --- a/edgedb/protocol/codecs/tuple.pyx +++ b/edgedb/protocol/codecs/tuple.pyx @@ -37,7 +37,7 @@ cdef class TupleCodec(BaseRecordCodec): f'cannot decode Tuple: expected {len(fields_codecs)} ' f'elements, got {elem_count}') - result = datatypes.tuple_new(elem_count) + result = cpython.PyTuple_New(elem_count) for i in range(elem_count): frb_read(buf, 4) # reserved @@ -54,7 +54,8 @@ cdef class TupleCodec(BaseRecordCodec): f'unexpected trailing data in buffer after ' f'tuple element decoding: {frb_get_len(&elem_buf)}') - datatypes.tuple_set(result, i, elem) + cpython.Py_INCREF(elem) + cpython.PyTuple_SET_ITEM(result, i, elem) return result diff --git a/edgedb/protocol/protocol.pyx b/edgedb/protocol/protocol.pyx index aeb953a4..3c361ade 100644 --- a/edgedb/protocol/protocol.pyx +++ b/edgedb/protocol/protocol.pyx @@ -381,7 +381,7 @@ cdef class SansIOProtocol: packet.write_bytes(SYNC_MESSAGE) self.write(packet) - result = datatypes.set_new(0) + result = [] exc = None while True: if not self.buffer.take_message(): @@ -1176,9 +1176,9 @@ cdef class SansIOProtocol: if buf.get_message_type() != DATA_MSG: raise RuntimeError('first message is not "DataMsg"') - if not datatypes.set_check(result): + if not isinstance(result, list): raise RuntimeError( - f'result is not an edgedb.Set, but {result!r}') + f'result is not a list, but {result!r}') while take_message_type(buf, DATA_MSG): cbuf = try_consume_message(buf, &cbuf_len) @@ -1207,7 +1207,7 @@ cdef class SansIOProtocol: frb_init(rbuf, cbuf + 6, cbuf_len - 6) row = decoder(out_dc, rbuf) - datatypes.set_append(result, row) + result.append(row) if frb_get_len(rbuf): raise RuntimeError( diff --git a/edgedb/protocol/protocol_v0.pyx b/edgedb/protocol/protocol_v0.pyx index b81c5b08..399f803c 100644 --- a/edgedb/protocol/protocol_v0.pyx +++ b/edgedb/protocol/protocol_v0.pyx @@ -175,7 +175,7 @@ cdef class SansIOProtocolBackwardsCompatible(SansIOProtocol): packet.write_bytes(SYNC_MESSAGE) self.write(packet) - result = datatypes.set_new(0) + result = [] exc = None while True: @@ -269,7 +269,7 @@ cdef class SansIOProtocolBackwardsCompatible(SansIOProtocol): packet.write_bytes(SYNC_MESSAGE) self.write(packet) - result = datatypes.set_new(0) + result = [] re_exec = False exc = None while True: diff --git a/setup.py b/setup.py index a345d7f4..4aeaf0ed 100644 --- a/setup.py +++ b/setup.py @@ -302,12 +302,9 @@ def finalize_options(self): "edgedb.datatypes.datatypes", ["edgedb/datatypes/args.c", "edgedb/datatypes/record_desc.c", - "edgedb/datatypes/tuple.c", "edgedb/datatypes/namedtuple.c", "edgedb/datatypes/object.c", - "edgedb/datatypes/set.c", "edgedb/datatypes/hash.c", - "edgedb/datatypes/array.c", "edgedb/datatypes/link.c", "edgedb/datatypes/linkset.c", "edgedb/datatypes/repr.c", diff --git a/tests/datatypes/test_datatypes.py b/tests/datatypes/test_datatypes.py index ab16640a..8f612427 100644 --- a/tests/datatypes/test_datatypes.py +++ b/tests/datatypes/test_datatypes.py @@ -17,8 +17,13 @@ # +import dataclasses +import gc +import os +import random +import string import unittest - +import weakref import edgedb from edgedb.datatypes import datatypes as private @@ -135,6 +140,7 @@ class TestTuple(unittest.TestCase): def test_tuple_empty_1(self): t = edgedb.Tuple() + self.assertIsInstance(t, tuple) self.assertEqual(len(t), 0) self.assertEqual(hash(t), hash(())) self.assertEqual(repr(t), '()') @@ -161,10 +167,6 @@ def test_tuple_3(self): self.assertEqual(repr(t), '(1, [(...)])') self.assertEqual(str(t), '(1, [(...)])') - def test_tuple_4(self): - with self.assertRaisesRegex(ValueError, f'more than {0x4000 - 1}'): - edgedb.Tuple([1] * 20000) - def test_tuple_freelist_1(self): lst = [] for _ in range(5000): @@ -377,6 +379,101 @@ def test_namedtuple_7(self): edgedb.NamedTuple(a=1, b=2, c=3), 1) + def test_namedtuple_8(self): + self.assertEqual( + edgedb.NamedTuple(壹=1, 贰=2, 叄=3), + (1, 2, 3)) + + def test_namedtuple_memory(self): + num = int(os.getenv("EDGEDB_PYTHON_TEST_NAMEDTUPLE_MEMORY", 100)) + + def test(): + nt = [] + fix_tp = type(edgedb.NamedTuple(a=1, b=2)) + for _i in range(num): + values = {} + for _ in range(random.randint(9, 16)): + key = "".join(random.choices(string.ascii_letters, k=3)) + value = random.randint(16384, 65536) + values[key] = value + nt.append(edgedb.NamedTuple(**values)) + if random.random() > 0.5: + nt.append( + fix_tp(random.randint(10, 20), random.randint(20, 30)) + ) + if len(nt) % random.randint(10, 20) == 0: + nt[:] = nt[random.randint(5, len(nt)):] + + gc.collect() + gc.collect() + gc.collect() + gc_count = gc.get_count() + test() + gc.collect() + gc.collect() + gc.collect() + self.assertEqual(gc.get_count(), gc_count) + + +class TestDerivedNamedTuple(unittest.TestCase): + DerivedNamedTuple = type(edgedb.NamedTuple(a=1, b=2, c=3)) + + def test_derived_namedtuple_1(self): + self.assertEqual( + (1, 2, 3), + self.DerivedNamedTuple(a=1, b=2, c=3), + ) + self.assertEqual( + (1, 2, 3), + self.DerivedNamedTuple(c=3, b=2, a=1), + ) + self.assertEqual( + (1, 2, 3), + self.DerivedNamedTuple(1, c=3, b=2), + ) + self.assertEqual( + (1, 2, 3), + self.DerivedNamedTuple(1, 2, 3), + ) + + def test_derived_namedtuple_2(self): + with self.assertRaisesRegex(ValueError, "requires 3 arguments"): + self.DerivedNamedTuple() + + with self.assertRaisesRegex(ValueError, "requires 3 arguments"): + self.DerivedNamedTuple(1) + + with self.assertRaisesRegex(ValueError, "only needs 3 arguments"): + self.DerivedNamedTuple(1, 2, 3, 4) + + def test_derived_namedtuple_3(self): + with self.assertRaisesRegex(ValueError, "missing required argument"): + self.DerivedNamedTuple(a=1) + + with self.assertRaisesRegex(ValueError, "missing required argument"): + self.DerivedNamedTuple(b=2) + + with self.assertRaisesRegex(ValueError, "missing required argument"): + self.DerivedNamedTuple(1, 2, d=4) + + with self.assertRaisesRegex(ValueError, "extra keyword arguments"): + self.DerivedNamedTuple(1, 2, 3, d=4) + + with self.assertRaisesRegex(ValueError, "extra keyword arguments"): + self.DerivedNamedTuple(1, 2, c=3, d=4) + + def test_derived_namedtuple_4(self): + tp = type(edgedb.NamedTuple(x=42)) + tp(8) + edgedb.NamedTuple(y=88) + tp(16) + tp_ref = weakref.ref(tp) + gc.collect() + self.assertIsNotNone(tp_ref()) + del tp + gc.collect() + self.assertIsNone(tp_ref()) + class TestObject(unittest.TestCase): @@ -422,7 +519,7 @@ def test_object_2(self): self.assertEqual(repr(o), 'Object{@lb := 2, c := 3}') - self.assertEqual(hash(o), hash(f(1, 2, 3))) + self.assertNotEqual(hash(o), hash(f(1, 2, 3))) self.assertNotEqual(hash(o), hash(f(1, 2, 'aaaa'))) self.assertNotEqual(hash(o), hash((1, 2, 3))) @@ -435,9 +532,6 @@ def test_object_3(self): o.c.append(o) self.assertEqual(repr(o), 'Object{id := 1, c := [Object{...}]}') - with self.assertRaisesRegex(TypeError, 'unhashable'): - hash(o) - def test_object_4(self): f = private.create_object_factory( id={'property', 'implicit'}, @@ -449,10 +543,8 @@ def test_object_4(self): o2 = f(1, 'ab', 'bb') o3 = f(3, 'ac', 'bc') - self.assertEqual(o1, o2) + self.assertNotEqual(o1, o2) self.assertNotEqual(o1, o3) - self.assertLess(o1, o3) - self.assertGreater(o3, o2) def test_object_5(self): f = private.create_object_factory( @@ -495,7 +587,6 @@ def test_object_links_1(self): linkset = o1['o2s'] self.assertEqual(len(linkset), 2) self.assertEqual(linkset, o1['o2s']) - self.assertEqual(hash(linkset), hash(o1['o2s'])) self.assertEqual( repr(linkset), "LinkSet(name='o2s', source_id=2, target_ids={1, 4})") @@ -580,12 +671,37 @@ def test_object_links_4(self): "link 'error_key' does not exist"): u['error_key'] + def test_object_dataclass_1(self): + User = private.create_object_factory( + id='property', + name='property', + tuple='property', + namedtuple='property', + ) + + u = User( + 1, + 'Bob', + edgedb.Tuple((1, 2.0, '3')), + edgedb.NamedTuple(a=1, b="Y"), + ) + self.assertTrue(dataclasses.is_dataclass(u)) + self.assertEqual( + dataclasses.asdict(u), + { + 'id': 1, + 'name': 'Bob', + 'tuple': (1, 2.0, '3'), + 'namedtuple': (1, "Y"), + }, + ) + class TestSet(unittest.TestCase): def test_set_1(self): s = edgedb.Set(()) - self.assertEqual(repr(s), 'Set{}') + self.assertEqual(repr(s), '[]') s = edgedb.Set((1, 2, [], 'a')) @@ -595,36 +711,23 @@ def test_set_1(self): with self.assertRaises(IndexError): s[10] - with self.assertRaises(TypeError): - s[0] = 1 - def test_set_2(self): s = edgedb.Set((1, 2, 3000, 'a')) - self.assertEqual(repr(s), "Set{1, 2, 3000, 'a'}") - - self.assertEqual( - hash(s), - hash(edgedb.Set((1, 2, sum([1000, 2000]), 'a')))) - - self.assertNotEqual( - hash(s), - hash((1, 2, 3000, 'a'))) + self.assertEqual(repr(s), "[1, 2, 3000, 'a']") def test_set_3(self): s = edgedb.Set(()) self.assertEqual(len(s), 0) - self.assertEqual(hash(s), hash(edgedb.Set(()))) - self.assertNotEqual(hash(s), hash(())) def test_set_4(self): s = edgedb.Set(([],)) s[0].append(s) - self.assertEqual(repr(s), "Set{[Set{...}]}") + self.assertEqual(repr(s), "[[[...]]]") def test_set_5(self): - self.assertEqual( + self.assertNotEqual( edgedb.Set([1, 2, 3]), edgedb.Set([3, 2, 1])) @@ -663,11 +766,11 @@ def test_set_6(self): o2 = f(1, 'ab', edgedb.Set([1, 2, 4])) o3 = f(3, 'ac', edgedb.Set([5, 5, 5, 5])) - self.assertEqual( + self.assertNotEqual( edgedb.Set([o1, o2, o3]), edgedb.Set([o2, o3, o1])) - self.assertEqual( + self.assertNotEqual( edgedb.Set([o1, o3]), edgedb.Set([o2, o3])) @@ -699,7 +802,6 @@ class TestArray(unittest.TestCase): def test_array_empty_1(self): t = edgedb.Array() self.assertEqual(len(t), 0) - self.assertNotEqual(hash(t), hash(())) with self.assertRaisesRegex(IndexError, 'out of range'): t[0] self.assertEqual(repr(t), "[]") @@ -711,8 +813,6 @@ def test_array_2(self): self.assertEqual(str(t), "[1, 'a']") self.assertEqual(len(t), 2) - self.assertEqual(hash(t), hash(edgedb.Array([1, 'a']))) - self.assertNotEqual(hash(t), hash(edgedb.Array([10, 'ab']))) self.assertEqual(t[0], 1) self.assertEqual(t[1], 'a') diff --git a/tests/test_async_query.py b/tests/test_async_query.py index e59a200d..6afe35c3 100644 --- a/tests/test_async_query.py +++ b/tests/test_async_query.py @@ -545,19 +545,22 @@ async def test_async_args_uuid_pack(self): ot = await self.client.query_single( 'select schema::Object {name} filter .id=$id', id=obj.id) - self.assertEqual(obj, ot) + self.assertEqual(obj.id, ot.id) + self.assertEqual(obj.name, ot.name) # Test that a string UUID is acceptable. ot = await self.client.query_single( 'select schema::Object {name} filter .id=$id', id=str(obj.id)) - self.assertEqual(obj, ot) + self.assertEqual(obj.id, ot.id) + self.assertEqual(obj.name, ot.name) # Test that a standard uuid.UUID is acceptable. ot = await self.client.query_single( 'select schema::Object {name} filter .id=$id', id=uuid.UUID(bytes=obj.id.bytes)) - self.assertEqual(obj, ot) + self.assertEqual(obj.id, ot.id) + self.assertEqual(obj.name, ot.name) with self.assertRaisesRegex(edgedb.InvalidArgumentError, 'invalid UUID.*length must be'): diff --git a/tests/test_enum.py b/tests/test_enum.py index 64c955a5..a5e99b15 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -17,6 +17,8 @@ # +import dataclasses +import enum import uuid import edgedb @@ -38,7 +40,7 @@ async def test_enum_01(self): self.assertTrue(isinstance(ct_red, edgedb.EnumValue)) self.assertTrue(isinstance(ct_red.__tid__, uuid.UUID)) - self.assertEqual(repr(ct_red), "") + self.assertEqual(repr(ct_red), "") self.assertEqual(str(ct_red), 'red') self.assertNotEqual(ct_red, 'red') @@ -74,3 +76,26 @@ async def test_enum_01(self): self.assertNotEqual(hash(ct_red), hash(c_red)) self.assertNotEqual(hash(ct_red), hash('red')) + + async def test_enum_02(self): + c_red = await self.client.query_single('SELECT "red"') + self.assertIsInstance(c_red, enum.Enum) + self.assertEqual(c_red.name, 'RED') + self.assertEqual(c_red.value, 'red') + + class Color(enum.Enum): + RED = 'red' + WHITE = 'white' + + @dataclasses.dataclass + class Container: + color: Color + + c = Container(c_red) + d = dataclasses.asdict(c) + self.assertIs(d['color'], c_red) + + async def test_enum_03(self): + c_red = await self.client.query_single('SELECT "red"') + c_red2 = await self.client.query_single('SELECT $0', c_red) + self.assertIs(c_red, c_red2)