spec/integration/schema_inference_spec.rb in rom-sql-0.8.0 vs spec/integration/schema_inference_spec.rb in rom-sql-0.9.0

- old
+ new

@@ -1,67 +1,229 @@ -RSpec.describe 'Schema inference' do +RSpec.describe 'Schema inference for common datatypes' do include_context 'database setup' - before do - conn.drop_table?(:test_inferrence) + let(:schema) { container.relations[dataset].schema } - conn.create_table :test_inferrence do - primary_key :id - String :text, null: false - Boolean :flag, null: false - Date :date - DateTime :datetime, null: false - Decimal :money, null: false - Bytea :data - end + def trunc_ts(time, drop_usec: false) + usec = drop_usec ? 0 : time.to_time.usec.floor + Time.mktime(time.year, time.month, time.day, time.hour, time.min, time.sec, usec) end - let(:schema) { container.relations[dataset].schema } + with_adapters do |adapter| + describe 'inferring attributes' do + before do + dataset = self.dataset + conf.relation(dataset) do + schema(dataset, infer: true) + end + end - context 'inferring attributes' do - before do - dataset = self.dataset - conf.relation(dataset) do - schema(dataset, infer: true) + context 'for simple table' do + let(:dataset) { :users } + + it 'can infer attributes for dataset' do + expect(schema.attributes).to eql( + id: ROM::SQL::Types::Serial.meta(name: :id), + name: ROM::SQL::Types::String.meta(name: :name) + ) + end end - end - context 'for simple table' do - let(:dataset) { :users } + context 'for a table with FKs' do + let(:dataset) { :tasks } - it 'can infer attributes for dataset' do - expect(schema.attributes).to eql( - id: ROM::SQL::Types::Serial.meta(name: :id), - name: ROM::SQL::Types::Strict::String.meta(name: :name) - ) + it 'can infer attributes for dataset' do + expect(schema.attributes).to eql( + id: ROM::SQL::Types::Serial.meta(name: :id), + title: ROM::SQL::Types::String.optional.meta(name: :title), + user_id: ROM::SQL::Types::Int.optional.meta(name: :user_id, + foreign_key: true, + relation: :users) + ) + end end - end - context 'for a table with FKs' do - let(:dataset) { :tasks } + context 'for complex table' do + before do |example| + ctx = self + conn.drop_table?(:test_inferrence) - it 'can infer attributes for dataset' do - expect(schema.attributes).to eql( - id: ROM::SQL::Types::Serial.meta(name: :id), - title: ROM::SQL::Types::Strict::String.optional.meta(name: :title), - user_id: ROM::SQL::Types::Strict::Int.optional.meta(name: :user_id, foreign_key: true, relation: :users) - ) + conn.create_table :test_inferrence do + primary_key :id + String :text, null: false + Boolean :flag, null: false + Date :date + DateTime :datetime, null: false + + if ctx.postgres?(example) + Bytea :data + else + Blob :data + end + end + end + + let(:dataset) { :test_inferrence } + + it 'can infer attributes for dataset' do + expect(schema.attributes).to eql( + id: ROM::SQL::Types::Serial.meta(name: :id), + text: ROM::SQL::Types::String.meta(name: :text), + flag: ROM::SQL::Types::Bool.meta(name: :flag), + date: ROM::SQL::Types::Date.optional.meta(name: :date), + datetime: ROM::SQL::Types::Time.meta(name: :datetime), + data: ROM::SQL::Types::Blob.optional.meta(name: :data) + ) + end end end - context 'for complex table' do - let(:dataset) { :test_inferrence } + describe 'using commands with inferred schema' do + let(:relation) { container.relation(:people) } - it 'can infer attributes for dataset' do - expect(schema.attributes).to eql( - id: ROM::SQL::Types::Serial.meta(name: :id), - text: ROM::SQL::Types::Strict::String.meta(name: :text), - flag: ROM::SQL::Types::Strict::Bool.meta(name: :flag), - date: ROM::SQL::Types::Strict::Date.optional.meta(name: :date), - datetime: ROM::SQL::Types::Strict::Time.meta(name: :datetime), - money: ROM::SQL::Types::Strict::Decimal.meta(name: :money), - data: ROM::SQL::Types::Strict::String.optional.meta(name: :data) - ) + before do + conn.drop_table?(:people) + + conf.relation(:people) do + schema(dataset, infer: true) + end + + conf.commands(:people) do + define(:create) do + result :one + end + end + end + + describe 'inserting' do + let(:create) { commands[:people].create } + + context "Sequel's types" do + before do + conn.create_table :people do + primary_key :id + String :name, null: false + end + end + + it "doesn't coerce or check types on insert by default" do + result = create.call(name: Sequel.function(:upper, 'Jade')) + + expect(result).to eql(id: 1, name: 'JADE') + end + end + + context 'nullable columns' do + before do + conn.create_table :people do + primary_key :id + String :name, null: false + Integer :age, null: true + end + end + + it 'allows to insert records with nil value' do + result = create.call(name: 'Jade', age: nil) + + expect(result).to eql(id: 1, name: 'Jade', age: nil) + end + + it 'allows to omit nullable columns' do + result = create.call(name: 'Jade') + + expect(result).to eql(id: 1, name: 'Jade', age: nil) + end + end + + context 'columns with default value' do + before do + conn.create_table :people do + primary_key :id + String :name, null: false + Integer :age, null: false, default: 18 + end + end + + it 'sets default value on missing key' do + result = create.call(name: 'Jade') + + expect(result).to eql(id: 1, name: 'Jade', age: 18) + end + + it 'raises an error on inserting nil value' do + expect { create.call(name: 'Jade', age: nil) }.to raise_error(ROM::SQL::NotNullConstraintError) + end + end + + context 'coercions' do + context 'date' do + before do + conn.create_table :people do + primary_key :id + String :name, null: false + Date :birth_date, null: false + end + end + + it 'accetps Time' do + time = Time.iso8601('1970-01-01T06:00:00') + result = create.call(name: 'Jade', birth_date: time) + + expect(result).to eql(id: 1, name: 'Jade', birth_date: Date.iso8601('1970-01-01T00:00:00')) + end + end + + unless metadata[:sqlite] && defined? JRUBY_VERSION + context 'timestamp' do + before do + conn.create_table :people do + primary_key :id + String :name, null: false + Timestamp :created_at, null: false + end + end + + it 'accepts Date' do + date = Date.today + result = create.call(name: 'Jade', created_at: date) + + expect(result).to eql(id: 1, name: 'Jade', created_at: date.to_time) + end + + it 'accepts Time' do |ex| + now = Time.now + result = create.call(name: 'Jade', created_at: now) + + expected_time = trunc_ts(now, drop_usec: mysql?(ex)) + expect(result).to eql(id: 1, name: 'Jade', created_at: expected_time) + end + + it 'accepts DateTime' do |ex| + now = DateTime.now + result = create.call(name: 'Jade', created_at: now) + + expected_time = trunc_ts(now, drop_usec: mysql?(ex)) + expect(result).to eql(id: 1, name: 'Jade', created_at: expected_time) + end + + if !metadata[:mysql] + it 'accepts strings in RFC 2822' do + now = Time.now + result = create.call(name: 'Jade', created_at: now.rfc822) + + expect(result).to eql(id: 1, name: 'Jade', created_at: trunc_ts(now, drop_usec: true)) + end + + it 'accepts strings in RFC 3339' do + now = DateTime.now + result = create.call(name: 'Jade', created_at: now.rfc3339) + + expect(result).to eql(id: 1, name: 'Jade', created_at: trunc_ts(now, drop_usec: true)) + end + end + end + end + end end end end end