用Python使用Google應用服務引擎(GAE)隨手記錄

資料來源:http://code.google.com/intl/zh-TW/appengine/docs/python/runtime.html

==========

from google.appengine.runtime import DeadlineExceededError

class MainPage(webapp.RequestHandler):
def get(self):
try:
# Do stuff…

except DeadlineExceededError:
self.response.clear()
self.response.set_status(500)
self.response.out.write(“This operation could not be completed in time…”)

如果處理常式無法在第二個期限內傳回回應或引發例外狀況,處理常式將終止並傳回預設的錯誤回應。

/////

下列範例示範快取匯入模組的方式。因為單一網頁伺服器只會匯入一次 mymodule,全域 mymodule.counter 只會在伺服器第一次服務要求時初始化為 0。後續的要求會使用之前要求的值。

### mymodule.py
counter = 0
def increment():
global counter
counter += 1
return counter

### myhandler.py
import mymodule

print “Content-Type: text/plain”
print “”
print “My number: ” + str(mymodule.increment())

此輸出 My number: #:其中的 # 是此常式被處理要求的網頁伺服器呼叫的次數。

要快取常式指令碼,「應用服務引擎」必須能夠呼叫沒有引數的 main()。若處理常式指令碼沒有定義 main() 函式,或者 main() 函式需要引數 (預設不需要),則「應用服務引擎」會在每次要求都載入和評估整個指令碼。

///
下列範例與之前的範例的目的都相同,使用常式指令碼的全域環境:

### myhandler.py

# A global variable, cached between requests on this web server.
counter = 0

def main():
global counter
counter += 1
print “Content-Type: text/plain”
print “”
print “My number: ” + str(counter)

if __name__ == “__main__”:
main()

注意:小心不要在要求之間「洩露」使用者的資訊。使用全域變數時,請使用快取功能,並於 main() 常式中初始化要求特定的資料。

快取 main() 的應用程式可大幅縮短應用程式的回應時間。我們建議所有應用程式都使用此方式。

/////

「應用服務引擎」Python 執行階段環境,對 Python 標準程式庫的 logging 模組有特殊的支援,以瞭解記錄概念例如記錄等級 (debug – 偵錯、info – 資訊、warning – 警告、error – 錯誤、critical – 重大)。

import logging

from google.appengine.api import users
from google.appengine.ext import db

user = users.get_current_user()
if user:
q = db.GqlQuery(“SELECT * FROM UserPrefs WHERE user = :1”, user)
results = q.fetch(2)
if len(results) > 1:
logging.error(“more than one UserPrefs object for user %s”, str(user))
if len(results) == 0:
logging.debug(“creating UserPrefs object for user %s”, str(user))
userprefs = UserPrefs(user=user)
userprefs.put()
else:
userprefs = results[0]
else:
logging.debug(“creating dummy UserPrefs for anonymous user”)

/////

環境

執行環境包括幾個對應用程式有用的環境變數。部分環境變數是「應用服務引擎」特有的,而其他則為 CGI 標準的一部分。Python 程式碼可以使用 os.environ 字典來存取這些變數。

下列環境變數是「應用服務引擎」特有的:

APPLICATION_ID:目前正在執行之應用程式的 ID。
CURRENT_VERSION_ID:目前正在執行之應用程式的主要和次要版本,顯示為「X.Y」。應用程式的 app.yaml 檔案會指定主要版本號碼 (「X」)。而應用程式的各個版本上傳到「應用服務引擎」時,會自動設定次要版本號碼 (「Y」)。在開發網頁伺服器上,次要版本是「1」。
AUTH_DOMAIN:使用此網域來驗證有 Users API (使用者 API) 的使用者。於 appspot.com 主控的應用服務具有 gmail.com 的 AUTH_DOMAIN,並接受任何 Google 帳戶。使用「Google 應用服務」、於自訂網域主控的應用服務,具有等於自訂網域的 AUTH_DOMAIN。

下列環境變數是 CGI 標準的一部分,在「應用服務引擎」中有特殊行為:

SERVER_SOFTWARE:在開發網頁伺服器中,此值是 「Development/X.Y」,其中的 「X.Y」是執行階段的版本。

其他環境變數會根據 CGI 標準來設定。如需關於這些變數的詳細資訊,請參閱 CGI 標準。

提示:下列 webapp 要求處理常式會在瀏覽器中顯示應用程式可見的所有環境變數:

from google.appengine.ext import webapp
import os

class PrintEnvironmentHandler(webapp.RequestHandler):
def get(self):
for name in os.environ.keys():
self.response.out.write(“%s = %s<br />\n” % (name, os.environ[name]))

/////

限制 大小
要求大小 10 MB
回應大小 10 MB
要求持續時間 30 秒
同時動態要求 30 *
應用程式檔案數量上限 1,000
靜態檔案數量上限 1,000
應用程式檔案大小上限 10 MB
靜態檔案大小上限 10 MB
所有應用程式和靜態檔案的總計大小上限 150 MB

====

注意:Blobstore API 僅供已啟用計費的應用程式使用。您仍然會取得一定的免費配額,但必須啟用計費才能使用。
資源 預設免費配額 啟用計費後的預設配額
Blob 存放區儲存的資料 1 GB 1 GB 免費;無上限
資源 啟用計費後的預設配額
每日上限 速率上限
Blobstore API 呼叫次數 none 次呼叫 none 次呼叫/分鐘

====

Reference:
GAE 簡介: http://code.google.com/intl/zh-TW/appengine/docs/python/overview.html
WebApp: http://code.google.com/intl/zh-TW/appengine/docs/python/tools/webapp/
Djago: https://www.djangoproject.com/

///
http://code.google.com/intl/zh-TW/appengine/docs/python/tools/webapp/running.html

提示:「應用服務引擎」會根據應用程式的 app.yaml 檔案所指定的 URL 和對應關係,將要求傳送給 Python 指令碼。webapp WSGIApplication 進一步將特定 URL 路徑對應至要求處理常式。如何使用兩種對應方式,全靠您自行決定:您可以讓所有非靜態 URL 移至單一 Python 指令碼,並讓指令碼將所有動態 URL 分派給處理常式。或者,您可以將功能性群組為多個 WSGI 應用程式,由不同指令碼執行,並使用 app.yaml 將適當的 URL 對應至適當的應用程式。

///
要求資料

# <input name=”name” type=”text” />
name = self.request.get(“name”)

# <input name=”subscribe” type=”checkbox” value=”yes” />
subscribe_to_newsletter = self.request.get(“subscribe”, default_value=”no”)

# <select name=”favorite_foods” multiple=”true”>…</select>
favorite_foods = self.request.get(“favorite_foods”, allow_multiple=True)
for food in favorite_foods:
# …

….

favorite_foods = self.request.get_all(“favorite_foods”)
birth_year = self.request.get_range(“birth_year”,
min_value=1900,
max_value=datetime.datetime.utcnow().year,
default_value=1900)

///
建置回應
self.response.out.write(“<html><body><p>Hi there!</p></body></html>”)

error(code): 與呼叫 self.response.clear() 和 self.response.set_status(code) 相同。

///
重新導向、標頭和狀態碼
self.redirect(“/home”)
permanent=True 會使用永久的重新導向代碼

set_status(…) 方法會變更回應的狀態碼

///
http://code.google.com/intl/zh-TW/appengine/docs/python/tools/webapp/responseclass.html
Response 類別

import datetime

class MyRequestHandler(webapp.RequestHandler):
def get(self):
self.response.out.write(“<html><body>”)
self.response.out.write(“<p>Welcome to the Internet!</p>”)
self.response.out.write(“</body></html>”)

expires_date = datetime.datetime.utcnow() + datetime.timedelta(365)
expires_str = expires_date.strftime(“%d %b %Y %H:%M:%S GMT”)
self.response.headers.add_header(“Expires”, expires_str)

注意:在處理常式中操縱物件,不會與使用者溝通任何資料。特別是,這表示 webapp 不能像是在串流應用程式中一樣傳送資料給瀏覽器,然後再執行其他邏輯。(「應用服務引擎」應用程式不能將資料串流給瀏覽器,不論是否為 webapp。

若回應在 Content-Type 標頭中沒有指定字元集,則回應的字元集會自動設為 UTF-8。

不允許的 HTTP 回應標頭
基於安全性的理由,應用程式無法修改下列 HTTP 回應標頭。在 Response 物件的 headers 物件中設定這些項目,不會有任何作用。

Content-Encoding
Content-Length
Date
Server
Transfer-Encoding

//////
http://code.google.com/intl/zh-TW/appengine/docs/python/datastore/overview.html

利用 Python 建立資料模型

資料存放區實體「沒有結構」:兩個相同種類的實體不需具備相同屬性,而相同屬性也不需使用相同值類型。應用程式必須確保實體在需要時符合結構。因此,Python SDK 包含具備資料模型功能的 Rich 程式庫,可輕鬆強制執行結構描述。

在 Python API 中,模型會描述實體的種類,包括其屬性的類型和設定。應用程式使用 Python 類別來定義模型,並使用類別屬性 (attribute) 來描述屬性。某一種類的實體是由相應模型類別的實例所代表,並以實例屬性 (attribute) 代表屬性值。可經由呼叫類別的建構函式來建立實體,然後呼叫 put() 方法進行儲存。

import datetime
from google.appengine.ext import db
from google.appengine.api import users

class Employee(db.Model):
name = db.StringProperty(required=True)
role = db.StringProperty(required=True, choices=set([“executive”, “manager”, “producer”]))
hire_date = db.DateProperty()
new_hire_training_completed = db.BooleanProperty()
account = db.UserProperty()

e = Employee(name=””,
role=”manager”,
account=users.get_current_user())
e.hire_date = datetime.datetime.now()
e.put()

Datastore API (資料存放區 API) 提供兩種查詢介面:查詢物件介面以及類似 SQL 的查詢語言 (稱為 GQL)。查詢會以模型類別實例的型式傳回實體,可以修改和放回資料存放區。

training_registration_list = [users.User(“Alfred.Smith@example.com”),
users.User(“jharrison@example.com”),
users.User(“budnelson@example.com”)]
employees_trained = db.GqlQuery(“SELECT * FROM Employee WHERE account IN :1″,
training_registration_list)
for e in employees_trained:
e.new_hire_training_completed = True
db.put(e)

///
http://code.google.com/intl/zh-TW/appengine/docs/python/datastore/keysandentitygroups.html

class Story(db.Model):
title = db.StringProperty()
author = db.StringProperty()

每個實體都有識別項。應用程式可以指派其本身的識別項,用於金鑰之中,只要指定實例建構函式的 key_name 引數 (為 str 值) 即可:

s = Story(key_name=”xzy123”)

若未指定 key_name,則實體第一次儲存在資料存放區時,會指派數字 ID。

s2 = Story() # s2 does not have a name or an ID.
s2.put() # s2 is given an ID by the datastore.

///
http://code.google.com/intl/zh-TW/appengine/docs/python/datastore/modelclass.html

每個實體都有金鑰,它是代表該實體的獨特識別項。實體可以具有選擇性的金鑰,此字串在整個指定的種類實體中是唯一的。Key.from_path() 和 Model.get_by_key_name() 方法可以用實體的種類和名稱來抓取實體。如需關於金鑰的詳細資訊,請參閱金鑰和實體群組。

方法 Model.get_or_insert() 可以用來抓取可能不存在的實體,並視需要在資料存放區中建立它:

keyname = “some_key”
s = Story.get_or_insert(keyname, title=”The Three Little Pigs”)

注意:Model 實例第一次明確地或透過 Model.get_or_insert() 進行 put()之後,在資料存放區中才會有相應的實體。

///
http://code.google.com/intl/zh-TW/appengine/docs/python/datastore/entitiesandmodels.html

from google.appengine.api import users

pet = Pet(name=”Fluffy”,
type=”cat”,
owner=users.get_current_user())
pet.weight_in_pounds = 24

因為驗證是發生在建構實例時,任何設定為必要的屬性必須在建構函式中初始化。在此範例中,name、type 以及 owner 都是必要值,所以必須在建構函式中指定其初始值。weight_in_pounds 不是模型所必需,所以一開始的時候未指派,而是稍後才進行指派。

///
多型態模型

下列範例定義 Contact 類別,以及 Contact 的子類別 Person 與 Company 類別:

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

class Contact(polymodel.PolyModel):
phone_number = db.PhoneNumberProperty()
address = db.PostalAddressProperty()

class Person(Contact):
first_name = db.StringProperty()
last_name = db.StringProperty()
mobile_number = db.PhoneNumberProperty()

class Company(Contact):
name = db.StringProperty()
fax_number = db.PhoneNumberProperty()

此模型會確保所有 Person 實體和所有 Company 實體具備 phone_number 和 address 屬性,且查詢 Contact 實體可傳回 Person 或 Company 實體。只有 Person 實體具備 mobile_number 屬性。

子類別和其他模型類別一樣都可以被具現化:

p = Person(phone_number=’1-206-555-9234′,
address=’123 First Ave., Seattle, WA, 98101′,
first_name=’Alfred’,
last_name=’Smith’,
mobile_number=’1-206-555-0117′)
p.put()

c = Company(phone_number=’1-503-555-9123′,
address=’P.O. Box 98765, Salem, OR, 97301′,
name=’Data Solutions, LLC’,
fax_number=’1-503-555-6622′)
c.put()

查詢 Contact 實體可傳回 Contact、Person 或 Company 的例項。下列程式碼會列印上方建立之兩個實體的資訊:

for contact in Contact.all():
print ‘Phone: %s\nAddress: %s\n\n’
% (contact.phone,
contact.address))

///
StringProperty

class MyModel(db.Model):
string = db.StringProperty()

obj = MyModel()

# Python Unicode literal syntax fully describes characters in a text string.
obj.string = u”kittens”

# unicode() converts a byte string to a Unicode value using the named codec.
obj.string = unicode(“kittens”, “latin-1”)

# A byte string is assumed to be text encoded as ASCII (the ‘ascii’ codec).
obj.string = “kittens”

# Short string properties can be used in query filters.
results = db.GqlQuery(“SELECT * FROM MyModel WHERE string = :1″, u”kittens”)

///
TextProperty

class MyModel(db.Model):
text = db.TextProperty()

obj = MyModel()

# Text() can take a Unicode value.
obj.text = db.Text(u”lots of kittens”)

# Text() can take a byte string and the name of an encoding.
obj.text = db.Text(“lots of kittens”, “latin-1”)

# If no encoding is specified, a byte string is assumed to be ASCII text.
obj.text = db.Text(“lots of kittens”)

# Text properties can store large values.
obj.text = db.Text(open(“a_tale_of_two_cities.txt”).read(), “utf-8″)

///
參考資料

class FirstModel(db.Model):
prop = db.IntegerProperty()

class SecondModel(db.Model):
reference = db.ReferenceProperty(FirstModel)

obj1 = FirstModel()
obj1.prop = 42
obj1.put()

obj2 = SecondModel()

# A reference value is the key of another entity.
obj2.reference = obj1.key()

# Assigning a model instance to a property uses the entity’s key as the value.
obj2.reference = obj1
obj2.put()

刪除實體時,其金鑰若為參考屬性的值,則不會變更參考屬性。參考屬性值可以是不再有效的金鑰。若應用程式預期參考可能無效,則應用程式可以使用 if 陳述式來測試物件是否存在:

obj1 = obj2.reference

if not obj1:
# Referenced entity was deleted.

///
code.google.com/intl/zh-TW/appengine/docs/python/datastore/creatinggettinganddeletingdata.html

建立和更新實體
Model (和 Expando) 類別的實例代表資料存放區實體。藉由呼叫與指定種類的新實體相應之模型類別的建構函式,應用程式會建立該指定種類的新實體。

pet = Pet(name=”Fluffy”,
type=”cat”,
owner=users.get_current_user())

實例第一次「放置」後,才會在資料存放區建立新實體 (可以在實例上呼叫 put() 方法,或將實例傳遞給 db.put() 函式)。

pet.put()

db.put(pet)

如果實例已經儲存,put() 方法會更新現有的實體。

查詢將結果以模型實例傳回。這些實例可以修改再放回資料存放區。

if users.get_current_user():
user_pets = db.GqlQuery(“SELECT * FROM Pet WHERE owner = :1”,
users.get_current_user())
for pet in user_pets:
pet.spayed_or_neutered = True

db.put(user_pets)

使用查詢取得實體

資料存放區可以橫跨指定種類的實體執行查詢。 查詢可以使用條件篩選結果,而實體屬性值必須符合該條件,而且可以傳回按照屬性值排序的結果。查詢也可以將實體的範圍限制為具有指定上階的實體;請參閱金鑰和實體群組。

若要取得查詢運作的完整描述,包括查詢無法執行的事情,請參閱查詢和索引。

資料存放區 API 提供下列兩個介面,在實體屬性上執行查詢:Query,在查詢物件上使用方法準備查詢的介面,以及 GqlQuery,使用類似 SQL 查詢語言 (稱為 GQL) 的介面。
Query 介面

all() 方法 (位於 Model 或 Expando) 類別傳回 Query 物件,該物件代表相應種類的所有實體。應用程式藉由呼叫物件的 filter()、order() 以及 ancestor() 方法來準備查詢。

class Story(db.Model):
title = db.StringProperty()
date = db.DateTimeProperty()

query = Story.all()

query.filter(‘title =’, ‘Foo’)
query.order(‘-date’)
query.ancestor(key)

# These methods can be chained together on one line.
query.filter(‘title =’, ‘Foo’).order(‘-date’).ancestor(key)

GqlQuery 介面

GqlQuery 類別建構函式使用 GQL 查詢字串和選用的參數繫結。查詢字串指定種類、篩選器、排序順序以及上階條件。查詢字串也可以包括結果限制和位移。

# Parameters can be bound with positional arguments.
query = db.GqlQuery(“SELECT * FROM Story WHERE title = :1 ”
“AND ANCESTOR IS :2 ”
“ORDER BY date DESC”,
‘Foo’, key)

# Or, parameters can be bound with keyword arguments.
query = db.GqlQuery(“SELECT * FROM Story WHERE title = :title ”
“AND ANCESTOR IS :parent ”
“ORDER BY date DESC”,
title=’Foo’, parent=key)

# String, number and Boolean values can be literal values in the string.
query = db.GqlQuery(“SELECT * FROM Story WHERE title = ‘Foo’ ”
“AND ANCESTOR IS :parent ”
“ORDER BY date DESC”,
parent=key)

Model 類別的 gql() 類別方法也準備來自字串的 GqlQuery 物件。該字串是 GQL 查詢字串省略了 SELECT * FROM Model,因為這個部分是內建的。

query = Story.gql(“WHERE title = :title ”
“AND ANCESTOR IS :parent ”
“ORDER BY date DESC”,
title=’Foo’, parent=key)

參數繫結可以使用 bind() 方法,重新繫結至新值。應用程式可以透過重新繫結參數和重新執行查詢,便可重新使用 GqlQuery 物件。
執行查詢和存取結果

應用程式嘗試存取結果時,才會執行 Query 和 GqlQuery 物件。應用程式存取結果時,查詢會成為查詢之模型類別的實例,載入記憶體。兩種查詢類別都提供兩種方式來執行查詢和存取結果:fetch() 方法,以及 iterator 介面。

fetch() 方法接受要擷取結果的最大數量 (限制,以及選用的跳過結果數量 (位移)。方法會執行查詢,然後擷取結果,直到擷取的限制,或者已經沒有其他結果。結果一旦載入記憶體,它會跳過位移 (若有指定),然後將要求的結果以模型實例的清單傳回。每次呼叫 fetch(),都會執行完整的查詢。

注意:位移不會影響從資料存放區擷取的結果數量。限制以內的所有結果,都會被擷取並放置於記憶體。位移只會影響 fetch() 方法傳回的內容。

results = query.fetch(10)
for result in results:
print “Title: ” + result.title

指定給 fetch() 方法的限制和位移,會覆寫 GQL 查詢字串中指定的任何限制和位移。

若查詢物件是用來當做 iterator,則查詢會以沒有限制或位移的方式來執行,結果會載入記憶體,而傳回的值會是結果的 iterator。iterator 會產生模型類別的實例。

for result in query:
print “Title: ” + result.title

注意:資料存放區在回應查詢時,最多只會傳回 1000 個結果,不論用來擷取結果的限制和位移為何。1000 個結果包括使用位移跳過的任何結果,因此查詢的結果若超過 1000 個,並使用位移 100,將傳回 900 個結果。

刪除實體

應用程式可以使用模型實例或 Key,從資料存放區刪除實體。模型實例的 delete() 方法,會從資料存放區刪除相應的實體。delete() 函式取得 Key 或 Key 清單,並從資料存放區刪除實體。

q = db.GqlQuery(“SELECT * FROM Message WHERE create_date < :1”, earliest_date)
results = q.fetch(10)
for result in results:
result.delete()

# or…

q = db.GqlQuery(“SELECT * FROM Message WHERE create_date < :1”, earliest_date)
results = q.fetch(10)
db.delete(results)

刪除實體不會變更資料存放區中可能參考實體的任何 Key 值。若您的應用程式嘗試為已刪除的實體解除參考 Key 值,則應用程式應使用 db.get() 執行此動作,然後在存取屬性之前,先測試傳回值。

刪除其他實體上階的實體,不會影響其他實體。只要應用程式並非依賴上階的存在,為子系實體建立金鑰,則應用程式仍然可以存取子系。

///
http://code.google.com/intl/zh-TW/appengine/docs/python/datastore/queriesandindexes.html

查詢簡介

class Person(db.Model):
first_name = db.StringProperty()
last_name = db.StringProperty()
city = db.StringProperty()
birth_year = db.IntegerProperty()
height = db.IntegerProperty()

# The Query interface prepares a query using instance methods.
q = Person.all()
q.filter(“last_name =”, “Smith”)
q.filter(“height <“, 72)
q.order(“-height”)

# The GqlQuery interface prepares a query using a GQL query string.
q = db.GqlQuery(“SELECT * FROM Person ” +
“WHERE last_name = :1 AND height < :2 ” +
“ORDER BY height DESC”,
“Smith”, 72)

# The query is not executed until results are accessed.
results = q.fetch(5)
for p in results:
print “%s %s, %d inches tall” % (p.first_name, p.last_name, p.height)

篩選器包括屬性名稱、比較運算子及值。如果篩選器具有特定名稱的屬性,且其值可與運算子所描述的特定值比較,則實體會符合篩選器。如果實體符合所有的篩選器,則實體會是查詢的結果

篩選運算子可以是下列其中之一:

< 小於
<= 小於或等於
= 等於
> 大於
>= 大於或等於
!= 不等於
IN 等於所提供清單中的任何值。

!= 運算子實際上會執行 2 個查詢:一個是其他篩選器保持原狀,但以小於篩選器取代不等於篩選器,另一個會以大於篩選器取代不等於篩選器。結果會按照順序合併。就像下方討論的不等式篩選器一樣,一個查詢只能有一個不等於篩選器,這種查詢不能有其他的不等式篩選器。

IN 運算子也會執行多個查詢,一個是針對提供之清單值的所有項目使用相同的篩選器,但以等於篩選器取代 IN 篩選器。結果會按照清單中的項目順序合併。如果查詢超過 IN 篩選器,則查詢會以多個查詢的方式來執行,一次對 IN 中的值組合執行一個查詢。

包含 != 和 IN 運算子的單一查詢限制是 30 個子查詢。

索引簡介

對於每個應用程式執行的查詢動作,「應用服務引擎」資料存放區都會編出索引。如果應用程式變更了資料存放區實體,資料存放區就會以正確的結果來更新索引。應用程式執行查詢時,資料存放區會直接從相應的索引擷取結果。

針對查詢中使用的種類、篩選器屬性和運算子、排序順序的組合,應用程式都有索引。請參考 GQL 當中的查詢範例:

SELECT * FROM Person WHERE last_name = “Smith”
AND height < 72
ORDER BY height DESC

此查詢的索引是 Person 種類之實體的金鑰表格,而欄中是 height 的值和 last_name 的屬性。索引會按照 height 以遞減方式排序。

相同格式,但不同篩選值的兩個查詢,會使用相同的索引。例如,下列查詢使用的索引,與上述的查詢相同:

SELECT * FROM Person WHERE last_name = “Jones”
AND height < 63
ORDER BY height DESC

資料存放區使用下列步驟來執行查詢:

資料存放區會識別與查詢種類、篩選器屬性、篩選運算子以及排序順序等相應的索引。
資料存放區開始掃描索引,找出使用查詢篩選值、符合所篩選器條件的第一個索引。
資料存放區持續掃描索引,傳回每個實體,直到找出下一個不符合篩選器條件的實體,或接觸索引的末端,或已收集查詢所要求的最大結果數量。

索引表格包含篩選器或排序順序所使用之每個屬性的欄。列會按照下列各項依順序排序:

上階
等式篩選器使用的屬性值
不等式篩選器使用的屬性值
排序順序使用的屬性值

注意:根據索引的目的,IN 篩選器會像 = 篩選器一樣來處理,而 != 篩選器則會像其他不等式篩選器來處理。

這樣會將使用此索引之每個可能查詢的所有結果,放在表格中的連續列中。

提示:「查詢篩選器」沒有明確的方式可以比對字串值的某一部分,但是您可以使用不等式篩選器進行擬字首比對:

db.GqlQuery(“SELECT * FROM MyModel WHERE prop >= :1 AND prop < :2”, “abc”, u”abc” + u”\ufffd”)

這會比對每個 MyModel 實體,並找出 prop 字串屬性的前置字元為 abc 的實體。Unicode 字串 u”\ufffd” 代表 Unicode 字元的最大可能值。在排序索引中的屬性值時,落入此範圍的值即是所有擁有指定前置字元的值。

此機制支援的查詢種類十分廣泛,因此也適合大部分應用程式。不過,此機制並不支援您在其他資料庫技術所慣用的部分查詢種類。

查詢不會傳回不具有篩選器屬性的實體

實體必須有索引所參考的全部屬性,才會包含在索引中。如果實體沒有索引所參考的屬性,則實體不會出現在索引中,也就不可能會出現在使用該索引的查詢結果。

請注意,「應用服務引擎」資料存放區會區別沒有屬性以及屬性值為空值的實體 (None)。如果您要查詢同一種類的所有實體,可以使用負責指派預設值 (例如 None) 至查詢篩選器所使用之屬性的資料模型。
不會索引 Text 和 Blob 值

長文字類型 (Text) 或大型二進位類型 (Blob) 屬性值的屬性不會納入索引,因此無法提供查詢。

不會索引這些屬性值的結果,就是在屬性使用篩選器或排序順序的查詢,不會比對屬性值為 Text 或 Blob 的實體。具有這類值的屬性,就像是設定與查詢篩選器和排序順序無關。
混合類型的屬性值會依類型排序

如果兩個實體的屬性擁有相同名稱,但是屬性值的類型不同,則屬性的索引會先依照屬性值類型排序實體,再依照每個類型適用的順序排序。舉例來說,如果兩個實體均擁有「年齡」屬性,但是其中一個的屬性值為整數,而另一個的屬性值為字串,則使用「年齡」屬性排序時,擁有整數值的實體會排在擁有字串值的實體前面,無論兩者屬性值為何。

當兩個實體的屬性值分別為整數和浮點數時,請特別留意,因為這兩種類型的差別較難以辨識,可是在資料存放區中確實為不同的類型。整數的順序比浮點高,因此擁有整數值 38 的屬性會排在擁有浮點值 37.5 的屬性前面。

使用設定檔定義索引

「應用服務引擎」按照預設,會為每個簡單的查詢建置索引。針對其他查詢,應用程式必須在設定檔中指定它需要的索引,此設定檔的名稱為 index.yaml。 若「應用服務引擎」下的應用程式嘗試執行查詢,卻沒有相應的索引 (不論是預設所提供或 index.yaml 所描述的),則查詢將會失敗。

「應用服務引擎」會為下列格式的查詢提供自動索引:

僅使用等式及上階篩選器的查詢
僅使用不等式篩選器 (這只能是單一屬性) 的查詢
僅使用一個遞增或遞減順序排列一個屬性的查詢

其他型式的查詢都必須在 index.yaml 中指定其索引,包括:

使用多個排序順序的查詢
使用一個遞減順序排列多個金鑰的查詢
使用一或多個不等式篩選器篩選一個屬性並使用一或多個等式篩選器篩選其他屬性的查詢
使用不等式和上階篩選器的查詢

開發網頁伺服器使得索引設定的管理更加輕鬆:需要索引的查詢如果沒有索引將無法執行,但是開發網頁伺服器可以產生索引的設定檔,成功地執行查詢。本機測試應用程式時,如果呼叫了應用程式可能執行的所有查詢 (各種種類、上階、篩選器和排序順序的組合),則產生的項目將代表一組完整的索引。如果測試並未執行每種可能的查詢格式,您可以在上傳應用程式之前,檢視並修改索引的設定檔。

「應用服務引擎」按照預設,會為每個簡單的查詢建置索引。針對其他查詢,應用程式必須在設定檔中指定它需要的索引,此設定檔的名稱為 index.yaml。 若「應用服務引擎」下的應用程式嘗試執行查詢,卻沒有相應的索引 (不論是預設所提供或 index.yaml 所描述的),則查詢將會失敗。

index.yaml 描述每個索引表格,包括其種類、查詢篩選器和排序順序所需的屬性,以及查詢是否使用上階子句 (可以是 Query.ancestor() 或 GQL ANCESTOR IS 子句)。 屬性會以它們的排序順序列出:首先是等式或 IN 篩選器使用的屬性,接著是不等式篩選器使用的屬性,然後是查詢結果排列順序及其方向。

再次看看下方的範例查詢:

SELECT * FROM Person WHERE last_name = “Smith”
AND height < 72
ORDER BY height DESC

若應用程式只執行此查詢 (可能還有類似的查詢,只是 “Smith” 和 72 的值不同),則 index.yaml 檔案看起來會像這樣:

indexes:
– kind: Person
properties:
– name: last_name
– name: height
direction: desc

實體建立或更新時,也會更新每個適當的索引。套用至實體的索引數量,會影響建立或更新實體的時間。

如需關於 index.yaml 語法的詳細資訊,請參閱設定索引。

///
不等式篩選器中的屬性,必須在其他排序順序前先進行排序

如果某查詢同時具有含不等式比較的篩選器以及一或多個排序順序,則查詢必須包含該不等式的屬性排序順序,而該排序順序必須出現在其他屬性的排序順序之前。

這個查詢是「無效」的,因為它使用不等式篩選器,卻沒有依照篩選的屬性排序:

SELECT * FROM Person WHERE birth_year >= :min_year
ORDER BY last_name # ERROR

同樣地,這個查詢也是無效的,因為在使用其他屬性排序之前,它沒有先依照篩選的屬性排序:

SELECT * FROM Person WHERE birth_year >= :min_year
ORDER BY last_name, birth_year # ERROR

這個查詢是有效的:

SELECT * FROM Person WHERE birth_year >= :min_year
ORDER BY birth_year, last_name

為取得符合不等式篩選器的所有結果,查詢會掃描索引表格,找到第一個符合的列,再傳回所有連續的結果,直到找到不符合的列為止。如要使用連續列做為完整的結果集,這些列必須在其他排序順序之前,先依照不等式篩選器排序。

///
大型實體和過量的索引

如上所述,每個實體的每個屬性 (不含 Text 或 Blob 值者) 都會被新增到至少一個索引表格,包括預設提供的簡單索引,以及應用程式參考屬性的 index.yaml 檔案所描述的任何索引。對於每個屬性都有單一個值的實體,「應用服務引擎」的簡單索引會儲存其屬性值一次,而每當自訂索引參考該屬性時,會再儲存一次。每次屬性值變更時,所有索引項目都必須更新,因此如果參考同一個屬性的索引增加,則該屬性的更新時間也會拉長。

為了防止實體的更新時間太長,資料存放區會限制單一實體可以擁有的索引項目數量。此限制訂得很寬鬆,因此大部分應用程式都不會有這個問題。然而,仍有部分情況可能會達到上限。例如實體具有非常多的單一值屬性,就可能超出索引項目限制。

擁有多個值的屬性會將每個值以個別項目儲存在索引中。因此,即使實體只有一個屬性,只要該屬性擁有多個值,仍可能會超過索引項目限制。

自訂索引如果參考多個多重值屬性,則只要幾個值就可能變得非常龐大。如要完整記錄這類屬性,索引表格必須為每個屬性的「每個屬性值排列」提供一列資料列。

例如,下列索引 (以 index.yaml 語法描述) 包括 x 和 y 屬性供種類 MyModel 的實體使用:

indexes:
– kind: MyModel
properties:
– name: x
– name: y

下列程式碼建立的實體,具有 x 屬性的兩個值以及 y 屬性的兩個值:

class MyModel(db.Expando):
pass

e2 = MyModel()
e2.x = [‘red’, ‘blue’]
e2.y = [1, 2]
e2.put()

如要正確呈現這些值,索引必須儲存 12 個屬性值:x 和 y 的內建索引分別使用 2 個,以及自訂索引中的 x 和 y 共 4 個排列分別使用 2 個。由於多重值屬性的值很多,因此索引可能需要為單一的實體儲存大量索引項目。您可以把參考多個多重值屬性的索引稱為「過量索引」,因為只要幾個屬性值,該索引就可能變得非常龐大。

如果 put() 導致索引項目的數量超過上限,則呼叫會失敗,並發生例外狀況。在您建立的新索引中,如果有部分索引項目都超出建立實體時的上限,就無法查詢該索引;而在「管理控制台」中,索引會顯示為「錯誤」狀態。

若要處理「錯誤」的索引,請先將它們從 index.yaml 檔案中移除,並執行 appcfg.py vacuum_indexes。然後,重新規劃索引定義和相關查詢,或者移除導致索引「過量」的實體。最後,將索引新增回 index.yaml,並執行 appcfg.py update_indexes。

只要避免查詢需要使用清單屬性的自訂索引,就可以避免過量索引的發生。如上所述,這包括使用多個排序順序的查詢、混合使用等式和不等式篩選器的查詢,以及使用上階篩選器的查詢。

///
http://code.google.com/intl/zh-TW/appengine/docs/python/datastore/gqlreference.html

GQL 參考資料

GQL 語法的摘要如下:

SELECT * FROM <kind>
[WHERE <condition> [AND <condition> …]]
[ORDER BY <property> [ASC | DESC] [, <property> [ASC | DESC] …]]
[LIMIT [<offset>,]<count>]
[OFFSET <offset>]

<condition> := <property> {< | <= | > | >= | = | != } <value>
<condition> := <property> IN <list>
<condition> := ANCESTOR IS <entity or key>

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.