简体   繁体   English

SQLAlchemy,急于加载PostgreSQL INET / CIDR关系

[英]SQLAlchemy, eager load postgresql INET/CIDR relationships

I have two tables in postgresql, one named ip_address , and another named network . 我在postgresql中有两个表,一个表名为ip_address ,另一个表名为network

The ip_address table has two columns: ip_address表具有两列:

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

The network table has two columns: network表有两列:

  1. id is an INTEGER id是一个INTEGER
  2. v4representation is a CIDR v4representationCIDR

I want to be able to select an ip_address and eagerly load the ip_addresses network (s) without having to define an id based foreign key relationship between the tables. 我希望能够选择一个ip_address并热切加载ip_addresses network而不必在表之间定义基于id的外键关系。 The id columns are used by other, unrelated relationships to other tables. id列由与其他表的其他不相关关系使用。

The SQL to accomplish this is: 完成此任务的SQL是:

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

In postgresql, the << operator can be used to compare an INET and a CIDR . 在postgresql中, <<操作符可用于比较INETCIDR It will match rows where the INET is contained within the CIDR . 它将匹配CIDR包含INET行。

I can define a property on my IPAddress model which will accomplish this: 我可以在IPAddress模型上定义一个property来完成此任务:

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

This works, but then when I'm actually attempting to use this property in an application, I experience the typical "N + 1" queries problem. 这可行,但是当我实际尝试在应用程序中使用此property时,我遇到了典型的“ N + 1”查询问题。 I'd like to define this in such a way as to be able to eager load an ip addresses networks. 我想以一种能够加载IP地址网络的方式来定义它。

I've tried to define it as a relationship using primaryjoin , but can't figure out what's needed. 我尝试使用primaryjoin将其定义为relationship ,但无法弄清楚需要什么。 I've tried this: 我已经试过了:

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

But sqlalchemy doesn't know what to do with the << operator, so I've switched to this: 但是sqlalchemy不知道如何处理<<操作符,因此我切换到了这个操作:

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

But sqlalchemy throws an ArgumentError : 但是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.

I've tried several combinations of defining a foreign_key for the relationship : 我尝试了几种定义relationshipforeign_key组合:

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

But sqlalchemy throws ArgumentErrors : ArgumentError: Relationship IPAddress.networks could not determine any unambiguous local/remote column pairs based on join condition and remote_side arguments. 但是sqlalchemy抛出ArgumentErrors :ArgumentError:关系IPAddress.networks无法基于联接条件和remote_side参数确定任何明确的本地/远程列对。 Consider using the remote() annotation to accurately mark those elements of the join condition that are on the remote side of the relationship. 考虑使用remote()批注来准确标记关系的远程端上的联接条件元素。

Neither specifying IPAddress.v4address or Network.v4representation as a remote_side changes the exception. IPAddress.v4addressNetwork.v4representation指定为remote_side都不会更改该异常。

No attempts at annotating the primaryjoin condition with foreign / remote has helped either. 尝试使用foreign / remote注释primaryjoin条件也没有任何帮助。

Coming back to my original intent, I want to be able to perform a query which will return ip addresses and eager load their networks (and possibly data from the networks other relations, as this is a simplification of my full schema). 回到我最初的意图,我希望能够执行一个查询,该查询将返回ip地址并渴望加载其网络(以及可能来自网络其他关系的数据,因为这是我完整架构的简化)。

Does anyone have any suggestions? 有没有人有什么建议?

Thanks in advance. 提前致谢。

the missing piece here is that the custom operator isn't working within the relationship framework. 这里缺少的部分是自定义运算符不在关系框架内工作。 To assist in this case I've added a new feature for SQLAlchemy 0.9.2 which is the "is_comparison" flag, and an example is here, at Using custom operators in join conditions . 为了在这种情况下提供帮助,我为SQLAlchemy 0.9.2添加了一个新功能,即“ is_comparison”标志,此处是在联接条件下使用自定义运算符的示例。

Here is a version that accomplishes this same result using less public APIs, which will work in 0.8 also: 这是一个使用较少的公共API即可达到相同结果的版本,该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)

In 0.9.2 and greater, it can be done as: 在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
                    )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM