2010-07-26 105 views
5

我有一個包含IP地址範圍的模型,與此類似:在App Engine數據存儲上等價的查詢之間?

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) 

當我嘗試插入新的國家反對這種失敗雖然complaning有關無法創建名稱:

... 
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但似乎並沒有幫助。絕望地卡住或朝着正確的方向前進?

回答

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) 

更好的解決方案(最終 - 目前只有以下工作的開發服務器上,由於一個bug(見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) 
+1

錯誤是dev_appserver執行的方式,不幸的是:正確的行爲是當前的生產行爲。 – 2010-07-27 08:46:36

+0

這看起來像一個有前途的解決方案,很好,很簡單。從文檔看來,我可以根據begin_ipnum和end_ipnum的值創建一個自定義模型屬性,用於報告列表值'live'。但據我所知,我無法使用GQL查詢定製屬性。所以我會首先嚐試一個計算ListProperty的值來創建對象的範圍。將報告我的票價! – tijs 2010-07-27 08:51:31

2

我的解決方案不符合你要求的模式,但我認爲它會在App Engine上運行良好。我使用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) 
+0

乾淨的想法。這可能通常需要對數據存儲進行多次往返 - 如果不需要進行擴展,或者如果此類查找不經常發生,則可以。 (每次往返將花費至少60-80毫秒)。 – 2010-07-26 22:12:18

+0

我的解決方案針對速度與您的解決方案交換存儲空間。假設我們考慮了所有地址,那麼您將擁有大約3B個列表項。 爲了減少我的往返行程,您可以一次對所有可能性進行IN查詢。 – cope360 2010-07-26 22:49:55

+0

IN查詢肯定會有助於減少運行時間 - 儘管這會導致非常昂貴的查詢,因爲IN查詢將被拆分爲[最多] 30個子查詢。 (如果您想要這樣的匹配,則返回結果中的內存中最長前綴匹配;與我的解決方案相同。)這就是說,雖然很貴,但可以在大型地址範圍內執行:)。 (+1) – 2010-07-26 23:21:58

0

這很奇怪,因爲我試圖找出相同的基本前提和運行到各種問題(以及基於130K記錄號,假設你使用的MaxMind國家地理文件)。雖然David的答案很容易,但在實踐中並不奏效。這來自Google:

首先,如果一個查詢在給定屬性上有多個不等式過濾器,那麼實體只會匹配該查詢,如果它具有與該不等式過濾器匹配的該屬性的單個值。例如,如果實體對屬性x的值爲[1,2],則它不會與查詢WHERE x> 1 AND x < 2匹配。每個過濾器都匹配x的值之一,但沒有單個值與兩個值匹配過濾器。

我批量上傳了創建listproperty字段的整個數據集以嘗試無效。

因此,任何額外的幫助將不勝感激!

+0

正如你可以從我更新的問題,我也無法讓它工作。我嘗試了其他一些方法,但很快遇到了其他問題,所以我暫時放棄了此項目的App引擎。 我不確定是否將任何答案標記爲正確,但因爲大衛似乎有最大的成功機會,我標記了那一個。怕我幫不了你。如果你想出來,請在這裏留下評論,以便我可以嘗試你的解決方案。 – tijs 2010-08-03 13:24:19

+0

p.s. stackoverflow規則決定你的問題應該已發佈爲評論而不是答案。只是讓你知道... :) – tijs 2010-08-03 13:25:31

相關問題