【问题标题】:Between query equivalent on App Engine datastore?在 App Engine 数据存储区上的等效查询之间?
【发布时间】:2011-03-21 06:26:04
【问题描述】:

我有一个包含 IP 地址范围的模型,类似于:

class Country(db.Model):
  begin_ipnum = db.IntegerProperty()
  end_ipnum = db.IntegerProperty()

在 SQL 数据库上,我将能够找到包含特定范围内 IP 的行,如下所示:

SELECT * FROM Country WHERE ipnum BETWEEN begin_ipnum AND end_ipnum

或者这个:

SELECT * FROM Country WHERE begin_ipnum < ipnum AND end_ipnum > ipnum

遗憾的是,GQL 仅允许对一个属性进行不等式过滤器,并且不支持 BETWEEN 语法。如何解决此问题并在 App Engine 上构建与这些等效的查询?

另外,ListProperty 可以是“活的”还是必须在创建记录时计算?

问题已更新,首次尝试解决方案:

所以根据大卫下面的回答和这样的文章:

http://appengine-cookbook.appspot.com/recipe/custom-model-properties-are-cute/

我正在尝试向我的模型添加一个自定义字段,如下所示:

class IpRangeProperty(db.Property):
  def __init__(self, begin=None, end=None, **kwargs):
    if not isinstance(begin, db.IntegerProperty) or not isinstance(end, db.IntegerProperty):
        raise TypeError('Begin and End must be Integers.')
    self.begin = begin
    self.end = end
    super(IpRangeProperty, self).__init__(self.begin, self.end, **kwargs)

  def get_value_for_datastore(self, model_instance):
    begin = self.begin.get_value_for_datastore(model_instance)
    end = self.end.get_value_for_datastore(model_instance)
    if begin is not None and end is not None:
      return range(begin, end)

class Country(db.Model):
  begin_ipnum = db.IntegerProperty()
  end_ipnum = db.IntegerProperty()
  ip_range = IpRangeProperty(begin=begin_ipnum, end=end_ipnum)

我的想法是,在我添加自定义属性后,我可以按原样导入我的数据集,然后基于 ListProperty 运行查询,如下所示:

q = Country.gql('WHERE ip_range = :1', my_num_ipaddress)

当我尝试插入新的 Country 对象时,失败了,抱怨无法创建名称:

...
File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/db/__init__.py", line 619, in _attr_name
return '_' + self.name
TypeError: cannot concatenate 'str' and 'IntegerProperty' objects

我尝试为新属性定义一个attr_name 方法,或者只是设置self.name,但这似乎没有帮助。绝望地陷入困境还是朝着正确的方向前进?

【问题讨论】:

    标签: google-app-engine google-cloud-datastore gql


    【解决方案1】:

    简短回答:目前并不真正支持查询之间。但是,如果您先验地知道您的范围会相对较小,那么您可以伪造它:只需在实体上存储一个列表,其中包含范围内的 每个 数字。然后,您可以使用简单的相等过滤器来获取范围包含特定值的实体。显然,如果您的范围很大,这将不起作用。但它是这样工作的:

    class M(db.Model):
        r = db.ListProperty(int)
    
    # create an instance of M which has a range from `begin` to `end` (inclusive)
    M(r=range(begin, end+1)).put()
    
    # query to find instances of M which contain a value `v`
    q = M.gql('WHERE r = :1', v)
    

    更好的解决方案(最终 - 由于错误,现在以下仅适用于开发服务器(请参阅issue 798)。理论上,您可以解决您提到的限制并通过利用执行范围查询如何查询db.ListProperty。这个想法是将范围的开始和结束都存储在一个列表中(在你的情况下,整数表示IP地址)。然后获取范围包含某个值v的实体(即,介于列表中的两个值),您只需在列表中使用 两个 不等式过滤器执行查询 - 一个确保 v 至少与列表中的最小元素一样大,另一个确保v 至少与列表中的最大元素一样小。

    下面是一个如何实现该技术的简单示例:

    class M(db.Model):
        r = db.ListProperty(int)
    
    # create an instance of M which has a rnage from `begin` to `end` (inclusive)
    M(r=[begin, end]).put()
    
    # query to find instances of M which contain a value `v`
    q = M.gql('WHERE r >= :1 AND r <= :1', v)
    

    【讨论】:

    • 错误是 dev_appserver 的执行方式,不幸的是:正确的行为是当前的生产行为。
    • 这看起来是一个很有前途的解决方案,又好又简单。从文档看来,我可以创建一个自定义模型属性,根据 begin_ipnum 和 end_ipnum 的值报告列表值“live”。但据我所知,我无法使用 GQL 查询自定义属性。因此,我将首先尝试为对象创建范围计算 ListProperty 值。将报告我的表现!
    • 这个问题是否有更新,因为这是 7.5 年前回答的?现在查询之间有可能吗?
    【解决方案2】:

    我的解决方案不符合您要求的模式,但我认为它可以在应用引擎上运行良好。我使用 CIDR 范围的字符串列表来定义 IP 块,而不是特定的开始和结束编号。

    from google.appengine.ext import db    
    class Country(db.Model):
        subnets = db.StringListProperty()
        country_code = db.StringProperty()
    
    c = Country()
    c.subnets = ['1.2.3.0/24', '1.2.0.0/16', '1.3.4.0/24']
    c.country_code = 'US'
    c.put()
    
    c = Country()
    c.subnets = ['2.2.3.0/24', '2.2.0.0/16', '2.3.4.0/24']
    c.country_code = 'CA'
    c.put()
    
    # Search for 1.2.4.5 starting with most specific block and then expanding until found    
    result = Country.all().filter('subnets =', '1.2.4.5/32').fetch(1)
    result = Country.all().filter('subnets =', '1.2.4.4/31').fetch(1)
    result = Country.all().filter('subnets =', '1.2.4.4/30').fetch(1)
    result = Country.all().filter('subnets =', '1.2.4.0/29').fetch(1)
    # ... repeat until found
    # optimize by starting with the largest routing prefix actually found in your data (probably not 32)
    

    【讨论】:

    • 好主意。不过,这通常可能需要多次往返数据存储 - 如果您不需要扩展,或者这种查找不常见,这没关系。 (每次往返至少需要 60-80 毫秒)。
    • 我的解决方案以存储换取速度与您的解决方案。假设我们考虑了所有地址,您的将有大约 3B 个列表项。为了减少我的往返次数,您可以一次对所有可能性进行 IN 查询。
    • IN 查询肯定有助于减少运行时间——尽管这会导致查询非常昂贵,因为 IN 查询将被拆分为 [最多] 30 个子查询。 (如果您想要这样的匹配,则返回结果的内存中最长前缀匹配;与我的解决方案相同。)也就是说,虽然很昂贵,但可以在大地址范围上执行:)。 (+1)
    • 这很聪明,但我更容易理解大卫的解决方案,所以我会先尝试一下。顺便说一下,我用作起点的完整数据集(每个国家/地区的 IP 范围)只有大约 130k 个条目,所以还不错。
    • @tijs 我对项目的引用是所有列表中的 IP 地址总数。您有 130k 范围,但每个范围可能包含数万个地址。 David 的解决方案实现起来要简单得多。祝你好运!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 2015-04-19
    • 1970-01-01
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多