Типы составных колонн¶
Наборы столбцов могут быть связаны с одним определяемым пользователем типом данных. ORM предоставляет один атрибут, который представляет группу столбцов, используя предоставленный вами класс.
Простой пример представляет пары столбцов в виде объекта Point
. Point
представляет такую пару как .x
и .y
:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __composite_values__(self):
return self.x, self.y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
def __eq__(self, other):
return isinstance(other, Point) and other.x == self.x and other.y == self.y
def __ne__(self, other):
return not self.__eq__(other)
Требования к классу пользовательского типа данных заключаются в том, что он должен иметь конструктор, который принимает позиционные аргументы, соответствующие формату столбцов, а также предоставляет метод __composite_values__()
, который возвращает состояние объекта в виде списка или кортежа в порядке следования его атрибутов, основанных на столбцах. Он также должен предоставлять адекватные методы __eq__()
и __ne__()
, которые проверяют равенство двух экземпляров.
Мы создадим отображение на таблицу vertices
, которая представляет две точки как x1/y1
и x2/y2
. Они создаются обычно как объекты Column
. Затем функция composite()
используется для назначения новых атрибутов, которые будут представлять наборы столбцов через класс Point
:
from sqlalchemy import Column, Integer
from sqlalchemy.orm import composite, declarative_base
Base = declarative_base()
class Vertex(Base):
__tablename__ = "vertices"
id = Column(Integer, primary_key=True)
x1 = Column(Integer)
y1 = Column(Integer)
x2 = Column(Integer)
y2 = Column(Integer)
start = composite(Point, x1, y1)
end = composite(Point, x2, y2)
Классическое отображение выше определило бы каждую composite()
по существующей таблице:
mapper_registry.map_imperatively(
Vertex,
vertices_table,
properties={
"start": composite(Point, vertices_table.c.x1, vertices_table.c.y1),
"end": composite(Point, vertices_table.c.x2, vertices_table.c.y2),
},
)
Теперь мы можем сохранять и использовать экземпляры Vertex
, а также запрашивать их, используя атрибуты .start
и .end
против специальных экземпляров Point
:
>>> v = Vertex(start=Point(3, 4), end=Point(5, 6))
>>> session.add(v)
>>> q = session.query(Vertex).filter(Vertex.start == Point(3, 4))
sql>>> print(q.first().start)
BEGIN (implicit)
INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?)
(3, 4, 5, 6)
SELECT vertices.id AS vertices_id,
vertices.x1 AS vertices_x1,
vertices.y1 AS vertices_y1,
vertices.x2 AS vertices_x2,
vertices.y2 AS vertices_y2
FROM vertices
WHERE vertices.x1 = ? AND vertices.y1 = ?
LIMIT ? OFFSET ?
(3, 4, 1, 0)
Point(x=3, y=4)
Отслеживание мутаций на месте на композитах¶
Изменения на месте существующего составного значения не отслеживаются автоматически. Вместо этого композитный класс должен явно предоставлять события своему родительскому объекту. Эта задача в значительной степени автоматизирована с помощью миксина MutableComposite
, который использует события для связывания каждого определенного пользователем составного объекта со всеми родительскими ассоциациями. Пожалуйста, посмотрите пример в mutable_composites.
Пересмотр операций сравнения для композитов¶
Операция сравнения «равно» по умолчанию производит И всех соответствующих столбцов, приравненных друг к другу. Это можно изменить с помощью аргумента comparator_factory
в composite()
, где мы указываем пользовательский класс Comparator
для определения существующих или новых операций. Ниже мы проиллюстрируем оператор «больше чем», реализующий то же выражение, что и базовый «больше чем»:
from sqlalchemy import sql
from sqlalchemy.orm.properties import CompositeProperty
class PointComparator(CompositeProperty.Comparator):
def __gt__(self, other):
"""redefine the 'greater than' operation"""
return sql.and_(
*[
a > b
for a, b in zip(
self.__clause_element__().clauses,
other.__composite_values__(),
)
]
)
class Vertex(Base):
__tablename__ = "vertices"
id = Column(Integer, primary_key=True)
x1 = Column(Integer)
y1 = Column(Integer)
x2 = Column(Integer)
y2 = Column(Integer)
start = composite(Point, x1, y1, comparator_factory=PointComparator)
end = composite(Point, x2, y2, comparator_factory=PointComparator)
Композиты для раскроя¶
Составные объекты могут быть определены для работы в простых вложенных схемах, переопределяя поведение внутри составного класса для работы по желанию, а затем отображая составной класс на всю длину отдельных столбцов обычным образом. Как правило, удобно определить отдельные конструкторы для пользовательского использования и для использования генерации из строки. Ниже мы реорганизуем класс Vertex
, чтобы он сам стал составным объектом, который затем отображается на класс HasVertex
:
from sqlalchemy.orm import composite
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __composite_values__(self):
return self.x, self.y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
def __eq__(self, other):
return isinstance(other, Point) and other.x == self.x and other.y == self.y
def __ne__(self, other):
return not self.__eq__(other)
class Vertex:
def __init__(self, start, end):
self.start = start
self.end = end
@classmethod
def _generate(self, x1, y1, x2, y2):
"""generate a Vertex from a row"""
return Vertex(Point(x1, y1), Point(x2, y2))
def __composite_values__(self):
return self.start.__composite_values__() + self.end.__composite_values__()
class HasVertex(Base):
__tablename__ = "has_vertex"
id = Column(Integer, primary_key=True)
x1 = Column(Integer)
y1 = Column(Integer)
x2 = Column(Integer)
y2 = Column(Integer)
vertex = composite(Vertex._generate, x1, y1, x2, y2)
Затем мы можем использовать приведенное выше отображение как:
hv = HasVertex(vertex=Vertex(Point(1, 2), Point(3, 4)))
s.add(hv)
s.commit()
hv = (
s.query(HasVertex)
.filter(HasVertex.vertex == Vertex(Point(1, 2), Point(3, 4)))
.first()
)
print(hv.vertex.start)
print(hv.vertex.end)