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

时间:2014-01-31 20:18:44

标签: python postgresql sqlalchemy

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

ip_address表有两列:

  1. idINTEGER
  2. v4addressINET
  3. network表有两列:

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

      实现此目的的SQL是:

      select * from ip_address join network on ip_address.v4address << network.v4representation;
      

      在postgresql中,<<运算符可用于比较INETCIDR。它将匹配INET中包含CIDR的行。

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

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

      这很有效,但是当我实际尝试在应用程序中使用此property时,我会遇到典型的“N + 1”查询问题。我想以这样的方式定义它,以便能够急于加载IP地址网络。

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

      networks = db.relationship("Network",
                                 primaryjoin='IPAddress.v4address << Network.v4representation',
                                 viewonly=True)
      

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

      networks = db.relationship("Network",
                                 primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
                                 viewonly=True)
      

      但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.
      

      我尝试了为foreign_key定义relationship的几种组合:

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

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

      IPAddress.v4addressNetwork.v4representation指定为remote_side都不会更改例外。

      使用primaryjoin / foreign注释remote条件的任何尝试都没有帮助。

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

      有人有任何建议吗?

      提前致谢。

1 个答案:

答案 0 :(得分:5)

这里缺少的部分是自定义运算符在关系框架内不起作用。为了帮助解决这个问题,我为SQLAlchemy 0.9.2添加了一个新功能,即“is_comparison”标志,并在Using custom operators in join conditions处添加了一个示例。

这是一个使用较少的公共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)

在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
                    )