Last active
August 29, 2015 14:23
-
-
Save eevee/aac4cf69d55acbd18cbf to your computer and use it in GitHub Desktop.
sqlalchemy doesn't like my zany self-join
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Context: I have a self-referential nested set table and I'm trying to write a | |
# parent/child relationship. I use DISTINCT ON, so this assumes Postgres. | |
from sqlalchemy import * | |
from sqlalchemy.ext import compiler | |
from sqlalchemy.ext.declarative import declarative_base | |
from sqlalchemy.orm import foreign, remote | |
from sqlalchemy.orm import joinedload | |
from sqlalchemy.orm import joinedload | |
from sqlalchemy.orm import relationship | |
from sqlalchemy.orm import sessionmaker | |
from sqlalchemy.schema import DDLElement | |
from sqlalchemy.sql import select | |
from sqlalchemy.sql import table | |
# Verbatim from https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/Views | |
class CreateView(DDLElement): | |
def __init__(self, name, selectable): | |
self.name = name | |
self.selectable = selectable | |
class DropView(DDLElement): | |
def __init__(self, name): | |
self.name = name | |
@compiler.compiles(CreateView) | |
def compile(element, compiler, **kw): | |
return "CREATE VIEW %s AS %s" % (element.name, compiler.sql_compiler.process(element.selectable)) | |
@compiler.compiles(DropView) | |
def compile(element, compiler, **kw): | |
return "DROP VIEW %s" % (element.name) | |
def view(name, metadata, selectable): | |
t = table(name) | |
for c in selectable.c: | |
c._make_proxy(t) | |
CreateView(name, selectable).execute_at('after-create', metadata) | |
DropView(name).execute_at('before-drop', metadata) | |
return t | |
# End copy-paste | |
Base = declarative_base() | |
class Node(Base): | |
__tablename__ = 'node' | |
id = Column(Integer, primary_key=True, nullable=False) | |
left = Column(Integer, nullable=False) | |
right = Column(Integer, nullable=False) | |
def __repr__(self): | |
return "<Node {self.id}>".format(self=self) | |
_node_parent = Node.__table__.alias() | |
_node_child = Node.__table__.alias() | |
node_bridge = view( | |
'node_parentage', | |
Base.metadata, | |
select([_node_child.c.id.label('child_id'), _node_parent.c.id.label('parent_id')]) | |
.select_from( | |
_node_child.join( | |
_node_parent, | |
and_( | |
_node_parent.c.left < _node_child.c.left, | |
_node_child.c.right < _node_parent.c.right, | |
), | |
) | |
) | |
.order_by(_node_child.c.id, _node_parent.c.left) | |
.distinct(_node_child.c.id) | |
) | |
Node.parent = relationship( | |
Node, | |
secondary=node_bridge, | |
primaryjoin=Node.id == node_bridge.c.child_id, | |
secondaryjoin=Node.id == node_bridge.c.parent_id, | |
viewonly=True, | |
uselist=False, | |
backref='children', | |
) | |
engine = create_engine('postgresql:///temp', echo=True) | |
Base.metadata.drop_all(bind=engine) | |
Base.metadata.create_all(bind=engine) | |
session = sessionmaker(bind=engine)() | |
node1 = Node(id=1, left=1, right=4) | |
node2 = Node(id=2, left=2, right=3) | |
node3 = Node(id=3, left=5, right=6) | |
session.add_all([node1, node2, node3]) | |
session.commit() | |
# Note that the bridge table itself has the correct results -- the only | |
# parent/child link is that 1 is the parent of 2. | |
# Output: | |
# (2, 1) | |
for row in session.execute(select(node_bridge.c)): | |
print(row) | |
# PROBLEM 3 | |
# This query is totally bogus: | |
# SELECT node.id AS node_id, node."left" AS node_left, node."right" AS node_right | |
# FROM node, node_parentage AS node_parentage_1 | |
# WHERE node_parentage_1.child_id = node_parentage_1.child_id | |
# AND %(param_1)s = node_parentage_1.parent_id | |
# Output: | |
# <Node 1> | |
# <Node 2> | |
# <Node 3> | |
print('-' * 80) | |
q = ( | |
session.query(Node) | |
.filter(Node.parent == node1) | |
.all() | |
) | |
for node in q: | |
print(node) | |
session.rollback() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment