# Copyright 2016 Google LLC # # 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 # # https://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-spanner" require "google/cloud/spanner/project" require "google/cloud/config" require "google/cloud/env" module Google module Cloud ## # # Cloud Spanner # # Cloud Spanner is a fully managed, mission-critical, relational database # service that offers transactional consistency at global scale, schemas, # SQL (ANSI 2011 with extensions), and automatic, synchronous replication # for high availability. # # For more information about Cloud Spanner, read the [Cloud # Spanner Documentation](https://cloud.google.com/spanner/docs/). # # The goal of google-cloud is to provide an API that is comfortable to # Rubyists. Your authentication credentials are detected automatically in # Google Cloud Platform environments such as Google Compute Engine, Google # App Engine and Google Kubernetes Engine. In other environments you can # configure authentication easily, either directly in your code or via # environment variables. Read more about the options for connecting in the # [Authentication # Guide](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/guides/authentication). # # ## Enabling Logging # # To enable logging for this library, set the logger for the underlying # [gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The # logger that you set may be a Ruby stdlib # [`Logger`](https://ruby-doc.org/stdlib-2.5.0/libdoc/logger/rdoc/Logger.html) # as shown below, or a # [`Google::Cloud::Logging::Logger`](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/google-cloud-logging/latest/google/cloud/logging/logger) # that will write logs to [Stackdriver # Logging](https://cloud.google.com/logging/). See # [grpc/logconfig.rb](https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/logconfig.rb) # and the gRPC # [spec_helper.rb](https://github.com/grpc/grpc/blob/master/src/ruby/spec/spec_helper.rb) # for additional information. # # Configuring a Ruby stdlib logger: # # ```ruby # require "logger" # # module MyLogger # LOGGER = Logger.new $stderr, level: Logger::WARN # def logger # LOGGER # end # end # # # Define a gRPC module-level logger method before grpc/logconfig.rb loads. # module GRPC # extend MyLogger # end # ``` # # ## Creating instances # # When you first use Cloud Spanner, you must create an instance, which is an # allocation of resources that are used by Cloud Spanner databases. When you # create an instance, you choose where your data is stored and how many # nodes are used for your data. (For more information, see [Instance # Configuration](https://cloud.google.com/spanner/docs/instance-configuration)). # # Use {Spanner::Project#create_instance} to create an instance: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # job = spanner.create_instance "my-instance", # name: "My Instance", # config: "regional-us-central1", # nodes: 5, # labels: { production: :env } # # job.done? #=> false # job.reload! # API call # job.done? #=> true # # if job.error? # status = job.error # else # instance = job.instance # end # ``` # # ## Creating databases # # Now that you have created an instance, you can create a database. Cloud # Spanner databases hold the tables and indexes that allow you to read and # write data. You may create multiple databases in an instance. # # Use {Spanner::Project#create_database} (or # {Spanner::Instance#create_database}) to create a database: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # job = spanner.create_database "my-instance", "my-database" # # job.done? #=> false # job.reload! # API call # job.done? #=> true # # if job.error? # status = job.error # else # database = job.database # end # ``` # # ## Updating database schemas # # Cloud Spanner supports schema updates to a database while the database # continues to serve traffic. Schema updates do not require taking the # database offline and they do not lock entire tables or columns; you can # continue writing data to the database during the schema update. # # Use {Spanner::Database#update} to execute one or more statements in Cloud # Spanner's Data Definition Language (DDL): # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # database = spanner.database "my-instance", "my-database" # # add_users_table_sql = %q( # CREATE TABLE users ( # id INT64 NOT NULL, # username STRING(25) NOT NULL, # name STRING(45) NOT NULL, # email STRING(128), # ) PRIMARY KEY(id) # ) # # database.update statements: [add_users_table_sql] # ``` # # ## Creating clients # # In order to read and/or write data, you must create a database client. # You can think of a client as a database connection: All of your # interactions with Cloud Spanner data must go through a client. Typically # you create a client when your application starts up, then you re-use that # client to read, write, and execute transactions. # # Use {Spanner::Project#client} to create a client: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # db = spanner.client "my-instance", "my-database" # # results = db.execute "SELECT 1" # # results.rows.each do |row| # puts row # end # ``` # # ## Writing data # # You write data using your client object. The client object supports # various mutation operations, as well as combinations of inserts, updates, # deletes, etc., that can be applied atomically to different rows and/or # tables in a database. # # Use {Spanner::Client#commit} to execute various mutations atomically at a # single logical point in time. All changes are accumulated in memory until # the block completes. Unlike {Spanner::Client#transaction}, which can also # perform reads, this operation accepts only mutations and makes a single # API request. # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # db = spanner.client "my-instance", "my-database" # # db.commit do |c| # c.update "users", [{ id: 1, username: "charlie94", name: "Charlie" }] # c.insert "users", [{ id: 2, username: "harvey00", name: "Harvey" }] # end # ``` # # ## Querying data using SQL # # Cloud Spanner supports a native SQL interface for reading data that is # available through {Spanner::Client#execute}: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # db = spanner.client "my-instance", "my-database" # # results = db.execute "SELECT * FROM users" # # results.rows.each do |row| # puts "User #{row[:id]} is #{row[:name]}" # end # ``` # # ## Reading data using the read method # # In addition to Cloud Spanner's SQL interface, Cloud Spanner also supports # a read interface. Use the {Spanner::Client#read} method to read rows from # the database, and use its `keys` option to pass unique identifiers as both # lists and ranges: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # db = spanner.client "my-instance", "my-database" # # results = db.read "users", [:id, :name], keys: 1..5 # # results.rows.each do |row| # puts "User #{row[:id]} is #{row[:name]}" # end # ``` # # ## Using read-write transactions # # When an operation might write data depending on values it reads, you # should use a read-write transaction to perform the reads and writes # atomically. # # Suppose that sales of `Albums(1, 1)` are lower than expected and you want # to move $200,000 from the marketing budget of `Albums(2, 2)` to it, but # only if the budget of `Albums(2, 2)` is at least $300,000. # # Use {Spanner::Client#transaction} to execute both reads and writes # atomically at a single logical point in time. All changes are accumulated # in memory until the block completes. Transactions will be automatically # retried when possible. This operation makes separate API requests to begin # and commit the transaction. # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # db = spanner.client "my-instance", "my-database" # # db.transaction do |tx| # # Read the second album budget. # second_album_result = tx.read "Albums", ["marketing_budget"], # keys: [[2, 2]], limit: 1 # second_album_row = second_album_result.rows.first # second_album_budget = second_album_row.values.first # # transfer_amount = 200000 # # if second_album_budget < 300000 # # Raising an exception will automatically roll back the transaction. # raise "The second album doesn't have enough funds to transfer" # end # # # Read the first album's budget. # first_album_result = tx.read "Albums", ["marketing_budget"], # keys: [[1, 1]], limit: 1 # first_album_row = first_album_result.rows.first # first_album_budget = first_album_row.values.first # # # Update the budgets. # second_album_budget -= transfer_amount # first_album_budget += transfer_amount # puts "Setting first album's budget to #{first_album_budget} and the " \ # "second album's budget to #{second_album_budget}." # # # Update the rows. # rows = [ # {singer_id: 1, album_id: 1, marketing_budget: first_album_budget}, # {singer_id: 2, album_id: 2, marketing_budget: second_album_budget} # ] # tx.update "Albums", rows # end # ``` # # ## Using read-only transactions # # Suppose you want to execute more than one read at the same timestamp. # Read-only transactions observe a consistent prefix of the transaction # commit history, so your application always gets consistent data. Because # read-only transactions are much faster than locking read-write # transactions, we strongly recommend that you do all of your transaction # reads in read-only transactions if possible. # # Use a {Spanner::Snapshot} object to execute statements in a read-only # transaction. The snapshot object is available via a block provided to # {Spanner::Client#snapshot}: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # db = spanner.client "my-instance", "my-database" # # db.snapshot do |snp| # results_1 = snp.execute "SELECT * FROM users" # results_1.rows.each do |row| # puts "User #{row[:id]} is #{row[:name]}" # end # # # Perform another read using the `read` method. Even if the data # # is updated in-between the reads, the snapshot ensures that both # # return the same data. # results_2 = db.read "users", [:id, :name] # results_2.rows.each do |row| # puts "User #{row[:id]} is #{row[:name]}" # end # end # ``` # # ## Deleting databases # # Use {Spanner::Database#drop} to delete a database: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # database = spanner.database "my-instance", "my-database" # # database.drop # ``` # # ## Deleting instances # # When you delete an instance, all databases within it are automatically # deleted. (If you only delete databases and not your instance, you will # still incur charges for the instance.) Use {Spanner::Instance#delete} to # delete an instance: # # ```ruby # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # # instance = spanner.instance "my-instance" # # instance.delete # ```` # module Spanner ## # Creates a new object for connecting to the Spanner 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_id Project identifier for the Spanner service # you are connecting to. If not present, the default project for the # credentials is used. # @param [String, Hash, Google::Auth::Credentials] credentials The path to # the keyfile as a String, the contents of the keyfile as a Hash, or a # Google::Auth::Credentials object. (See {Spanner::Credentials}) # @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 scopes are: # # * `https://www.googleapis.com/auth/spanner` # * `https://www.googleapis.com/auth/spanner.data` # @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. Optional. # @param [String] project Alias for the `project_id` argument. Deprecated. # @param [String] keyfile Alias for the `credentials` argument. # Deprecated. # # @return [Google::Cloud::Spanner::Project] # # @example # require "google/cloud/spanner" # # spanner = Google::Cloud::Spanner.new # def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, client_config: nil, project: nil, keyfile: nil project_id ||= (project || default_project_id) project_id = project_id.to_s # Always cast to a string raise ArgumentError, "project_id is missing" if project_id.empty? scope ||= configure.scope timeout ||= configure.timeout client_config ||= configure.client_config credentials ||= (keyfile || default_credentials(scope: scope)) unless credentials.is_a? Google::Auth::Credentials credentials = Spanner::Credentials.new credentials, scope: scope end Spanner::Project.new( Spanner::Service.new( project_id, credentials, timeout: timeout, client_config: client_config ) ) end ## # Configure the Google Cloud Spanner library. # # The following Spanner configuration parameters are supported: # # * `project_id` - (String) Identifier for a Spanner project. (The # parameter `project` is considered deprecated, but may also be used.) # * `credentials` - (String, Hash, Google::Auth::Credentials) The path to # the keyfile as a String, the contents of the keyfile as a Hash, or a # Google::Auth::Credentials object. (See {Spanner::Credentials}) (The # parameter `keyfile` is considered deprecated, but may also be used.) # * `scope` - (String, Array) The OAuth 2.0 scopes controlling # the set of resources and operations that the connection can access. # * `timeout` - (Integer) Default timeout to use in requests. # * `client_config` - (Hash) A hash of values to override the default # behavior of the API client. # # @return [Google::Cloud::Config] The configuration object the # Google::Cloud::Spanner library uses. # def self.configure yield Google::Cloud.configure.spanner if block_given? Google::Cloud.configure.spanner end ## # @private Default project. def self.default_project_id Google::Cloud.configure.spanner.project_id || Google::Cloud.configure.project_id || Google::Cloud.env.project_id end ## # @private Default credentials. def self.default_credentials scope: nil Google::Cloud.configure.spanner.credentials || Google::Cloud.configure.credentials || Spanner::Credentials.default(scope: scope) end end end end