5

次のような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は1つのプロパティでのみ不等式フィルターを許可し、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たが、それは役に立たないようです。うまくいけないのか、正しい方向に向かっていますか?

4

2 に答える 2

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)

より良い解決策(最終的には、バグのために開発サーバーでのみ機能します(問題798を参照)。理論的には、クエリ方法を利用して、前述の制限を回避し、範囲クエリを実行できますdb.ListProperty。範囲の開始と終了の両方をリスト(この場合はIPアドレスを表す整数)に格納するという考え方です。次に、範囲に何らかの値が含まれるエンティティv(つまり、リスト内の2つの値の間)を取得するには、単純にリスト上の2つの不等式フィルターを使用してクエリを実行します。1つvはリスト内の最小要素と少なくとも同じ大きさであることを確認し、もう1つは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)
于 2010-07-26T20:11:46.800 に答える
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)
于 2010-07-26T20:25:43.950 に答える