diff --git a/lib/gcloud/bigquery.rb b/lib/gcloud/bigquery.rb
index 56a23934ac7f..43fc6b01ede8 100644
--- a/lib/gcloud/bigquery.rb
+++ b/lib/gcloud/bigquery.rb
@@ -209,33 +209,13 @@ def self.bigquery project = nil, keyfile = nil, options = {}
# bigquery = gcloud.bigquery
# dataset = bigquery.dataset "my_dataset"
#
- # schema = {
- # "fields" => [
- # {
- # "name" => "first_name",
- # "type" => "STRING",
- # "mode" => "REQUIRED"
- # },
- # {
- # "name" => "cities_lived",
- # "type" => "RECORD",
- # "mode" => "REPEATED",
- # "fields" => [
- # {
- # "name" => "place",
- # "type" => "STRING",
- # "mode" => "REQUIRED"
- # },
- # {
- # "name" => "number_of_years",
- # "type" => "INTEGER",
- # "mode" => "REQUIRED"
- # }
- # ]
- # }
- # ]
- # }
- # table = dataset.create_table "people", schema: schema
+ # table = dataset.create_table "people" do |schema|
+ # schema.string "first_name", mode: :required
+ # schema.record "cities_lived", mode: :repeated do |nested_schema|
+ # nested_schema.string "place", mode: :required
+ # nested_schema.integer "number_of_years", mode: :required
+ # end
+ # end
#
# Because of the repeated field in this schema, we cannot use the CSV format
# to load data into the table.
@@ -306,26 +286,11 @@ def self.bigquery project = nil, keyfile = nil, options = {}
# gcloud = Gcloud.new
# bigquery = gcloud.bigquery
# dataset = bigquery.dataset "my_dataset"
- # schema = {
- # "fields" => [
- # {
- # "name" => "name",
- # "type" => "STRING",
- # "mode" => "REQUIRED"
- # },
- # {
- # "name" => "sex",
- # "type" => "STRING",
- # "mode" => "REQUIRED"
- # },
- # {
- # "name" => "number",
- # "type" => "INTEGER",
- # "mode" => "REQUIRED"
- # }
- # ]
- # }
- # table = dataset.create_table "baby_names", schema: schema
+ # table = dataset.create_table "baby_names" do |schema|
+ # schema.string "name", mode: :required
+ # schema.string "sex", mode: :required
+ # schema.integer "number", mode: :required
+ # end
#
# file = File.open "names/yob2014.txt"
# load_job = table.load file, format: "csv"
diff --git a/lib/gcloud/bigquery/dataset.rb b/lib/gcloud/bigquery/dataset.rb
index 36e4e483b385..6842533720a6 100644
--- a/lib/gcloud/bigquery/dataset.rb
+++ b/lib/gcloud/bigquery/dataset.rb
@@ -16,6 +16,7 @@
require "json"
require "gcloud/bigquery/errors"
require "gcloud/bigquery/table"
+require "gcloud/bigquery/table/schema"
require "gcloud/bigquery/dataset/list"
require "gcloud/bigquery/dataset/access"
@@ -329,10 +330,11 @@ def delete options = {}
# options[:description]
::
# A user-friendly description of the table. (+String+)
# options[:schema]
::
- # A schema specifying fields and data types for the table. See the
+ # A hash specifying fields and data types for the table. A block may be
+ # passed instead (see examples.) For the format of this hash, see the
# {Tables resource
# }[https://cloud.google.com/bigquery/docs/reference/v2/tables#resource]
- # for more information. (+Hash+)
+ # . (+Hash+)
#
# === Returns
#
@@ -347,7 +349,35 @@ def delete options = {}
# dataset = bigquery.dataset "my_dataset"
# table = dataset.create_table "my_table"
#
- # A name and description can be provided:
+ # You can also pass name and description options.
+ #
+ # require "gcloud"
+ #
+ # gcloud = Gcloud.new
+ # bigquery = gcloud.bigquery
+ # dataset = bigquery.dataset "my_dataset"
+ # table = dataset.create_table "my_table"
+ # name: "My Table",
+ # description: "A description of my table."
+ #
+ # You can define the table's schema using a block.
+ #
+ # require "gcloud"
+ #
+ # gcloud = Gcloud.new
+ # bigquery = gcloud.bigquery
+ # dataset = bigquery.dataset "my_dataset"
+ # table = dataset.create_table "my_table" do |schema|
+ # schema.string "first_name", mode: :required
+ # schema.record "cities_lived", mode: :repeated do |nested_schema|
+ # nested_schema.string "place", mode: :required
+ # nested_schema.integer "number_of_years", mode: :required
+ # end
+ # end
+ #
+ # Or, if you are adapting existing code that was written for the {Rest API
+ # }[https://cloud.google.com/bigquery/docs/reference/v2/tables#resource],
+ # you can pass the table's schema as a hash.
#
# require "gcloud"
#
@@ -381,20 +411,21 @@ def delete options = {}
# }
# ]
# }
- # table = dataset.create_table "my_table",
- # name: "My Table",
- # schema: schema
+ # table = dataset.create_table "my_table", schema: schema
#
# :category: Table
#
def create_table table_id, options = {}
ensure_connection!
- resp = connection.insert_table dataset_id, table_id, options
- if resp.success?
- Table.from_gapi resp.data, connection
- else
- fail ApiError.from_response(resp)
+ if block_given?
+ if options[:schema]
+ fail ArgumentError, "only schema block or schema option is allowed"
+ end
+ schema_builder = Table::Schema.new nil
+ yield schema_builder
+ options[:schema] = schema_builder.schema if schema_builder.changed?
end
+ insert_table table_id, options
end
##
@@ -710,6 +741,15 @@ def self.from_gapi gapi, conn #:nodoc:
protected
+ def insert_table table_id, options
+ resp = connection.insert_table dataset_id, table_id, options
+ if resp.success?
+ Table.from_gapi resp.data, connection
+ else
+ fail ApiError.from_response(resp)
+ end
+ end
+
##
# Raise an error unless an active connection is available.
def ensure_connection!
diff --git a/lib/gcloud/bigquery/table.rb b/lib/gcloud/bigquery/table.rb
index 2fb492d7f670..d0b42db32d33 100644
--- a/lib/gcloud/bigquery/table.rb
+++ b/lib/gcloud/bigquery/table.rb
@@ -16,6 +16,7 @@
require "gcloud/bigquery/view"
require "gcloud/bigquery/data"
require "gcloud/bigquery/table/list"
+require "gcloud/bigquery/table/schema"
require "gcloud/bigquery/errors"
require "gcloud/bigquery/insert_response"
require "gcloud/upload"
@@ -36,35 +37,14 @@ module Bigquery
# gcloud = Gcloud.new
# bigquery = gcloud.bigquery
# dataset = bigquery.dataset "my_dataset"
- # table = dataset.create_table "my_table"
#
- # schema = {
- # "fields" => [
- # {
- # "name" => "first_name",
- # "type" => "STRING",
- # "mode" => "REQUIRED"
- # },
- # {
- # "name" => "cities_lived",
- # "type" => "RECORD",
- # "mode" => "REPEATED",
- # "fields" => [
- # {
- # "name" => "place",
- # "type" => "STRING",
- # "mode" => "REQUIRED"
- # },
- # {
- # "name" => "number_of_years",
- # "type" => "INTEGER",
- # "mode" => "REQUIRED"
- # }
- # ]
- # }
- # ]
- # }
- # table.schema = schema
+ # table = dataset.create_table "my_table" do |schema|
+ # schema.string "first_name", mode: :required
+ # schema.record "cities_lived", mode: :repeated do |nested_schema|
+ # nested_schema.string "place", mode: :required
+ # nested_schema.integer "number_of_years", mode: :required
+ # end
+ # end
#
# row = {
# "first_name" => "Alice",
@@ -311,20 +291,64 @@ def location
end
##
- # The schema of the table.
+ # Returns the table's schema as hash containing the keys and values
+ # returned by the Google Cloud BigQuery {Rest API
+ # }[https://cloud.google.com/bigquery/docs/reference/v2/tables#resource].
+ # This method can also be used to replace or update the schema by passing
+ # a block. See Table::Schema for available methods. To replace the current
+ # schema by passing a hash instead, use #schema=.
+ #
+ # === Parameters
+ #
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:replace]
::
+ # Whether to replace the existing schema with the new schema. If
+ # +false+, new fields will be added to the existing schema. The default
+ # value is +true+. (+Boolean+)
+ #
+ # === Examples
+ #
+ # require "gcloud"
+ #
+ # gcloud = Gcloud.new
+ # bigquery = gcloud.bigquery
+ # dataset = bigquery.dataset "my_dataset"
+ # table = dataset.create_table "my_table"
+ #
+ # table.schema do |schema|
+ # schema.string "first_name", mode: :required
+ # schema.record "cities_lived", mode: :repeated do |nested_schema|
+ # nested_schema.string "place", mode: :required
+ # nested_schema.integer "number_of_years", mode: :required
+ # end
+ # end
#
# :category: Attributes
#
- def schema
+ def schema options = {}
ensure_full_data!
- s = @gapi["schema"]
- s = s.to_hash if s.respond_to? :to_hash
- s = {} if s.nil?
- s
+ g = @gapi
+ g = g.to_hash if g.respond_to? :to_hash
+ s = g["schema"] ||= {}
+ return s unless block_given?
+ old_schema = options[:replace] == false ? s : nil
+ schema_builder = Schema.new old_schema
+ yield schema_builder
+ self.schema = schema_builder.schema if schema_builder.changed?
end
##
# Updates the schema of the table.
+ # To update the schema using a block instead, use #schema.
+ #
+ # === Parameters
+ #
+ # +schema+::
+ # A hash containing keys and values as specified by the Google Cloud
+ # BigQuery {Rest API
+ # }[https://cloud.google.com/bigquery/docs/reference/v2/tables#resource]
+ # . (+Hash+)
#
# === Example
#
diff --git a/lib/gcloud/bigquery/table/schema.rb b/lib/gcloud/bigquery/table/schema.rb
new file mode 100644
index 000000000000..3d3c2e70ace5
--- /dev/null
+++ b/lib/gcloud/bigquery/table/schema.rb
@@ -0,0 +1,252 @@
+#--
+# Copyright 2015 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.
+
+module Gcloud
+ module Bigquery
+ class Table
+ ##
+ # = Table Schema
+ #
+ # A builder for BigQuery table schemas, passed to block arguments to
+ # Dataset#create_table and Table#schema. Supports nested and
+ # repeated fields via a nested block. For more information about BigQuery
+ # schema definitions, see {Preparing Data for BigQuery
+ # }[https://cloud.google.com/bigquery/preparing-data-for-bigquery].
+ #
+ # require "gcloud"
+ #
+ # gcloud = Gcloud.new
+ # bigquery = gcloud.bigquery
+ # dataset = bigquery.dataset "my_dataset"
+ # table = dataset.create_table "my_table"
+ #
+ # table.schema do |schema|
+ # schema.string "first_name", mode: :required
+ # schema.record "cities_lived", mode: :repeated do |cities_lived|
+ # cities_lived.string "place", mode: :required
+ # cities_lived.integer "number_of_years", mode: :required
+ # end
+ # end
+ #
+ class Schema
+ MODES = %w( NULLABLE REQUIRED REPEATED ) #:nodoc:
+ TYPES = %w( STRING INTEGER FLOAT BOOLEAN TIMESTAMP RECORD ) #:nodoc:
+
+ attr_reader :fields #:nodoc:
+
+ ##
+ # Initializes a new schema object with an existing schema.
+ def initialize schema = nil, nested = false #:nodoc:
+ fields = (schema && schema["fields"]) || []
+ @original_fields = fields.dup
+ @fields = fields.dup
+ @nested = nested
+ end
+
+ def changed? #:nodoc:
+ @original_fields != @fields
+ end
+
+ ##
+ # Returns the schema as hash containing the keys and values specified by
+ # the Google Cloud BigQuery {Rest API
+ # }[https://cloud.google.com/bigquery/docs/reference/v2/tables#resource]
+ # .
+ def schema #:nodoc:
+ {
+ "fields" => @fields
+ }
+ end
+
+ ##
+ # Adds a string field to the schema.
+ #
+ # === Parameters
+ #
+ # +name+::
+ # The field name. The name must contain only letters (a-z, A-Z),
+ # numbers (0-9), or underscores (_), and must start with a letter or
+ # underscore. The maximum length is 128 characters. (+String+)
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:description]
::
+ # A description of the field. (+String+)
+ # options[:mode]
::
+ # The field's mode. The possible values are +:nullable+, +:required+,
+ # and +:repeated+. The default value is +:nullable+. (+Symbol+)
+ def string name, options = {}
+ add_field name, :string, nil, options
+ end
+
+ ##
+ # Adds an integer field to the schema.
+ #
+ # === Parameters
+ #
+ # +name+::
+ # The field name. The name must contain only letters (a-z, A-Z),
+ # numbers (0-9), or underscores (_), and must start with a letter or
+ # underscore. The maximum length is 128 characters. (+String+)
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:description]
::
+ # A description of the field. (+String+)
+ # options[:mode]
::
+ # The field's mode. The possible values are +:nullable+, +:required+,
+ # and +:repeated+. The default value is +:nullable+. (+Symbol+)
+ def integer name, options = {}
+ add_field name, :integer, nil, options
+ end
+
+ ##
+ # Adds a floating-point number field to the schema.
+ #
+ # === Parameters
+ #
+ # +name+::
+ # The field name. The name must contain only letters (a-z, A-Z),
+ # numbers (0-9), or underscores (_), and must start with a letter or
+ # underscore. The maximum length is 128 characters. (+String+)
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:description]
::
+ # A description of the field. (+String+)
+ # options[:mode]
::
+ # The field's mode. The possible values are +:nullable+, +:required+,
+ # and +:repeated+. The default value is +:nullable+. (+Symbol+)
+ def float name, options = {}
+ add_field name, :float, nil, options
+ end
+
+ ##
+ # Adds a boolean field to the schema.
+ #
+ # === Parameters
+ #
+ # +name+::
+ # The field name. The name must contain only letters (a-z, A-Z),
+ # numbers (0-9), or underscores (_), and must start with a letter or
+ # underscore. The maximum length is 128 characters. (+String+)
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:description]
::
+ # A description of the field. (+String+)
+ # options[:mode]
::
+ # The field's mode. The possible values are +:nullable+, +:required+,
+ # and +:repeated+. The default value is +:nullable+. (+Symbol+)
+ def boolean name, options = {}
+ add_field name, :boolean, nil, options
+ end
+
+ ##
+ # Adds a timestamp field to the schema.
+ #
+ # === Parameters
+ #
+ # +name+::
+ # The field name. The name must contain only letters (a-z, A-Z),
+ # numbers (0-9), or underscores (_), and must start with a letter or
+ # underscore. The maximum length is 128 characters. (+String+)
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:description]
::
+ # A description of the field. (+String+)
+ # options[:mode]
::
+ # The field's mode. The possible values are +:nullable+, +:required+,
+ # and +:repeated+. The default value is +:nullable+. (+Symbol+)
+ def timestamp name, options = {}
+ add_field name, :timestamp, nil, options
+ end
+
+ ##
+ # Adds a record field to the schema. A block must be passed describing
+ # the nested fields of the record. For more information about nested
+ # and repeated records, see {Preparing Data for BigQuery
+ # }[https://cloud.google.com/bigquery/preparing-data-for-bigquery].
+ #
+ # === Parameters
+ #
+ # +name+::
+ # The field name. The name must contain only letters (a-z, A-Z),
+ # numbers (0-9), or underscores (_), and must start with a letter or
+ # underscore. The maximum length is 128 characters. (+String+)
+ # +options+::
+ # An optional Hash for controlling additional behavior. (+Hash+)
+ # options[:description]
::
+ # A description of the field. (+String+)
+ # options[:mode]
::
+ # The field's mode. The possible values are +:nullable+, +:required+,
+ # and +:repeated+. The default value is +:nullable+. (+Symbol+)
+ #
+ # === Example
+ #
+ # require "gcloud"
+ #
+ # gcloud = Gcloud.new
+ # bigquery = gcloud.bigquery
+ # dataset = bigquery.dataset "my_dataset"
+ # table = dataset.create_table "my_table"
+ #
+ # table.schema do |schema|
+ # schema.string "first_name", mode: :required
+ # schema.record "cities_lived", mode: :repeated do |cities_lived|
+ # cities_lived.string "place", mode: :required
+ # cities_lived.integer "number_of_years", mode: :required
+ # end
+ # end
+ #
+ def record name, options = {}
+ fail ArgumentError, "nested RECORD type is not permitted" if @nested
+ fail ArgumentError, "a block is required" unless block_given?
+ nested_schema = self.class.new nil, true
+ yield nested_schema
+ add_field name, :record, nested_schema.fields, options
+ end
+
+ protected
+
+ def upcase_type type
+ upcase_type = type.to_s.upcase
+ unless TYPES.include? upcase_type
+ fail ArgumentError,
+ "Type '#{upcase_type}' not found in #{TYPES.inspect}"
+ end
+ upcase_type
+ end
+
+ def upcase_mode mode
+ upcase_mode = mode.to_s.upcase
+ unless MODES.include? upcase_mode
+ fail ArgumentError "Unable to determine mode for '#{mode}'"
+ end
+ upcase_mode
+ end
+
+ def add_field name, type, nested_fields, options
+ # Remove any existing field of this name
+ @fields.reject! { |h| h["name"] == name }
+ field = {
+ "name" => name,
+ "type" => upcase_type(type)
+ }
+ field["mode"] = upcase_mode(options[:mode]) if options[:mode]
+ field["description"] =options[:description] if options[:description]
+ field["fields"] = nested_fields if nested_fields
+ @fields << field
+ end
+ end
+ end
+ end
+end
diff --git a/test/gcloud/bigquery/dataset_test.rb b/test/gcloud/bigquery/dataset_test.rb
index e73b0504fff6..04c6870f19a0 100644
--- a/test/gcloud/bigquery/dataset_test.rb
+++ b/test/gcloud/bigquery/dataset_test.rb
@@ -27,22 +27,20 @@
{
"name" => "name",
"type" => "STRING",
- "mode" => "NULLABLE"
+ "mode" => "REQUIRED"
},
{
"name" => "age",
- "type" => "INTEGER",
- "mode" => "NULLABLE"
+ "type" => "INTEGER"
},
{
"name" => "score",
"type" => "FLOAT",
- "mode" => "NULLABLE"
+ "description" => "A score from 0.0 to 10.0"
},
{
"name" => "active",
- "type" => "BOOLEAN",
- "mode" => "NULLABLE"
+ "type" => "BOOLEAN"
}
]
}
@@ -106,7 +104,7 @@
table.wont_be :view?
end
- it "creates a table with a name, description, and schema" do
+ it "creates a table with a name, description, and schema option" do
id = "my_table"
name = "My Table"
description = "This is my table"
@@ -114,6 +112,7 @@
mock_connection.post "/bigquery/v2/projects/#{project}/datasets/#{dataset.dataset_id}/tables" do |env|
JSON.parse(env.body)["friendlyName"].must_equal name
JSON.parse(env.body)["description"].must_equal description
+ JSON.parse(env.body)["schema"].must_equal table_schema
[200, {"Content-Type" => "application/json"},
create_table_json(id, name, description)]
end
@@ -131,6 +130,28 @@
table.wont_be :view?
end
+ it "creates a table with a schema block" do
+ id = "my_table"
+
+ mock_connection.post "/bigquery/v2/projects/#{project}/datasets/#{dataset.dataset_id}/tables" do |env|
+ JSON.parse(env.body)["schema"].must_equal table_schema
+ [200, {"Content-Type" => "application/json"},
+ create_table_json(id, name, description)]
+ end
+
+ table = dataset.create_table id do |schema|
+ schema.string "name", mode: :required
+ schema.integer "age"
+ schema.float "score", description: "A score from 0.0 to 10.0"
+ schema.boolean "active"
+ end
+ table.must_be_kind_of Gcloud::Bigquery::Table
+ table.table_id.must_equal id
+ table.schema.must_equal table_schema
+ table.must_be :table?
+ table.wont_be :view?
+ end
+
it "can create a empty view" do
query = "SELECT * FROM [table]"
diff --git a/test/gcloud/bigquery/table_schema_test.rb b/test/gcloud/bigquery/table_schema_test.rb
new file mode 100644
index 000000000000..91a62fe4fcf5
--- /dev/null
+++ b/test/gcloud/bigquery/table_schema_test.rb
@@ -0,0 +1,191 @@
+# Copyright 2015 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 "helper"
+require "json"
+require "uri"
+
+describe Gcloud::Bigquery::Table, :mock_bigquery do
+ # Create a table object with the project's mocked connection object
+ let(:dataset) { "my_dataset" }
+
+ let(:table_id) { "my_table" }
+ let(:table_name) { "My Table" }
+ let(:description) { "This is my table" }
+ let(:etag) { "etag123456789" }
+ let(:location_code) { "US" }
+ let(:url) { "http://googleapi/bigquery/v2/projects/#{project}/datasets/#{dataset}/tables/#{table_id}" }
+ let(:table_hash) { random_table_hash dataset, table_id, table_name, description }
+ let(:table) { Gcloud::Bigquery::Table.from_gapi table_hash, bigquery.connection }
+
+ let(:schema) { table.schema.dup }
+
+ it "gets the schema, fields, and headers" do
+ table.schema.must_be_kind_of Hash
+ table.schema.keys.must_include "fields"
+ table.fields.must_equal table.schema["fields"]
+ table.headers.must_equal ["name", "age", "score", "active"]
+ end
+
+ it "sets its schema if assigned a hash" do
+ new_table_data = new_table_hash
+ new_table_data["schema"]["fields"].first["name"] = "moniker"
+ new_schema = new_table_data["schema"]
+ mock_connection.patch "/bigquery/v2/projects/#{project}/datasets/#{dataset}/tables/#{table_id}" do |env|
+ json = JSON.parse env.body
+ json["schema"].must_equal new_schema
+ [200, { "Content-Type" => "application/json" },
+ new_table_data.to_json]
+ end
+
+ table.schema = new_schema
+
+ table.schema.must_equal new_schema
+ table.schema["fields"].first["name"].must_equal "moniker"
+ end
+
+ it "sets a flat schema via a block" do
+ new_table_data = new_table_hash
+ new_table_data["schema"]["fields"] = [
+ field_string_required,
+ field_integer,
+ field_float,
+ field_boolean,
+ field_timestamp
+ ]
+ mock_connection.patch "/bigquery/v2/projects/#{project}/datasets/#{dataset}/tables/#{table_id}" do |env|
+ json = JSON.parse env.body
+ json["schema"].must_equal new_table_data["schema"]
+ [200, { "Content-Type" => "application/json" }, new_table_data.to_json]
+ end
+
+ table.schema do |schema|
+ schema.string "first_name", mode: :required
+ schema.integer "rank", description: "An integer value from 1 to 100"
+ schema.float "accuracy"
+ schema.boolean "approved"
+ schema.timestamp "start_date"
+ end
+
+ table.schema.must_equal new_table_data["schema"]
+ end
+
+ it "adds to its existing schema with replace option false" do
+ new_table_data = new_table_hash
+ new_table_data["schema"]["fields"] << field_timestamp
+ mock_connection.patch "/bigquery/v2/projects/#{project}/datasets/#{dataset}/tables/#{table_id}" do |env|
+ json = JSON.parse env.body
+ json["schema"].must_equal new_table_data["schema"]
+ [200, { "Content-Type" => "application/json" }, new_table_data.to_json]
+ end
+
+ table.schema replace: false do |schema|
+ schema.timestamp "start_date"
+ end
+
+ table.schema.must_equal new_table_data["schema"]
+ end
+
+ it "sets a nested repeated schema field via a nested block" do
+ new_table_data = new_table_hash
+ new_table_data["schema"]["fields"] = [
+ field_string_required,
+ field_record_repeated
+ ]
+ mock_connection.patch "/bigquery/v2/projects/#{project}/datasets/#{dataset}/tables/#{table_id}" do |env|
+ json = JSON.parse env.body
+ json["schema"].must_equal new_table_data["schema"]
+ [200, { "Content-Type" => "application/json" }, new_table_data.to_json]
+ end
+
+ table.schema do |schema|
+ schema.string "first_name", mode: :required
+ schema.record "cities_lived", mode: :repeated do |nested|
+ nested.integer "rank", description: "An integer value from 1 to 100"
+ nested.timestamp "start_date"
+ end
+ end
+
+ table.schema.must_equal new_table_data["schema"]
+ end
+
+ it "raises when nesting fields more than one level deep" do
+ original_schema = table.schema.dup
+
+ assert_raises ArgumentError do
+ table.schema do |schema|
+ schema.string "first_name", mode: :required
+ schema.record "countries_lived", mode: :repeated do |nested|
+ nested.record "cities_lived", mode: :repeated do |nested_2|
+ nested_2.integer "rank", description: "An integer value from 1 to 100"
+ end
+ end
+ end
+ end
+
+ table.schema.must_equal original_schema
+ end
+
+ protected
+
+ def new_table_hash
+ random_table_hash dataset, table_id, table_name, description
+ end
+
+ def field_string_required
+ {
+ "name" => "first_name",
+ "type" => "STRING",
+ "mode" => "REQUIRED"
+ }
+ end
+
+ def field_integer
+ {
+ "name" => "rank",
+ "type" => "INTEGER",
+ "description" => "An integer value from 1 to 100"
+ }
+ end
+
+ def field_float
+ {
+ "name" => "accuracy",
+ "type" => "FLOAT"
+ }
+ end
+
+ def field_boolean
+ {
+ "name" => "approved",
+ "type" => "BOOLEAN"
+ }
+ end
+
+ def field_timestamp
+ {
+ "name" => "start_date",
+ "type" => "TIMESTAMP"
+ }
+ end
+
+ def field_record_repeated
+ {
+ "name" => "cities_lived",
+ "type" => "RECORD",
+ "mode" => "REPEATED",
+ "fields" => [ field_integer, field_timestamp ]
+ }
+ end
+end
diff --git a/test/gcloud/bigquery/table_update_test.rb b/test/gcloud/bigquery/table_update_test.rb
index c02a9b543ccf..40f1fe3227f7 100644
--- a/test/gcloud/bigquery/table_update_test.rb
+++ b/test/gcloud/bigquery/table_update_test.rb
@@ -66,34 +66,4 @@
table.description.must_equal new_description
table.schema.must_equal schema
end
-
- it "updates its schema" do
- new_schema = schema.dup
- new_schema["fields"].first["name"].must_equal "name"
- new_schema["fields"].first["name"] = "moniker"
-
- mock_connection.patch "/bigquery/v2/projects/#{project}/datasets/#{dataset_id}/tables/#{table_id}" do |env|
- json = JSON.parse env.body
- json["schema"].must_equal new_schema
- [200, {"Content-Type"=>"application/json"},
- new_table_schema_json]
- end
-
- table.name.must_equal table_name
- table.description.must_equal description
- table.schema.must_equal schema
-
- table.schema = new_schema
-
- table.name.must_equal table_name
- table.description.must_equal description
- table.schema.must_equal new_schema
- table.schema["fields"].first["name"].must_equal "moniker"
- end
-
- def new_table_schema_json
- hash = random_table_hash dataset_id, table_id, table_name, description
- hash["schema"]["fields"].first["name"] = "moniker"
- hash.to_json
- end
end
diff --git a/test/helper.rb b/test/helper.rb
index 03eb7b2c62f2..ebfc819e77be 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -337,22 +337,20 @@ def random_table_hash dataset, id = nil, name = nil, description = nil, project_
{
"name" => "name",
"type" => "STRING",
- "mode" => "NULLABLE"
+ "mode" => "REQUIRED"
},
{
"name" => "age",
- "type" => "INTEGER",
- "mode" => "NULLABLE"
+ "type" => "INTEGER"
},
{
"name" => "score",
"type" => "FLOAT",
- "mode" => "NULLABLE"
+ "description" => "A score from 0.0 to 10.0"
},
{
"name" => "active",
- "type" => "BOOLEAN",
- "mode" => "NULLABLE"
+ "type" => "BOOLEAN"
}
]
},