# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "google-cloud-datastore"
require "google/cloud/datastore/errors"
require "google/cloud/datastore/dataset"
require "google/cloud/datastore/transaction"
require "google/cloud/datastore/credentials"
module Google
module Cloud
##
# # Google Cloud Datastore
#
# Google Cloud Datastore is a fully managed, schemaless database for storing
# non-relational data. You should feel at home if you are familiar with
# relational databases, but there are some key differences to be aware of to
# make the most of using Datastore.
#
# The goal of google-cloud is to provide a API that is comfortable to
# Rubyists. Authentication is handled by {Google::Cloud#datastore}. You can
# provide the project and credential information to connect to the Datastore
# service, or if you are running on Google Compute Engine this configuration
# is taken care of for you.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new(
# project: "my-todo-project",
# keyfile: "/path/to/keyfile.json"
# )
#
# task = datastore.find "Task", "sampleTask"
# task["priority"] = 5
# datastore.save task
# ```
#
# You can learn more about various options for connection on the
# [Authentication
# Guide](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/guides/authentication).
#
# To learn more about Datastore, read the
# [Google Cloud Datastore Concepts Overview
# ](https://cloud.google.com/datastore/docs/concepts/overview).
#
# ## Retrieving records
#
# Records, called "entities" in Datastore, are retrieved by using a key.
# The key is more than a numeric identifier, it is a complex data structure
# that can be used to model relationships. The simplest key has a string
# kind value, and either a numeric id value, or a string
# name value. A single record can be retrieved by calling
# {Google::Cloud::Datastore::Dataset#find} and passing the parts of the key:
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task = datastore.find "Task", "sampleTask"
# ```
#
# Optionally, {Google::Cloud::Datastore::Dataset#find} can be given a key
# object:
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task_key = datastore.key "Task", 123456
# task = datastore.find task_key
# ```
#
# See {Google::Cloud::Datastore::Dataset#find}
#
# ## Querying records
#
# Multiple records can be found that match criteria.
# (See {Google::Cloud::Datastore::Query#where})
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# query = datastore.query("Task").
# where("done", "=", false)
#
# tasks = datastore.run query
# ```
#
# Records can also be ordered. (See {Google::Cloud::Datastore::Query#order})
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# query = datastore.query("Task").
# order("created")
#
# tasks = datastore.run query
# ```
#
# The number of records returned can be specified.
# (See {Google::Cloud::Datastore::Query#limit})
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# query = datastore.query("Task").
# limit(5)
#
# tasks = datastore.run query
# ```
#
# Records' key structures can also be queried.
# (See {Google::Cloud::Datastore::Query#ancestor})
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task_list_key = datastore.key "TaskList", "default"
#
# query = datastore.query("Task").
# ancestor(task_list_key)
#
# tasks = datastore.run query
# ```
#
# See {Google::Cloud::Datastore::Query} and
# {Google::Cloud::Datastore::Dataset#run}
#
# ### Paginating records
#
# All records may not return at once, but multiple calls can be made to
# Datastore to return them all.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# query = datastore.query("Task")
# tasks = datastore.run query
# tasks.all do |task|
# puts t["description"]
# end
# ```
#
# See {Google::Cloud::Datastore::Dataset::LookupResults} and
# {Google::Cloud::Datastore::Dataset::QueryResults}
#
# ## Creating records
#
# New entities can be created and persisted buy calling
# {Google::Cloud::Datastore::Dataset#save}. The entity must have a key to be
# saved. If the key is incomplete then it will be completed when saved.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task = datastore.entity "Task" do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 4
# t["description"] = "Learn Cloud Datastore"
# end
# task.key.id #=> nil
# datastore.save task
# task.key.id #=> 123456
# ```
#
# Multiple new entities may be created in a batch.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task1 = datastore.entity "Task" do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 4
# t["description"] = "Learn Cloud Datastore"
# end
#
# task2 = datastore.entity "Task" do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 5
# t["description"] = "Integrate Cloud Datastore"
# end
#
# tasks = datastore.save(task1, task2)
# task_key1 = tasks[0].key
# task_key2 = tasks[1].key
# ```
#
# Entities in Datastore form a hierarchically structured space similar to
# the directory structure of a file system. When you create an entity, you
# can optionally designate another entity as its parent; the new entity is a
# child of the parent entity.
#
# ```ruby
# task_key = datastore.key "Task", "sampleTask"
# task_key.parent = datastore.key "TaskList", "default"
#
# task = datastore.entity task_key do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 5
# t["description"] = "Integrate Cloud Datastore"
# end
# ```
#
# ## Setting properties
#
# Entities hold properties. A property has a name that is a string or
# symbol, and a value that is an object. Most value objects are supported,
# including String, Integer, Date, Time, and even other entity or key
# objects. Changes to the entity's properties are persisted by calling
# {Google::Cloud::Datastore::Dataset#save}.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task = datastore.find "Task", "sampleTask"
# # Read the priority property
# task["priority"] #=> 4
# # Write the priority property
# task["priority"] = 5
# # Persist the changes
# datastore.save task
# ```
#
# Array properties can be used to store more than one value.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task = datastore.entity "Task", "sampleTask" do |t|
# t["tags"] = ["fun", "programming"]
# t["collaborators"] = ["alice", "bob"]
# end
# ```
#
# ## Deleting records
#
# Entities can be removed from Datastore by calling
# {Google::Cloud::Datastore::Dataset#delete} and passing the entity object
# or the entity's key object.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task = datastore.find "Task", "sampleTask"
# datastore.delete task
# ```
#
# Multiple entities may be deleted in a batch.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task_key1 = datastore.key "Task", "sampleTask1"
# task_key2 = datastore.key "Task", "sampleTask2"
# datastore.delete task_key1, task_key2
# ```
#
# ## Transactions
#
# Complex logic can be wrapped in a Transaction. All queries and updates
# within the {Google::Cloud::Datastore::Dataset#transaction} block are run
# within the transaction scope, and will be automatically committed when the
# block completes.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task_key = datastore.key "Task", "sampleTask"
#
# datastore.transaction do |tx|
# if tx.find(task_key).nil?
# task = datastore.entity task_key do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 4
# t["description"] = "Learn Cloud Datastore"
# end
# tx.save task
# end
# end
# ```
#
# Alternatively, if no block is given the transaction object is returned
# allowing you to commit or rollback manually.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new
#
# task_key = datastore.key "Task", "sampleTask"
#
# tx = datastore.transaction
# begin
# if tx.find(task_key).nil?
# task = datastore.entity task_key do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 4
# t["description"] = "Learn Cloud Datastore"
# end
# tx.save task
# end
# tx.commit
# rescue
# tx.rollback
# end
# ```
#
# See {Google::Cloud::Datastore::Transaction} and
# {Google::Cloud::Datastore::Dataset#transaction}
#
# ## Querying metadata
#
# Datastore provides programmatic access to some of its metadata to support
# meta-programming, implementing backend administrative functions, simplify
# consistent caching, and similar purposes. The metadata available includes
# information about the entity groups, namespaces, entity kinds, and
# properties your application uses, as well as the property representations
# for each property.
#
# The special entity kind `__namespace__` can be used to find all the
# namespaces used in your application entities.
#
# ```ruby
# query = datastore.query("__namespace__").
# select("__key__").
# where("__key__", ">=", datastore.key("__namespace__", "g")).
# where("__key__", "<", datastore.key("__namespace__", "h"))
#
# namespaces = datastore.run(query).map do |entity|
# entity.key.name
# end
# ```
#
# The special entity kind `__kind__` can be used to return all the
# kinds used in your application.
#
# ```ruby
# query = datastore.query("__kind__").
# select("__key__")
#
# kinds = datastore.run(query).map do |entity|
# entity.key.name
# end
# ```
#
# Property queries return entities of kind `__property__` denoting the
# indexed properties associated with an entity kind. (Unindexed properties
# are not included.)
#
# ```ruby
# query = datastore.query("__property__").
# select("__key__")
#
# entities = datastore.run(query)
# properties_by_kind = entities.each_with_object({}) do |entity, memo|
# kind = entity.key.parent.name
# prop = entity.key.name
# memo[kind] ||= []
# memo[kind] << prop
# end
# ```
#
# Property queries support ancestor filtering on a `__kind__` or
# `__property__` key, to limit the query results to a single kind or
# property. The `property_representation` property in the entity
# representing property `p` of kind `k` is an array containing all
# representations of `p`'s value in any entity of kind `k`.
#
# ```ruby
# ancestor_key = datastore.key "__kind__", "Task"
# query = datastore.query("__property__").
# ancestor(ancestor_key)
#
# entities = datastore.run(query)
# representations = entities.each_with_object({}) do |entity, memo|
# property_name = entity.key.name
# property_types = entity["property_representation"]
# memo[property_name] = property_types
# end
# ```
#
# Property queries can also be filtered with a range over the
# pseudo-property `__key__`, where the keys denote either `__kind__` or
# `__property__` entities.
#
# ```ruby
# start_key = datastore.key "__property__", "priority"
# start_key.parent = datastore.key "__kind__", "Task"
# query = datastore.query("__property__").
# select("__key__").
# where("__key__", ">=", start_key)
#
# entities = datastore.run(query)
# properties_by_kind = entities.each_with_object({}) do |entity, memo|
# kind = entity.key.parent.name
# prop = entity.key.name
# memo[kind] ||= []
# memo[kind] << prop
# end
# ```
#
# ## Configuring timeout
#
# You can configure the request `timeout` value in seconds.
#
# ```ruby
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new timeout: 120
# ```
#
# ## The Cloud Datastore Emulator
#
# As of this release, the Cloud Datastore emulator that is part of the
# gcloud SDK is no longer compatible with google-cloud. This is because the
# gcloud SDK's Cloud Datastore emulator does not yet support gRPC as a
# transport layer.
#
# A gRPC-compatible emulator is available until the gcloud SDK Cloud
# Datastore emulator supports gRPC. To use it you must [download the gRPC
# emulator](https://storage.googleapis.com/gcd/tools/cloud-datastore-emulator-1.1.1.zip)
# and use the `cloud_datastore_emulator` script.
#
# When you run the Cloud Datastore emulator you will see a message similar
# to the following printed:
#
# ```
# If you are using a library that supports the DATASTORE_EMULATOR_HOST
# environment variable, run:
#
# export DATASTORE_EMULATOR_HOST=localhost:8978
# ```
#
# Now you can connect to the emulator using the `DATASTORE_EMULATOR_HOST`
# environment variable:
#
# ```ruby
# require "google/cloud/datastore"
#
# # Make Datastore use the emulator
# ENV["DATASTORE_EMULATOR_HOST"] = "localhost:8978"
#
# datastore = Google::Cloud::Datastore.new project: "emulator-project-id"
#
# task = datastore.entity "Task", "emulatorTask" do |t|
# t["type"] = "Testing"
# t["done"] = false
# t["priority"] = 5
# t["description"] = "Use Datastore Emulator"
# end
#
# datastore.save task
# ```
#
module Datastore
##
# Creates a new object for connecting to the Datastore service.
# Each call creates a new connection.
#
# For more information on connecting to Google Cloud see the
# [Authentication
# Guide](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/guides/authentication).
#
# @param [String] project Dataset identifier for the Datastore you are
# connecting to.
# @param [String, Hash] keyfile Keyfile downloaded from Google Cloud. If
# file path the file must be readable.
# @param [String, Array] scope The OAuth 2.0 scopes controlling
# the set of resources and operations that the connection can access.
# See [Using OAuth 2.0 to Access Google
# APIs](https://developers.google.com/identity/protocols/OAuth2).
#
# The default scope is:
#
# * `https://www.googleapis.com/auth/datastore`
# @param [Integer] timeout Default timeout to use in requests. Optional.
# @param [Hash] client_config A hash of values to override the default
# behavior of the API client. See Google::Gax::CallSettings. Optional.
#
# @return [Google::Cloud::Datastore::Dataset]
#
# @example
# require "google/cloud/datastore"
#
# datastore = Google::Cloud::Datastore.new(
# project: "my-todo-project",
# keyfile: "/path/to/keyfile.json"
# )
#
# task = datastore.entity "Task", "sampleTask" do |t|
# t["type"] = "Personal"
# t["done"] = false
# t["priority"] = 4
# t["description"] = "Learn Cloud Datastore"
# end
#
# datastore.save task
#
def self.new project: nil, keyfile: nil, scope: nil, timeout: nil,
client_config: nil
project ||= Google::Cloud::Datastore::Dataset.default_project
project = project.to_s # Always cast to a string
fail ArgumentError, "project is missing" if project.empty?
if ENV["DATASTORE_EMULATOR_HOST"]
return Google::Cloud::Datastore::Dataset.new(
Google::Cloud::Datastore::Service.new(
project, :this_channel_is_insecure,
host: ENV["DATASTORE_EMULATOR_HOST"],
client_config: client_config))
end
credentials = credentials_with_scope keyfile, scope
Google::Cloud::Datastore::Dataset.new(
Google::Cloud::Datastore::Service.new(
project, credentials, timeout: timeout,
client_config: client_config))
end
##
# @private
def self.credentials_with_scope keyfile, scope
if keyfile.nil?
Google::Cloud::Datastore::Credentials.default(scope: scope)
else
Google::Cloud::Datastore::Credentials.new(keyfile, scope: scope)
end
end
end
end
end