在App Engine数据存储上查询等效?

时间:2010-07-26 19:07:03

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

我有一个包含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可以“生效”还是必须在创建记录时计算?

第一次尝试解决方案时,

更新了问题:

所以基于David的答案以及这些文章:

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但这似乎没有帮助。绝望地坚持或朝着正确的方向前进?

3 个答案:

答案 0 :(得分:2)

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

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地址的整数)。然后获取范围包含某些值的实体{{1 (即,在列表中的两个值之间),您只需在列表中使用两个不等式过滤器执行查询 - 一个确保v至少与列表一样大列表中的最小元素,以及确保v至少与列表中最大元素一样小的元素。

以下是如何实现此技术的简单示例:

v

答案 1 :(得分: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)

答案 2 :(得分:0)

这很奇怪,因为我试图找出相同的基本前提并遇到各种问题(并且基于130K记录号,假设您使用的是MaxMind Geo Country文件)。虽然大卫的回答很容易,但在实践中并不奏效。这来自Google:

首先,如果查询在给定属性上有多个不等式过滤器,则实体只有在该属性具有与所有不等式过滤器匹配的单个值时才匹配该查询。例如,如果实体具有属性x的值[1,2],则它将与查询WHERE x&gt;不匹配。 1 AND x&lt; 2.每个过滤器都匹配x的一个值,但是没有一个值匹配两个过滤器。

我批量上传了整个数据集,创建了listproperty字段,试图无济于事。

所以任何额外的帮助将不胜感激!