SQLAlchemy,急切加载postgresql INET/CIDR关系

mko*_*tee 1 python postgresql sqlalchemy

我在postgresql中有两个表,一个名为ip_address,另一个名为network.

ip_address表有两列:

  1. id 是一个 INTEGER
  2. v4address 是一个 INET

network表有两列:

  1. id 是一个 INTEGER
  2. v4representation 是一个 CIDR

我希望能够选择ip_address并急切加载ip_addresses network(s),而无需id在表之间定义基于外键的关系.这些id列由与其他表无关的其他关系使用.

完成此任务的SQL是:

select * from ip_address join network on ip_address.v4address << network.v4representation;
Run Code Online (Sandbox Code Playgroud)

在postgresql中,<<运算符可用于比较a INET和a CIDR.它将匹配INET包含在其中的行CIDR.

我可以property在我的IPAddress模型上定义一个可以完成此任务的模型:

@property
def networks(self):
    query = session.query(Network)
    query = query.filter("v4representation >> :v4addr").params(v4addr=self.v4address)
    return query.all()
Run Code Online (Sandbox Code Playgroud)

这有效,但是当我真正尝试property在应用程序中使用它时,我遇到了典型的"N + 1"查询问题.我想以这样的方式定义它,以便能够急于加载IP地址网络.

我试图将其定义为relationship使用primaryjoin,但无法弄清楚需要什么.我试过这个:

networks = db.relationship("Network",
                           primaryjoin='IPAddress.v4address << Network.v4representation',
                           viewonly=True)
Run Code Online (Sandbox Code Playgroud)

但是sqlalchemy不知道如何处理<<运营商,所以我改用了这个:

networks = db.relationship("Network",
                           primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
                           viewonly=True)
Run Code Online (Sandbox Code Playgroud)

但sqlalchemy抛出ArgumentError:

ArgumentError: Could not locate any relevant foreign key columns for primary join condition 'public.ip_address.v4address << public.network.v4representation' on relationship IPAddress.networks.  Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or are annotated in the join condition with the foreign() annotation.
Run Code Online (Sandbox Code Playgroud)

我试着定义的几种组合foreign_keyrelationship:

networks = db.relationship("Network",
                           primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
                           foreign_keys='[Network.v4representation]',
                           viewonly=True)
Run Code Online (Sandbox Code Playgroud)

但sqlalchemy抛出ArgumentErrors:ArgumentError:关系IPAddress.networks无法根据连接条件和remote_side参数确定任何明确的本地/远程列对.考虑使用remote()注释来准确标记关系远程端的连接条件的那些元素.

既不指定IPAddress.v4addressNetwork.v4representation作为remote_side改变异常.

没有尝试primaryjoinforeign/ 注释条件remote也有帮助.

回到我原来的意图,我希望能够执行一个查询,它将返回IP地址并急切加载他们的网络(以及可能来自网络其他关系的数据,因为这是我的完整模式的简化).

有没有人有什么建议?

提前致谢.

zzz*_*eek 5

这里缺少的部分是自定义运算符在关系框架内不起作用.为了帮助解决这个问题,我为SQLAlchemy 0.9.2添加了一个新功能,即"is_comparison"标志,这里有一个例子,在连接条件使用自定义运算符.

这是一个使用较少公共API完成相同结果的版本,它也可以在0.8中使用:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.dialects.postgresql import INET, CIDR

Base = declarative_base()

# workaround before 0.9.2
from sqlalchemy.sql import operators
is_contained_by = operators.custom_op("<<")
operators._comparison.add(is_contained_by)

class IPA(Base):
    __tablename__ = 'ip_address'

    id = Column(Integer, primary_key=True)
    v4address = Column(INET)

    network = relationship("Network",
                        primaryjoin=lambda: is_contained_by(
                                     IPA.v4address, 
                                     (foreign(Network.v4representation))
                                    ),
                        viewonly=True
                    )
class Network(Base):
    __tablename__ = 'network'

    id = Column(Integer, primary_key=True)
    v4representation = Column(CIDR)

print Session().query(IPA).join(IPA.network)
Run Code Online (Sandbox Code Playgroud)

在0.9.2及更高版本中,它可以完成为:

class IPA(Base):
    __tablename__ = 'ip_address'

    id = Column(Integer, primary_key=True)
    v4address = Column(INET)

    network = relationship("Network",
                        primaryjoin="IPA.v4address.op('<<', is_comparison=True)"
                            "(foreign(Network.v4representation))",
                        viewonly=True
                    )
Run Code Online (Sandbox Code Playgroud)