中繼資料

附註:我們強烈建議建構新應用程式的開發人員使用 NDB 用戶端程式庫,因為 NDB 用戶端程式庫與本用戶端程式庫相較之下有幾個優點,例如能透過 Memcache API 自動將實體加入快取。如果您目前使用的是舊版的 DB 用戶端程式庫,請參閱從 DB 至 NDB 的遷移指南

Datastore 針對部分中繼資料提供程式化的存取機制,進而支援元程式設計、實作後端管理函式、簡化一致性快取等類似目的;舉例來說,您可以將其用於為應用程式建構自訂 Datastore 檢視器。這些中繼資料包括實體群組、命名空間、實體種類、應用程式使用的屬性,以及各個屬性的屬性表示法等相關資訊。

另外在 Google Cloud 主控台的 Datastore 資訊主頁也提供應用程式的部分中繼資料,但該處顯示的資料與這些函式傳回的資料在某些重要層面有所不同。

  • 更新間隔。使用 API 讀取中繼資料會傳回最新資料,資訊主頁中的資料每天只更新一次。
  • 內容。無法透過 API 讀取資訊主頁中的某些中繼資料;反之亦然。
  • 速度。中繼資料的取得與查詢作業計費方式與 Datastore 取得與查詢作業相同。擷取命名空間、種類及屬性相關資訊的中繼資料查詢作業,執行速度通常較慢。根據經驗法則,單次中繼資料查詢傳回 N 個實體所需的時間,大約相當於 N 次一般查詢每次傳回單一實體的加總時間。再者,屬性表示法查詢 (非僅限金鑰屬性查詢) 的速度比僅限金鑰屬性查詢慢。實體群組中繼資料的中繼資料取得作業,比取得一般實體更快。

輔助函式

以下函式可取得中繼資料資訊:

  • get_entity_group_version() 可取得實體群組的版本號碼;這個函式適用於識別自您上次取得版本號碼後,群組中的任何實體是否有任何變更。
  • get_namespaces() 會傳回清單,其中包含應用程式所有或指定範圍的命名空間名稱。
  • get_kinds() 會傳回清單,其中包含應用程式所有或指定範圍的實體種類名稱。
  • get_properties_of_kind() 會傳回清單,其中包含與指定實體種類相關聯的應用程式所有 (或指定範圍的) 已建立索引的屬性名稱。不含未建立索引的屬性。
  • get_representations_of_kind() 會傳回字典,其中包含與指定實體種類相關聯的應用程式所有或指定範圍已建立索引的屬性表示法。這個字典將各個屬性的名稱與該屬性的表示法清單相互對應。不含未建立索引的屬性。

實體群組中繼資料

Cloud Datastore 提供實體群組的「版本」存取,每次變更實體群組,僅為正值的版本數字一定會增加。

以下範例顯示如何取得實體群組的版本:

from google.appengine.ext import db
from google.appengine.ext.db import metadata

class Simple(db.Model):
  x = db.IntegerProperty()

entity1 = Simple(x=11)
entity1.put()

# Print entity1's entity group version
print 'version', metadata.get_entity_group_version(entity1)

# Write to a different entity group
entity2 = Simple(x=22)
entity2.put()

# Will print the same version, as entity1's entity group has not changed
print 'version', metadata.get_entity_group_version(entity1)

# Change entity1's entity group by adding a new child entity
entity3 = Simple(x=33, parent=entity1.key())
entity3.put()

# Will print a higher version, as entity1's entity group has changed
print metadata.get_entity_group_version(entity1)

舊版行為

在舊版實體群組版本行為中,實體群組的版本數字僅會隨實體群組的變更而增加。舉例來說,舊版實體群組中繼資料行為,可用來確保實體群組複雜祖系查詢快取的一致性。

以下範例會快取查詢結果 (相符結果數),並藉由實體群組版本的舊版行為來使用最新的快取值:

from google.appengine.api import memcache
from google.appengine.ext import db
from google.appengine.ext.db import metadata

def count_entity_group(entity_group_key):
  """Count the entities in the specified entity group."""
  # Check if we have a cached version of the current entity group count
  cached = memcache.get(str(entity_group_key))
  if cached:
    (version, count) = cached
    # Is the cached value for the current version?
    if version == metadata.get_entity_group_version(entity_group_key):
      return count

  def tx():
    # Need to actually count entities. Using a transaction to get a consistent
    # count and entity group version.
    count = db.Query(keys_only=True).ancestor(entity_group_key).count(limit=5000)
    # Cache the count and the entity group version
    version = metadata.get_entity_group_version(entity_group_key)
    memcache.set(str(entity_group_key), (version, count))
    return count

  return db.run_in_transaction(tx)

get_entity_group_version() 可能會針對從未寫入的實體群組傳回 None

對於包含 __version__ 屬性的特殊虛擬實體呼叫 get(),可取得實體群組版本。詳情請參閱 EntityGroup 的參考說明文件。

中繼資料查詢

如果上節所述的輔助函式不符合您的需求,您可以透過明確的中繼資料查詢發出更詳細或彈性的中繼資料要求。在 Python 中,這類查詢的模型類別是在 google.appengine.ext.db.metadata 套件中定義。這些模型可提供中繼資料查詢專用的特殊實體種類:

模型類別 實體種類
Namespace __namespace__
Kind __kind__
Property __property__

這些模型與種類不會與應用程式中可能已存在的同名模型與種類發生衝突。您可以藉由查詢這些特殊種類,而擷取包含所需中繼資料的實體。

中繼資料查詢傳回的實體是根據 Datastore 現狀而動態產生的。雖然您可以建立 NamespaceKindProperty 模型類別的本機執行個體,但嘗試將這些執行個體儲存於 Datastore 時會發生 BadRequestError 例外狀況的失敗。

您可以使用屬於下列兩個類別的查詢物件來發出中繼資料查詢:

  • 類別方法 Namespace.all()Kind.all()Property.all() 傳回的 Query 物件 (繼承自父類別方法 Model.all())
  • GQL 樣式查詢的 GqlQuery 物件

以下範例會傳回應用程式所有實體種類的名稱:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Kind

for k in Kind.all():
  print "kind: '%s'" % k.kind_name

命名空間查詢

如果應用程式使用 Namespaces API,您可以使用「命名空間查詢」來找出在應用程式的實體中使用的所有命名空間。您可以透過這種方式執行活動,例如跨多個命名空間的管理功能。

命名空間查詢會傳回特殊種類 __namespace__ 的實體,其索引鍵名稱就是命名空間的名稱。(由空字串 "" 指定的預設命名空間則為例外:空字串並非有效的索引鍵名稱,因此這個命名空間會改以數字 ID 1 當做索引鍵)。這類型的查詢僅支援對特殊虛擬屬性 __key__ 的範圍進行篩選,其屬性值為實體索引鍵。結果可依照 __key__ 的值遞增排序 (不可遞減排序)。__namespace__ 實體沒有屬性,因此僅限索引鍵和非僅限索引鍵的查詢,都會傳回相同資訊。

命名空間實體是 google.appengine.ext.db.metadata.Namespace 模型類別的例項。從實體金鑰運算得出的 namespace_name 字串屬性,會傳回對應命名空間的名稱。(如果金鑰有數字 ID 1,屬性會傳回空白字串)。為加快查詢作業,Namespace 模型提供下列類別方法:

舉例來說,以下是輔助函式 get_namespaces() 的實作方式,它會傳回清單,其中包含應用程式所有命名空間的名稱 (或兩個指定名稱 startend 之間的範圍):

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Namespace

def get_namespaces(start=None, end=None):

  # Start with unrestricted namespace query
  q = Namespace.all()

  # Limit to specified range, if any
  if start is not None:
    q.filter('__key__ >=', Namespace.key_for_namespace(start))
  if end is not None:
    q.filter('__key__ <', Namespace.key_for_namespace(end))

  # Return list of query results
  return [ns.namespace_name for ns in q]

種類查詢

「種類查詢」會傳回種類 __kind__ 的實體,其索引鍵名稱為實體種類名稱。這種類型的查詢間接受限於目前的命名空間,且僅支援限定 __key__ 虛擬屬性範圍的篩選作業。可按照 __key__ 的值遞增排序結果 (不可遞減排序)。__kind__ 的實體沒有屬性,因此僅限索引鍵和並非僅限索引鍵的查詢,都會傳回相同資訊。

種類實體為 google.appengine.ext.db.metadata.Kind 模型類別的例項。從實體金鑰運算得出的 kind_name 字串屬性,會傳回對應實體種類的名稱。為加快查詢作業,Kind 模型提供下列類別方法:

舉例來說,以下是輔助函式 get_kinds() 的實作方式,這個函式會傳回清單,其中包含應用程式所有或指定範圍的實體種類名稱 (或兩個指定名稱 startend 之間的範圍):

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Kind

def get_kinds(start=None, end=None):

  # Start with unrestricted kind query
  q = Kind.all()

  # Limit to specified range, if any
  if start is not None and start != '':
    q.filter('__key__ >=', Kind.key_for_kind(start))
  if end is not None:
    if end == '':
      return []        # Empty string is not a valid kind name, so can't filter
    q.filter('__key__ <', Kind.key_for_kind(end))

  # Return list of query results
  return [k.kind_name for k in q]

以下範例可輸出名稱開頭為小寫字母的所有種類:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Kind

# Start with unrestricted kind query
q = Kind.all()

# Limit to lowercase initial letters
q.filter('__key__ >=', Kind.key_for_kind('a'))
endChar = chr(ord('z') + 1)                        # Character after 'z'
q.filter('__key__ <', Kind.key_for_kind(endChar))

# Print query results
for k in q:
  print k.kind_name

屬性查詢

「屬性查詢」會傳回表示實體種類相關屬性的 __property__ 種類實體 (無論目前是否已在這個種類的模型中定義這些屬性)。代表「K」種類的「P」屬性的實體建構方式如下:

  • 實體的索引鍵具有種類 __property__ 及索引鍵名稱「P」
  • 父系實體的金鑰具有 __kind__ 種類,且金鑰名稱為「K」

屬性實體是 google.appengine.ext.db.metadata.Property 模型類別的例項。從實體金鑰運算得出的 kind_nameproperty_name 字串屬性,會傳回對應種類和屬性的名稱。Property 模型提供四種類別方法,可簡化建構與檢查 __property__ 鍵的作業:

以下範例說明這些方法:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

class Employee(db.Model):
  name = db.StringProperty()
  ssn = db.IntegerProperty()

employee_key = Property.key_for_kind("Employee")
employee_name_key = Property.key_for_property("Employee", "Name")

Property.key_to_kind(employee_key)           # Returns "Employee"
Property.key_to_property(employee_name_key)  # Returns "Name"

屬性查詢的行為取決於其為僅限金鑰非僅限金鑰 (屬性表示法) 的查詢,詳細說明如以下子節所述。

屬性查詢:僅限金鑰

「僅限金鑰的屬性查詢」會針對指定實體種類的各個已建立索引的屬性傳回金鑰 (不含未建立索引的屬性)。以下範例會輸出應用程式所有實體種類的名稱,以及與各個實體種類相關屬性的名稱:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

# Create unrestricted keys-only property query
q = Property.all(keys_only=True)

# Print query results
for p in q:
  print "%s: %s" % (Property.key_to_kind(p), Property.key_to_property(p))

這種類型的查詢間接受限於目前的命名空間,且僅支援限定 __key__ 虛擬屬性範圍的篩選作業。其中金鑰代表 __kind____property__ 實體。可按照 __key__ 的值遞增排序結果 (不可遞減排序)。種類與屬性組合會套用篩選,先依照種類排序,再依照屬性排序;舉例來說,假設您有具下列屬性的實體:

  • 種類 Account 及其下列屬性
    • balance
    • company
  • 種類 Employee 及其下列屬性
    • name
    • ssn
  • 種類 Invoice 及其下列屬性
    • date
    • amount
  • 種類 Manager 及其下列屬性
    • name
    • title
  • 種類 Product 及其下列屬性
    • description
    • price

要傳回屬性資料的查詢應如下所示:

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

# Start with unrestricted keys-only property query
q = Property.all(keys_only=True)

# Limit range
q.filter('__key__ >=', Property.key_for_property("Employee", "salary"))
q.filter('__key__ <=', Property.key_for_property("Manager", "salary"))

# Print query results
for p in q:
  print "%s: %s" % (Property.key_to_kind(p), Property.key_to_property(p))

上述查詢將傳回下列內容:

Employee: ssn
Invoice: date
Invoice: amount
Manager: name

請注意,結果不包含 Employee 種類的 name 屬性和 Manager 種類的 title 屬性,也不包含 AccountProduct 種類的所有屬性,因為這些項目不在查詢指定的範圍內。

屬性查詢另外也支援以 __kind____property__ 索引鍵進行祖系篩選,藉此將查詢結果限制為單一種類或屬性。舉例來說,您可以使用屬性查詢取得與指定實體種類相關的屬性,如以下範例所示:

(輔助函式 get_properties_of_kind() 的實作程序)

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

def get_properties_of_kind(kind, start=None, end=None):

  # Start with unrestricted keys-only property query
  q = Property.all(keys_only=True)

  # Limit to specified kind
  q.ancestor(Property.key_for_kind(kind))

  # Limit to specified range, if any
  if start is not None and start != '':
    q.filter('__key__ >=', Property.key_for_property(kind, start))
  if end is not None:
    if end == '':
      return []     # Empty string is not a valid property name, so can't filter
    q.filter('__key__ <', Property.key_for_property(kind, end))

  # Return list of query results
  return [Property.key_to_property(p) for p in q]

屬性查詢:非僅限金鑰 (屬性表示法)

非僅限金鑰的屬性查詢,也稱為「屬性表示法查詢」,會傳回各個種類屬性組合使用的表示法詳細資訊 (不含未建立索引的屬性)。「K」種類的「P」屬性傳回的實體具有的金鑰,與對應的僅限金鑰查詢 (搭配會傳回屬性表示法的其他 property_representation 屬性) 傳回的金鑰相同。這個屬性的值是 StringListProperty 類別的例項,其中包含每個實體屬性 P 的表示法字串,這些實體屬性可在任何 K 類型實體中找到。

請注意,表示法跟屬性類別不同;同時可以有多個屬性類別對應至相同的表示法 (例如,StringPropertyPhoneNumberProperty 都使用 STRING 表示法)。

下表列出屬性類別分別對應的表示法:

屬性類別 表示法
IntegerProperty INT64
FloatProperty DOUBLE
BooleanProperty BOOLEAN
StringProperty STRING
ByteStringProperty STRING
DateProperty INT64
TimeProperty INT64
DateTimeProperty INT64
GeoPtProperty POINT
PostalAddressProperty STRING
PhoneNumberProperty STRING
EmailProperty STRING
UserProperty USER
IMProperty STRING
LinkProperty STRING
CategoryProperty STRING
RatingProperty INT64
ReferenceProperty
SelfReferenceProperty
REFERENCE
blobstore.BlobReferenceProperty STRING
ListProperty 清單元素的表示法
StringListProperty 清單元素的表示法

舉例來說,以下是輔助函式 get_representations_of_kind() 的實作方式,該函式會傳回字典,其中包含與指定實體種類相關聯的應用程式所有或指定範圍已建立索引的屬性表示法 (或兩個指定名稱 startend 之間的範圍)。這個字典將各個屬性的名稱與該屬性的表示法清單相互對應。

from google.appengine.ext import db
from google.appengine.ext.db.metadata import Property

def get_representations_of_kind(kind, start=None, end=None):

  # Start with unrestricted non-keys-only property query
  q = Property.all()

  # Limit to specified kind
  q.ancestor(Property.key_for_kind(kind))

  # Limit to specified range, if any
  if start is not None and start != '':
    q.filter('__key__ >=', Property.key_for_property(kind, start))
  if end is not None:
    if end == '':
      return []     # Empty string is not a valid property name, so can't filter
    q.filter('__key__ <', Property.key_for_property(kind, end))

  # Initialize result dictionary
  result = {}

  # Add query results to dictionary
  for p in q:
    result[p.property_name] = p.property_representation

  # Return dictionary
  return result