# Copyright 2017 Google, Inc
#
# 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_relative "./spec_helper"

describe "Google Cloud Spanner API samples" do
  before :each do
    cleanup_database_resources
  end

  after :each do
    cleanup_database_resources
    cleanup_instance_resources
  end

  example "create_instance" do
    instance_id = "rb-test-#{seed}"
    @created_instance_ids << instance_id

    capture do
      create_instance project_id:  @project_id,
                      instance_id: instance_id
    end

    expect(captured_output).to include(
      "Waiting for create instance operation to complete"
    )
    expect(captured_output).to include(
      "Created instance #{instance_id}"
    )
  end

  example "create_instance_with_processing_units" do
    instance_id = "rb-test-pu-#{seed}"
    @created_instance_ids << instance_id

    capture do
      create_instance_with_processing_units project_id: @project_id,
                                            instance_id: instance_id
    end

    expect(captured_output).to include(
      "Waiting for creating instance operation to complete"
    )
    expect(captured_output).to include(
      "Instance #{instance_id} has 500 processing units."
    )
  end

  example "create_database" do
    expect(@instance.databases.map(&:database_id)).not_to include @database_id

    capture do
      create_database project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: @database_id
    end

    expect(captured_output).to include(
      "Waiting for create database operation to complete"
    )
    expect(captured_output).to include(
      "Created database #{@database_id} on instance #{@instance.instance_id}"
    )

    @test_database = @instance.database @database_id
    expect(@test_database).not_to be nil

    data_definition_statements = @test_database.ddl

    expect(data_definition_statements.size).to eq 2

    expect(data_definition_statements).to include(match "CREATE TABLE Singers")
    expect(data_definition_statements).to include(match "CREATE TABLE Albums")
  end

  example "create_database_with_version_retention_period" do
    expect(@instance.databases.map(&:database_id)).not_to include @database_id

    capture do
      create_database_with_version_retention_period project_id:  @project_id,
                                                    instance_id: @instance.instance_id,
                                                    database_id: @database_id
    end

    expect(captured_output).to include(
      "Waiting for create database operation to complete"
    )
    expect(captured_output).to include(
      "Created database #{@database_id} on instance #{@instance.instance_id}"
    )
    expect(captured_output).to include(
      "\tVersion retention period: 7d"
    )

    @test_database = @instance.database @database_id
    expect(@test_database).not_to be nil

    data_definition_statements = @test_database.ddl

    expect(data_definition_statements.size).to eq 3

    expect(data_definition_statements).to include(match "CREATE TABLE Singers")
    expect(data_definition_statements).to include(match "CREATE TABLE Albums")
    expect(data_definition_statements).to include(match "version_retention_period = '7d'")
  end

  example "create_database_with_encryption_key" do
    expect(@instance.databases.map(&:database_id)).not_to include @database_id

    kms_key_name = "projects/#{@project_id}/locations/us-central1/keyRings/spanner-test-keyring/cryptoKeys/spanner-test-cmek"

    capture do
      create_database_with_encryption_key project_id:  @project_id,
                                          instance_id: @instance.instance_id,
                                          database_id: @database_id,
                                          kms_key_name: kms_key_name
    end

    expect(captured_output).to include(
      "Database #{@database_id} created with encryption key #{kms_key_name}"
    )
  end

  example "create database with multiple KMS keys" do
    capture do
      create_database_with_multiple_kms_keys project_id: @project_id,
                                             instance_id: @mr_instance_id,
                                             database_id: @database_id,
                                             kms_key_names: @kms_key_names
    end

    expect(captured_output).to include("Database #{@database_id}")
    expect(captured_output).to include("#{@kms_key_names[0]}")
    expect(captured_output).to include("#{@kms_key_names[1]}")
    expect(captured_output).to include("#{@kms_key_names[2]}")
  end

  example "create table with timestamp column" do
    database = create_singers_albums_database

    expect(@instance.databases.map(&:database_id)).to include @database_id

    capture do
      create_table_with_timestamp_column project_id:  @project_id,
                                         instance_id: @instance.instance_id,
                                         database_id: @database_id
    end

    expect(captured_output).to include(
      "Waiting for update database operation to complete"
    )
    expect(captured_output).to include(
      "Created table Performances in #{@database_id}"
    )

    data_definition_statements = database.ddl force: true
    expect(data_definition_statements.size).to eq 3
    expect(data_definition_statements.last).to include "CREATE TABLE Performances"
  end

  example "insert data" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 0
    expect(client.execute("SELECT * FROM Albums").rows.count).to  eq 0

    expect {
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    }.to output("Inserted data\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 5
    expect(singers.find { |s| s[:FirstName] == "Catalina" }).not_to be nil

    albums = client.execute("SELECT * FROM Albums").rows.to_a
    expect(albums.count).to eq 5
    expect(albums.find { |s| s[:AlbumTitle] == "Go, Go, Go" }).not_to be nil
  end

  example "insert data with timestamp column" do
    database = create_singers_albums_database
    create_performances_table
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Performances").rows.count).to eq 0

    expect {
      insert_data_with_timestamp_column project_id:  @project_id,
                                        instance_id: @instance.instance_id,
                                        database_id: database.database_id
    }.to output("Inserted data\n").to_stdout

    performances = client.execute("SELECT * FROM Performances").rows.to_a
    expect(performances.count).to eq 3
  end

  example "query data" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      query_data project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end

    expect(captured_output).to include "1 1 Total Junk"
    expect(captured_output).to include "1 2 Go, Go, Go"
    expect(captured_output).to include "2 1 Green"
    expect(captured_output).to include "2 2 Forever Hold Your Peace"
    expect(captured_output).to include "2 3 Terrified"
  end

  example "query with struct" do
    database = create_singers_albums_database

    capture do
      write_struct_data project_id:  @project_id,
                        instance_id: @instance.instance_id,
                        database_id: database.database_id
    end

    capture do
      query_with_struct project_id:  @project_id,
                        instance_id: @instance.instance_id,
                        database_id: database.database_id
    end
    expect(captured_output).to match(/6/)
  end

  example "query with array of struct" do
    database = create_singers_albums_database

    capture do
      write_struct_data project_id:  @project_id,
                        instance_id: @instance.instance_id,
                        database_id: database.database_id
    end

    capture do
      query_with_array_of_struct project_id:  @project_id,
                                 instance_id: @instance.instance_id,
                                 database_id: database.database_id
    end
    expect(captured_output).to include "8"
    expect(captured_output).to include "7"
    expect(captured_output).to include "6"
  end

  example "query struct field" do
    database = create_singers_albums_database

    capture do
      write_struct_data project_id:  @project_id,
                        instance_id: @instance.instance_id,
                        database_id: database.database_id
    end

    capture do
      query_struct_field project_id:  @project_id,
                         instance_id: @instance.instance_id,
                         database_id: database.database_id
    end
    expect(captured_output).to match(/6/)
  end

  example "query nested struct field" do
    database = create_singers_albums_database

    capture do
      write_struct_data project_id:  @project_id,
                        instance_id: @instance.instance_id,
                        database_id: database.database_id
    end

    capture do
      query_nested_struct_field project_id:  @project_id,
                                instance_id: @instance.instance_id,
                                database_id: database.database_id
    end
    expect(captured_output).to match(/6\nImagination\n9\nImagination/)
  end

  example "query data with timestamp column" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      update_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      add_timestamp_column project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id

      update_data_with_timestamp_column project_id:  @project_id,
                                        instance_id: @instance.instance_id,
                                        database_id: database.database_id
    end

    capture do
      query_data_with_timestamp_column project_id:  @project_id,
                                       instance_id: @instance.instance_id,
                                       database_id: database.database_id
    end

    expect(captured_output).to match(/1 1 100000 \d+/)
  end

  example "read data" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      read_data project_id:  @project_id,
                instance_id: @instance.instance_id,
                database_id: database.database_id
    end

    expect(captured_output).to include "1 1 Total Junk"
    expect(captured_output).to include "1 2 Go, Go, Go"
    expect(captured_output).to include "2 1 Green"
    expect(captured_output).to include "2 2 Forever Hold Your Peace"
    expect(captured_output).to include "2 3 Terrified"
  end

  example "delete data" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      delete_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      read_data project_id:  @project_id,
                instance_id: @instance.instance_id,
                database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 0
    expect(client.execute("SELECT * FROM Albums").rows.count).to eq 0
  end

  example "read stale data" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      read_stale_data project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: database.database_id
    end

    expect(captured_output).not_to include "1 1 Total Junk"
    expect(captured_output).not_to include "1 2 Go, Go, Go"
    expect(captured_output).not_to include "2 1 Green"
    expect(captured_output).not_to include "2 2 Forever Hold Your Peace"
    expect(captured_output).not_to include "2 3 Terrified"

    sleep 16 # read_stale_data expects staleness of at least 15 seconds

    capture do
      read_stale_data project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: database.database_id
    end

    expect(captured_output).to include "1 1 Total Junk"
    expect(captured_output).to include "1 2 Go, Go, Go"
    expect(captured_output).to include "2 1 Green"
    expect(captured_output).to include "2 2 Forever Hold Your Peace"
    expect(captured_output).to include "2 3 Terrified"
  end

  example "create index" do
    database = create_singers_albums_database

    expect(database.ddl(force: true).join).not_to include(
      "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"
    )

    capture do
      create_index project_id:  @project_id,
                   instance_id: @instance.instance_id,
                   database_id: database.database_id
    end

    expect(captured_output).to include "Waiting for database update to complete"
    expect(captured_output).to include "Added the AlbumsByAlbumTitle index"

    expect(database.ddl(force: true).join).to include(
      "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"
    )
  end

  example "create storing index" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end

    expect(database.ddl(force: true).join).not_to include(
      "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)"
    )

    capture do
      create_storing_index project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    expect(captured_output).to include "Waiting for database update to complete"
    expect(captured_output).to include "Added the AlbumsByAlbumTitle2 storing index"

    expect(database.ddl(force: true).join).to include(
      "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)"
    )
  end

  example "add column" do
    database = create_singers_albums_database

    expect(database.ddl(force: true).join).not_to include(
      "MarketingBudget INT64"
    )

    capture do
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end

    expect(database.ddl(force: true).join).to include(
      "MarketingBudget INT64"
    )
  end

  example "add column timestamp column" do
    database = create_singers_albums_database

    expect(database.ddl(force: true).join).not_to include(
      "MarketingBudget INT64"
    )

    capture do
      add_timestamp_column project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    expect(database.ddl(force: true).join).to include(
      "LastUpdateTime TIMESTAMP"
    )
  end

  example "update data" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    # Add MarketingBudget column (re-use add_column to add)
    capture do
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end

    albums = client.execute("SELECT * FROM Albums").rows.map(&:to_h)
    expect(albums).to include(
      SingerId: 1, AlbumId: 1, AlbumTitle: "Total Junk", MarketingBudget: nil
    )

    capture do
      update_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    expect(captured_output).to include "Updated data"

    albums = client.execute("SELECT * FROM Albums").rows.map(&:to_h)
    expect(albums).to include(
      SingerId: 1, AlbumId: 1, AlbumTitle: "Total Junk", MarketingBudget: 100_000
    )
  end

  example "update data with timestamp column" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Add Timestamp column
      add_timestamp_column project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    albums = client.execute("SELECT * FROM Albums").rows.map(&:to_h)
    expect(albums).to include(
      SingerId: 1, AlbumId: 1, AlbumTitle: "Total Junk", MarketingBudget: nil, LastUpdateTime: nil
    )

    capture do
      update_data_with_timestamp_column project_id:  @project_id,
                                        instance_id: @instance.instance_id,
                                        database_id: database.database_id
    end

    expect(captured_output).to include "Updated data"

    albums = client.execute("SELECT * FROM Albums").rows.map(&:to_h)
    expect(albums).not_to include(
      LastUpdateTime: nil
    )
  end

  example "query data with new column" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Add data to MarketingBudget column (re-use update_data to populate)
      update_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      query_data_with_new_column project_id:  @project_id,
                                 instance_id: @instance.instance_id,
                                 database_id: database.database_id
    end

    expect(captured_output).to include "1 1 100000"
    expect(captured_output).to include "1 2"
    expect(captured_output).to include "2 1"
    expect(captured_output).to include "2 2 500000"
    expect(captured_output).to include "2 3"
  end

  example "read/write transaction (successful transfer)" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Second Album(2, 2) needs at least $200,000 to transfer successfully
      # to Album(1, 1). This should transfer successfully.
      client.commit do |c|
        c.update "Albums", [
          { SingerId: 1, AlbumId: 1, MarketingBudget: 100_000 },
          { SingerId: 2, AlbumId: 2, MarketingBudget: 500_000 }
        ]
      end
    end

    capture do
      read_write_transaction project_id:  @project_id,
                             instance_id: @instance.instance_id,
                             database_id: database.database_id
    end

    expect(captured_output).to include "Transaction complete"

    first_album  = client.read("Albums", [:MarketingBudget], keys: [[1, 1]]).rows.first
    second_album = client.read("Albums", [:MarketingBudget], keys: [[2, 2]]).rows.first

    expect(first_album[:MarketingBudget]).to  eq 300_000
    expect(second_album[:MarketingBudget]).to eq 300_000
  end

  example "read/write transaction (not enough funds)" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Second Album(2, 2) needs at least $200,000 to transfer successfully
      # to Album(1, 1). Without enough funds, an exception should be raised.
      client.commit do |c|
        c.update "Albums", [
          { SingerId: 1, AlbumId: 1, MarketingBudget: 100_000 },
          { SingerId: 2, AlbumId: 2, MarketingBudget: 199_999 }
        ]
      end
    end

    expect {
      read_write_transaction project_id:  @project_id,
                             instance_id: @instance.instance_id,
                             database_id: database.database_id
    }.to raise_error("The second album does not have enough funds to transfer")
  end

  example "query data with index" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Add index on Albums(AlbumTitle) (re-use create_index to add)
      create_index project_id:  @project_id,
                   instance_id: @instance.instance_id,
                   database_id: database.database_id
    end

    capture do
      query_data_with_index project_id:  @project_id,
                            instance_id: @instance.instance_id,
                            database_id: database.database_id
    end

    expect(captured_output).to include "2 Go, Go, Go"
    expect(captured_output).to include "2 Forever Hold Your Peace"
  end

  example "read data with index" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Add index on Albums(AlbumTitle) (re-use create_index to add)
      create_index project_id:  @project_id,
                   instance_id: @instance.instance_id,
                   database_id: database.database_id
    end

    capture do
      read_data_with_index project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    expect(captured_output).to include "1 Total Junk"
    expect(captured_output).to include "2 Forever Hold Your Peace"
  end

  example "read data with storing index" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Add index on Albums(AlbumTitle) (re-use create_index to add)
      create_storing_index project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    capture do
      read_data_with_storing_index project_id:  @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    end

    expect(captured_output).to include "1 Total Junk"
    expect(captured_output).to include "2 Forever Hold Your Peace"
  end

  example "read only transaction" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      read_only_transaction project_id:  @project_id,
                            instance_id: @instance.instance_id,
                            database_id: database.database_id
    end

    expect(captured_output).to include "1 Total Junk 1"
    expect(captured_output).to include "2 Forever Hold Your Peace 2"
  end

  example "batch client read partitions across threads" do
    database = create_singers_albums_database

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    capture do
      spanner_batch_client project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    expect(captured_output).to include "Total Records: 5"
  end

  example "insert data using dml" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 5

    expect {
      insert_using_dml project_id:  @project_id,
                       instance_id: @instance.instance_id,
                       database_id: database.database_id
    }.to output("1 record inserted.\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 6
    expect(singers.find { |s| s[:FirstName] == "Virginia" }).not_to be nil
  end

  example "update using dml" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # First Album(1, 1) set MarketingBudget to $300,000
      client.commit do |c|
        c.update "Albums", [
          { SingerId: 1, AlbumId: 1, MarketingBudget: 300_000 }
        ]
      end
    end

    capture do
      update_using_dml project_id:  @project_id,
                       instance_id: @instance.instance_id,
                       database_id: database.database_id
    end

    expect(captured_output).to include "1 record updated."

    first_album = client.read("Albums", [:MarketingBudget], keys: [[1, 1]]).rows.first

    expect(first_album[:MarketingBudget]).to eq 600_000
  end

  example "delete data using dml" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 5

    expect {
      delete_using_dml project_id:  @project_id,
                       instance_id: @instance.instance_id,
                       database_id: database.database_id
    }.to output("1 record deleted.\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 4
    expect(singers.find { |s| s[:FirstName] == "Alice" }).to be_nil
  end

  example "update data using dml with timestamp column" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # Add Timestamp column
      add_timestamp_column project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    original_timestamp = client.read("Albums", [:LastUpdateTime], keys: [[1, 1]]).rows.first.to_h
    capture do
      update_using_dml_with_timestamp project_id:  @project_id,
                                      instance_id: @instance.instance_id,
                                      database_id: database.database_id
    end
    expect(captured_output).to include "2 records updated."
    updated_timestamp = client.read("Albums", [:LastUpdateTime], keys: [[1, 1]]).rows.first.to_h
    expect(original_timestamp[:LastUpdateTime].to_i < updated_timestamp[:LastUpdateTime].to_i).to be true
  end

  example "write and read data using dml" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 5

    expect {
      write_and_read_using_dml project_id:  @project_id,
                               instance_id: @instance.instance_id,
                               database_id: database.database_id
    }.to output(/1 record updated.\nTimothy Campbell\n/).to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 6
  end

  example "update data using dml with struct" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Insert single Singer record to be updated
      write_and_read_using_dml project_id:  @project_id,
                               instance_id: @instance.instance_id,
                               database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 6

    expect {
      update_using_dml_with_struct project_id:  @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    }.to output("1 record updated.\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 6
    expect(singers.find { |s| s[:LastName] == "Grant" }).not_to be nil
  end

  example "insert multiple records using dml" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 5

    expect {
      write_using_dml project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: database.database_id
    }.to output("14 records inserted.\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 19
    expect(singers.find { |s| s[:FirstName] == "Dylan" }).not_to be nil

    expect {
      query_with_parameter project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    }.to output("12 Melissa Garcia\n").to_stdout
  end

  example "write with transaction using dml (successful transfer)" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id

      # First Album(2, 2) needs at least $200,000 to transfer successfully
      # to Album(1, 1). This should transfer successfully.
      client.commit do |c|
        c.update "Albums", [
          { SingerId: 1, AlbumId: 1, MarketingBudget: 100_000 },
          { SingerId: 2, AlbumId: 2, MarketingBudget: 500_000 }
        ]
      end
    end

    capture do
      write_with_transaction_using_dml project_id:  @project_id,
                                       instance_id: @instance.instance_id,
                                       database_id: database.database_id
    end

    first_album  = client.read("Albums", [:MarketingBudget], keys: [[1, 1]]).rows.first
    second_album = client.read("Albums", [:MarketingBudget], keys: [[2, 2]]).rows.first

    expect(first_album[:MarketingBudget]).to  eq 300_000
    expect(second_album[:MarketingBudget]).to eq 300_000
  end

  example "update data using partioned dml" do
    database = create_singers_albums_database
    client   = @spanner.client @instance.instance_id, database.database_id

    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end

    capture do
      update_using_partitioned_dml project_id:  @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    end

    first_album  = client.read("Albums", [:MarketingBudget], keys: [[2, 1]]).rows.first
    second_album = client.read("Albums", [:MarketingBudget], keys: [[2, 2]]).rows.first
    third_album = client.read("Albums", [:MarketingBudget], keys: [[2, 3]]).rows.first

    expect(first_album[:MarketingBudget]).to  eq 100_000
    expect(second_album[:MarketingBudget]).to eq 100_000
    expect(third_album[:MarketingBudget]).to eq 100_000
  end

  example "delete multiple records using dml" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Insert additinal multiple records into Singers table
      write_using_dml project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 19

    expect {
      delete_using_partitioned_dml project_id:  @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    }.to output("14 records deleted.\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 5
    expect(singers.find { |s| s[:FirstName] == "Melissa" }).to be_nil
  end

  example "insert and update a record using batch dml" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      # Add MarketingBudget column (re-use add_column to add)
      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Albums").rows.count).to eq 5

    expect {
      update_using_batch_dml project_id:  @project_id,
                             instance_id: @instance.instance_id,
                             database_id: database.database_id
    }.to output("Executed 2 SQL statements using Batch DML.\n").to_stdout

    albums = client.execute("SELECT * FROM Albums").rows.to_a
    expect(albums.count).to eq 6
    expect(albums.find { |s| s[:AlbumTitle] == "Test Album Title" }).not_to be nil

    album = client.read("Albums", [:MarketingBudget], keys: [[1, 3]]).rows.first
    expect(album[:MarketingBudget]).to eq 20_000
  end

  example "create table with supported datatypes columns" do
    database = create_singers_albums_database

    expect(@instance.databases.map(&:database_id)).to include @database_id

    capture do
      create_table_with_datatypes project_id:  @project_id,
                                  instance_id: @instance.instance_id,
                                  database_id: @database_id
    end

    expect(captured_output).to include(
      "Waiting for update database operation to complete"
    )
    expect(captured_output).to include(
      "Created table Venues in #{@database_id}"
    )

    data_definition_statements = database.ddl force: true
    expect(data_definition_statements.size).to eq 3
    expect(data_definition_statements.last).to include "CREATE TABLE Venues"
  end

  example "insert datatypes data" do
    database = create_singers_albums_database
    create_venues_table

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Venues").rows.count).to eq 0

    expect {
      write_datatypes_data project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    }.to output("Inserted data\n").to_stdout

    venues = client.execute("SELECT * FROM Venues").rows.to_a
    expect(venues.count).to eq 3
  end

  example "query data with datatypes" do
    database = create_singers_albums_database
    create_venues_table

    # Ignore the following capture block
    capture do
      write_datatypes_data project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    capture do
      query_with_array project_id:  @project_id,
                       instance_id: @instance.instance_id,
                       database_id: database.database_id
    end

    expect(captured_output).to include "19 Venue 19 2020-11-01"
    expect(captured_output).to include "42 Venue 42 2020-10-01"

    capture do
      query_with_bool project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: database.database_id
    end

    expect(captured_output).to include "19 Venue 19 true"

    capture do
      query_with_bytes project_id:  @project_id,
                       instance_id: @instance.instance_id,
                       database_id: database.database_id
    end

    expect(captured_output).to include "4 Venue 4"

    capture do
      query_with_date project_id:  @project_id,
                      instance_id: @instance.instance_id,
                      database_id: database.database_id
    end

    expect(captured_output).to include "4 Venue 4 2018-09-02"
    expect(captured_output).to include "42 Venue 42 2018-10-01"

    capture do
      query_with_float project_id:  @project_id,
                       instance_id: @instance.instance_id,
                       database_id: database.database_id
    end

    expect(captured_output).to include "4 Venue 4 0.8"
    expect(captured_output).to include "19 Venue 19 0.9"

    capture do
      query_with_int project_id:  @project_id,
                     instance_id: @instance.instance_id,
                     database_id: database.database_id
    end

    expect(captured_output).to include "19 Venue 19 6300"
    expect(captured_output).to include "42 Venue 42 3000"

    capture do
      query_with_string project_id:  @project_id,
                        instance_id: @instance.instance_id,
                        database_id: database.database_id
    end

    expect(captured_output).to include "42 Venue 42"

    capture do
      query_with_timestamp project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    expect(captured_output).to include "4 Venue 4"
    expect(captured_output).to include "19 Venue 19"
    expect(captured_output).to include "42 Venue 42"
  end

  example "query data with query options" do
    database = create_singers_albums_database
    create_venues_table

    # Ignore the following capture block
    capture do
      write_datatypes_data project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    capture do
      query_with_query_options project_id:  @project_id,
                               instance_id: @instance.instance_id,
                               database_id: database.database_id
    end

    expect(captured_output).to include "4 Venue 4"
    expect(captured_output).to include "19 Venue 19"
    expect(captured_output).to include "42 Venue 42"

    capture do
      create_client_with_query_options project_id:  @project_id,
                                       instance_id: @instance.instance_id,
                                       database_id: database.database_id
    end

    expect(captured_output).to include "4 Venue 4"
    expect(captured_output).to include "19 Venue 19"
    expect(captured_output).to include "42 Venue 42"
  end

  example "query data with numeric column" do
    database = create_singers_albums_database
    create_venues_table

    # Ignore the following capture block
    capture do
      write_datatypes_data project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id
    end

    capture do
      add_numeric_column project_id:  @project_id,
                         instance_id: @instance.instance_id,
                         database_id: database.database_id
    end

    expect(captured_output).to include "Added the Revenue as a numeric column in Venues table"

    capture do
      update_data_with_numeric_column project_id:  @project_id,
                                      instance_id: @instance.instance_id,
                                      database_id: database.database_id
    end

    expect(captured_output).to include "Updated data"

    capture do
      query_with_numeric_parameter project_id:  @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    end

    expect(captured_output).to include "4 0.35e5"
  end

  example "write data with array types and read" do
    database = create_boxes_database

    capture do
      write_read_bool_array project_id: @project_id,
                            instance_id: @instance.instance_id,
                            database_id: database.database_id
    end

    expect(captured_output.split("\n")).to match_array(["true", "false", "true"])

    capture do
      write_read_empty_int64_array project_id: @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    end

    expect(captured_output).to include "true"

    capture do
      write_read_null_int64_array project_id: @project_id,
                                  instance_id: @instance.instance_id,
                                  database_id: database.database_id
    end

    expect(captured_output.split("\n")).to match_array(["true", "true", "true"])

    capture do
      write_read_int64_array project_id: @project_id,
                             instance_id: @instance.instance_id,
                             database_id: database.database_id
    end

    expect(captured_output).to include "10"
    expect(captured_output).to include "11"
    expect(captured_output).to include "12"

    capture do
      write_read_empty_float64_array project_id: @project_id,
                                     instance_id: @instance.instance_id,
                                     database_id: database.database_id
    end

    expect(captured_output).to include "true"

    capture do
      write_read_null_float64_array project_id: @project_id,
                                    instance_id: @instance.instance_id,
                                    database_id: database.database_id
    end

    expect(captured_output.split("\n")).to match_array(["true", "true", "true"])

    capture do
      write_read_float64_array project_id: @project_id,
                               instance_id: @instance.instance_id,
                               database_id: database.database_id
    end

    expect(captured_output).to include "10.001"
    expect(captured_output).to include "11.1212"
    expect(captured_output).to include "104.4123101"
  end

  xexample "create backup" do
    cleanup_backup_resources
    database = create_database_with_data

    client = @spanner.client @instance.instance_id, database.database_id
    version_time = client.execute("SELECT CURRENT_TIMESTAMP() as timestamp").rows.first[:timestamp]

    capture do
      create_backup project_id:   @project_id,
                    instance_id:  @instance.instance_id,
                    database_id:  database.database_id,
                    backup_id:    @backup_id,
                    version_time: version_time
    end

    expect(captured_output).to include(
      "Backup operation in progress"
    )
    expect(captured_output).to match(
      /Backup #{@backup_id} of size \d+ bytes was created at/
    )
    expect(captured_output).to match(
      /for version of database at/
    )

    @test_backup = @instance.backup @backup_id
    expect(@test_backup).not_to be nil
  end

  xexample "copy backup" do
    cleanup_backup_resources
    create_backup_with_data

    backup_id = "test_bu_copied"

    capture do
      copy_backup project_id: @project_id,
                  instance_id: @instance.instance_id,
                  backup_id: backup_id,
                  source_backup_id: @backup_id
    end

    expect(captured_output).to include(
      "Copy backup operation in progress"
    )
    expect(captured_output).to match(
      /Backup #{backup_id} of size \d+ bytes was copied at/
    )

    test_backup = @instance.backup backup_id
    expect(test_backup).not_to be nil

    # Clean up copied backup
    test_backup&.delete

    test_backup = @instance.backup backup_id
    expect(test_backup).to be_nil
  end

  xexample "copy backup with multiple KMS keys" do
    create_backup_with_data @mr_instance

    backup_id = "test_mr_cmek_copied"

    capture do
      copy_backup_with_multiple_kms_keys project_id: @project_id,
                                         instance_id: @mr_instance.instance_id,
                                         backup_id: backup_id,
                                         source_backup_id: @backup_id,
                                         kms_key_names: @kms_key_names
    end

    expect(captured_output).to include("Copy backup operation in progress")
    expect(captured_output).to match(
      /Backup #{backup_id} of size \d+ bytes was copied at/
    )
    expect(captured_output).to include("#{@kms_key_names[0]}")
    expect(captured_output).to include("#{@kms_key_names[1]}")
    expect(captured_output).to include("#{@kms_key_names[2]}")

    base_backup = @mr_instance.backup @backup_id
    test_backup = @mr_instance.backup backup_id
    expect(base_backup).not_to be nil
    expect(test_backup).not_to be nil

    # Clean up backups
    base_backup&.delete
    test_backup&.delete
  end

  xexample "create backup with encryption key" do
    cleanup_backup_resources
    database = create_database_with_data

    client = @spanner.client @instance.instance_id, database.database_id
    kms_key_name = "projects/#{@project_id}/locations/us-central1/keyRings/spanner-test-keyring/cryptoKeys/spanner-test-cmek"

    capture do
      create_backup_with_encryption_key project_id:   @project_id,
                                        instance_id:  @instance.instance_id,
                                        database_id:  database.database_id,
                                        backup_id:    @backup_id,
                                        kms_key_name: kms_key_name
    end

    expect(captured_output).to include(
      "Backup operation in progress"
    )
    expect(captured_output).to match(
      /Backup #{@backup_id} of size \d+ bytes was created at/
    )
    expect(captured_output).to match(
      /using encryption key #{kms_key_name}/
    )

    @test_backup = @instance.backup @backup_id
    expect(@test_backup).not_to be nil
  end

  xexample "create backup with multiple KMS keys" do
    database = create_database_with_data @mr_instance

    backup_id = "test_mr_cmek_created"

    capture do
      create_backup_with_multiple_kms_keys project_id:   @project_id,
                                           instance_id:  @mr_instance.instance_id,
                                           database_id:  database.database_id,
                                           backup_id:    backup_id,
                                           kms_key_names: @kms_key_names
    end

    expect(captured_output).to include("Backup operation in progress")
    expect(captured_output).to match(
      /Backup #{backup_id} of size \d+ bytes was created at/
    )
    expect(captured_output).to include("#{@kms_key_names[0]}")
    expect(captured_output).to include("#{@kms_key_names[1]}")
    expect(captured_output).to include("#{@kms_key_names[2]}")

    test_backup = @mr_instance.backup backup_id
    expect(test_backup).not_to be nil
    # Clean up
    test_backup&.delete
  end

  xexample "restore backup" do
    backup = create_backup_with_data
    database = @instance.database @database_id

    capture do
      restore_backup project_id:  @project_id,
                     instance_id: @instance.instance_id,
                     database_id: @restored_database_id,
                     backup_id:   backup.backup_id
    end

    expect(captured_output).to include(
      "Waiting for restore backup operation to complete"
    )
    expect(captured_output).to match(
      /Database #{database.path} was restored to #{@restored_database_id} from backup #{backup.path} with version time/
    )

    @test_database = @instance.database @restored_database_id
    expect(@test_database).not_to be nil
  end

  xexample "restore database with encryption key" do
    backup = create_backup_with_data
    database = @instance.database @database_id
    kms_key_name = "projects/#{@project_id}/locations/us-central1/keyRings/spanner-test-keyring/cryptoKeys/spanner-test-cmek"

    capture do
      restore_database_with_encryption_key project_id:   @project_id,
                                           instance_id:  @instance.instance_id,
                                           database_id:  @restored_database_id,
                                           backup_id:    backup.backup_id,
                                           kms_key_name: kms_key_name
    end

    expect(captured_output).to include(
      "Waiting for restore backup operation to complete"
    )
    expect(captured_output).to match(
      /Database #{database.path} was restored to #{@restored_database_id} from backup #{backup.path} using encryption key #{kms_key_name}/
    )

    @test_database = @instance.database @restored_database_id
    expect(@test_database).not_to be nil
  end

  xexample "restore database with multiple KMS keys" do
    backup = create_backup_with_data @mr_instance
    database = @mr_instance.database @database_id

    capture do
      restore_database_with_multiple_kms_keys project_id:   @project_id,
                                              instance_id:  @mr_instance_id,
                                              database_id:  @restored_database_id,
                                              backup_id:    backup.backup_id,
                                              kms_key_names: @kms_key_names
    end

    expect(captured_output).to include(
      "Waiting for restore backup operation to complete"
    )
    expect(captured_output).to include("#{@kms_key_names[0]}")
    expect(captured_output).to include("#{@kms_key_names[1]}")
    expect(captured_output).to include("#{@kms_key_names[2]}")

    @test_database = @mr_instance.database @restored_database_id
    expect(@test_database).not_to be nil

    test_backup = @mr_instance.backup backup_id
    expect(test_backup).not_to be nil

    # Clean up backup
    test_backup&.delete
  end

  xexample "cancel backup operation" do
    database = create_database_with_data

    cancel_backup_id = "cancel_#{@backup_id}"
    capture do
      create_backup_cancel project_id:  @project_id,
                           instance_id: @instance.instance_id,
                           database_id: database.database_id,
                           backup_id:   cancel_backup_id
    end

    expect(captured_output).to include(
      "Backup operation in progress"
    )
    expect(captured_output).to include(
      "#{cancel_backup_id} creation job cancelled"
    )

    @test_backup = @instance.backup cancel_backup_id
    expect(@test_backup).to be nil
  end

  xexample "list backup operations" do
    backup = create_backup_with_data
    capture do
      list_backup_operations project_id:  @project_id,
                             instance_id: @instance.instance_id,
                             database_id: @database_id
    end

    expect(captured_output).to match(
      /Backup #{backup.path} on database #{@database_id} is \d+% complete/
    )

    @test_backup = @instance.backup @backup_id
    expect(@test_backup).not_to be nil
  end

  xexample "list copy backup operations" do
    backup = create_backup_with_data
    copied_backup = create_copy_backup
    capture do
      list_copy_backup_operations project_id:  @project_id,
                                  instance_id: @instance.instance_id,
                                  backup_id: @backup_id
    end

    expect(captured_output).to match(
      /Backup #{copied_backup.path} on source backup #{@backup_id} is \d+% complete/
    )

    @test_backup = @instance.backup @backup_id
    expect(@test_backup).not_to be nil
  end

  xexample "list database operations" do
    database = restore_database_from_backup

    capture do
      list_database_operations project_id:  @project_id,
                               instance_id: @instance.instance_id
    end

    expect(captured_output).to match(
      /List database operations with optimized database filter found \d+ jobs/
    )
  end

  xexample "list backups with various filters" do
    backup = create_backup_with_data

    capture do
      list_backups project_id:  @project_id,
                   instance_id: @instance.instance_id,
                   backup_id: @backup_id,
                   database_id: backup.database_id
    end

    # Segregate each list backup filters output.
    output_segments = captured_output.split(/(All backups)/)
                                     .reject(&:empty?)
                                     .each_slice(2)
                                     .map(&:join)

    expect(output_segments.shift).to include("All backups", backup.path)

    expect(output_segments.shift).to include(
      "All backups with backup name containing",
      "\"#{backup.backup_id}\":\n#{backup.path}"
    )

    expect(output_segments.shift).to include(
      "All backups for databases with a name containing",
      "\"#{backup.database_id}\":\n#{backup.path}"
    )

    expect(output_segments.shift).to include(
      "All backups that expire before a timestamp", backup.backup_id
    )
    expect(output_segments.shift).to include(
      "All backups with a size greater than 500 bytes", backup.backup_id
    )
    expect(output_segments.shift).to include(
      "All backups that were created after a timestamp that are also ready",
      backup.backup_id
    )
    expect(output_segments.shift).to include(
      "All backups with pagination", backup.backup_id
    )

    @test_backup = @instance.backup @backup_id
    expect(@test_backup).not_to be nil
  end

  xexample "delete backup" do
    backup = create_backup_with_data

    capture do
      delete_backup project_id:  @project_id,
                    instance_id: @instance.instance_id,
                    backup_id:   backup.backup_id
    end

    expect(captured_output).to include(
      "Backup #{backup.backup_id} deleted"
    )

    @test_backup = @instance.backup @backup_id
    expect(@test_backup).to be nil
  end

  xexample "update backup" do
    backup = create_backup_with_data

    capture do
      update_backup project_id:  @project_id,
                    instance_id: @instance.instance_id,
                    backup_id:   backup.backup_id
    end

    expect(captured_output).to include(
      "Expiration time updated: #{backup.expire_time + 2_592_000}"
    )
  end

  example "set custom timeout and retry settings" do
    database = create_singers_albums_database
    # Ignore the following capture block
    capture do
      # Insert Singers and Albums (re-use insert_data sample to populate)
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id
    end

    client = @spanner.client @instance.instance_id, database.database_id

    expect(client.execute("SELECT * FROM Singers").rows.count).to eq 5

    expect {
      set_custom_timeout_and_retry project_id:  @project_id,
                                   instance_id: @instance.instance_id,
                                   database_id: database.database_id
    }.to output("1 record inserted.\n").to_stdout

    singers = client.execute("SELECT * FROM Singers").rows.to_a
    expect(singers.count).to eq 6
    expect(singers.find { |s| s[:FirstName] == "Virginia" }).not_to be nil
  end

  example "get commit stats" do
    database = create_singers_albums_database

    capture do
      insert_data project_id:  @project_id,
                  instance_id: @instance.instance_id,
                  database_id: database.database_id

      add_column project_id:  @project_id,
                 instance_id: @instance.instance_id,
                 database_id: database.database_id
    end


    capture do
      commit_stats project_id:  @project_id,
                   instance_id: @instance.instance_id,
                   database_id: database.database_id
    end

    expect(captured_output).to match(/Updated data with \d+ mutations/)
  end
end
