upsert 2.2.0 → 2.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/.travis.yml +54 -31
  5. data/CHANGELOG +16 -0
  6. data/Gemfile +12 -1
  7. data/LICENSE +3 -1
  8. data/README.md +43 -8
  9. data/Rakefile +7 -1
  10. data/lib/upsert.rb +49 -7
  11. data/lib/upsert/column_definition/mysql.rb +2 -2
  12. data/lib/upsert/column_definition/postgresql.rb +9 -8
  13. data/lib/upsert/column_definition/sqlite3.rb +3 -3
  14. data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +5 -3
  15. data/lib/upsert/connection/Java_OrgPostgresqlJdbc_PgConnection.rb +33 -0
  16. data/lib/upsert/connection/PG_Connection.rb +5 -0
  17. data/lib/upsert/connection/jdbc.rb +7 -1
  18. data/lib/upsert/connection/postgresql.rb +2 -3
  19. data/lib/upsert/merge_function.rb +3 -2
  20. data/lib/upsert/merge_function/{Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb → Java_OrgPostgresqlJdbc_PgConnection.rb} +2 -2
  21. data/lib/upsert/merge_function/PG_Connection.rb +2 -2
  22. data/lib/upsert/merge_function/postgresql.rb +81 -19
  23. data/lib/upsert/merge_function/sqlite3.rb +10 -0
  24. data/lib/upsert/version.rb +1 -1
  25. data/spec/correctness_spec.rb +20 -5
  26. data/spec/database_functions_spec.rb +6 -2
  27. data/spec/hstore_spec.rb +53 -38
  28. data/spec/logger_spec.rb +1 -1
  29. data/spec/postgresql_spec.rb +81 -3
  30. data/spec/reserved_words_spec.rb +18 -14
  31. data/spec/sequel_spec.rb +16 -7
  32. data/spec/spec_helper.rb +238 -111
  33. data/spec/speed_spec.rb +3 -33
  34. data/spec/threaded_spec.rb +35 -12
  35. data/spec/type_safety_spec.rb +2 -1
  36. data/travis/run_docker_db.sh +20 -0
  37. data/upsert-java.gemspec +13 -0
  38. data/upsert.gemspec +9 -58
  39. data/upsert.gemspec.common +107 -0
  40. metadata +39 -46
  41. data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +0 -20
data/spec/logger_spec.rb CHANGED
@@ -12,7 +12,7 @@ describe Upsert do
12
12
  Upsert.logger.warn "hello"
13
13
 
14
14
  io.rewind
15
- io.read.chomp.should == 'hello'
15
+ io.read.chomp.should =~ /hello/
16
16
  end
17
17
  ensure
18
18
  Upsert.logger = old_logger
@@ -1,16 +1,94 @@
1
1
  require 'spec_helper'
2
+ require 'upsert/merge_function/postgresql'
3
+
2
4
  describe Upsert do
3
- version = Pet.connection.select_value("SHOW server_version")[0..2].split('.').join('').to_i
5
+ version = 'postgresql' == ENV['DB'] ? Upsert::MergeFunction::Postgresql.extract_version(
6
+ Pet.connection.select_value("SHOW server_version")
7
+ ) : 0
4
8
 
5
9
  let(:upsert) do
6
10
  Upsert.new($conn, :pets)
7
11
  end
8
12
 
9
- it "uses the native method if available (#{(UNIQUE_CONSTRAINT && version >= 95).inspect})" do
13
+ it "uses the native method if available (#{(UNIQUE_CONSTRAINT && version >= 90500).inspect})" do
10
14
  p = Pet.create(:name => 'Jerry', :tag_number => 5)
11
15
  upsert.row({ :name => 'Jerry'}, :tag_number => 6 )
12
16
  expect(upsert.instance_variable_get(:@merge_function_cache).values.first.use_pg_native?).to(
13
- UNIQUE_CONSTRAINT && version >= 95 ? be_truthy : be_falsey
17
+ UNIQUE_CONSTRAINT && version >= 90500 ? be_truthy : be_falsey
14
18
  )
15
19
  end
20
+
21
+ if version >= 90500 && UNIQUE_CONSTRAINT
22
+ it "works with a schema" do
23
+ table_name = ["#{RawConnectionFactory::DB_NAME}2", :pets2]
24
+ cls = clone_ar_class(Pet, table_name)
25
+ upsert = Upsert.new $conn, table_name
26
+ upsert.row({:name => 'Jerry'}, {:gender => 'male'})
27
+ expect(upsert.instance_variable_get(:@merge_function_cache).values.first.use_pg_native?).to be_truthy
28
+ end
29
+
30
+ it "checks the correct table for a unique constraint" do
31
+ Pet.connection.execute("CREATE SCHEMA IF NOT EXISTS unique_constraint_test")
32
+ Pet.connection.execute("CREATE TABLE unique_constraint_test.pets (LIKE public.pets INCLUDING ALL)")
33
+ Pet.connection.execute("SET search_path TO unique_constraint_test")
34
+
35
+ if RUBY_PLATFORM == "java"
36
+ $conn.nativeSQL("SET search_path TO unique_constraint_test")
37
+ $conn.setSchema("unique_constraint_test")
38
+ else
39
+ $conn.exec("SET search_path TO unique_constraint_test")
40
+ end
41
+
42
+ Pet.connection.execute("DROP INDEX unique_constraint_test.pets_name_idx")
43
+ Pet.connection.execute("ALTER TABLE unique_constraint_test.pets DROP CONSTRAINT IF EXISTS unique_name")
44
+ p = Pet.create(:name => 'Jerry', :tag_number => 5)
45
+ upsert.row({ :name => 'Jerry'}, :tag_number => 6 )
46
+ expect(upsert.instance_variable_get(:@merge_function_cache).values.first.use_pg_native?).to be_falsey
47
+ Pet.connection.execute("SET search_path TO public")
48
+
49
+ if RUBY_PLATFORM == "java"
50
+ $conn.nativeSQL("SET search_path TO public")
51
+ $conn.setSchema("public")
52
+ else
53
+ $conn.exec("SET search_path TO public")
54
+ end
55
+
56
+ Pet.connection.execute("DROP SCHEMA unique_constraint_test CASCADE")
57
+ end
58
+ end
59
+
60
+ describe "array escaping" do
61
+ let(:upsert) do
62
+ Upsert.new($conn, :posts)
63
+ end
64
+
65
+ before(:all) do
66
+ Sequel.migration do
67
+ change do
68
+ db = self
69
+ create_table?(:posts) do
70
+ primary_key :id
71
+ String :name
72
+ column :tags, "text[]"
73
+ end
74
+ end
75
+ end.apply(DB, :up)
76
+
77
+ Object.const_set("Post", Class.new(ActiveRecord::Base))
78
+ end
79
+
80
+ [
81
+ %w[1 2 3],
82
+ %w[can't stop won't stop],
83
+ %w["''" '""' '\\],
84
+ ["[]", "{}", "\\\\", "()"],
85
+ %w[*& *&^ $%IUBS (&^ ) ()*& // \\ \\\\ (*&^JN) (*HNCSD) ~!!!`` {} } { ( )],
86
+ %w[\\ \\\\ \\\\\\ \\\\\\\\ \\'\\'\'\\\'" \\'\\"\''\""],
87
+ ].each do |arr|
88
+ it "properly upserts array of: #{arr}" do
89
+ upsert.row({name: "same-name"}, tags: arr)
90
+ expect(Post.first.tags).to eq(arr)
91
+ end
92
+ end
93
+ end
16
94
  end if ENV['DB'] == 'postgresql'
@@ -11,29 +11,33 @@ describe Upsert do
11
11
  # make lots of AR models, each of which has 10 columns named after these words
12
12
  nasties = []
13
13
  reserved_words.each_slice(10) do |words|
14
- eval %{
15
- class Nasty#{nasties.length} < ActiveRecord::Base
16
- end
17
- }
18
- nasty = Object.const_get("Nasty#{nasties.length}")
14
+ name = "Nasty#{nasties.length}"
15
+ Object.const_set(name, Class.new(ActiveRecord::Base))
16
+ nasty = Object.const_get(name)
19
17
  nasty.class_eval do
20
- self.primary_key = 'fake_primary_key'
21
- col :fake_primary_key, limit: 191
22
- words.each do |word|
23
- col word, limit: 191
24
- end
18
+ self.table_name = name.downcase
19
+ self.primary_key = "fake_primary_key"
25
20
  end
21
+
22
+ Sequel.migration do
23
+ change do
24
+ db = self
25
+ create_table?(name.downcase) do
26
+ primary_key :fake_primary_key
27
+ words.each do |word|
28
+ String word, limit: 191
29
+ end
30
+ end
31
+ end
32
+ end.apply(DB, :up)
26
33
  nasties << [ nasty, words ]
27
34
  end
28
- nasties.each do |nasty, _|
29
- nasty.auto_upgrade!
30
- end
31
35
 
32
36
  describe "reserved words" do
33
37
  nasties.each do |nasty, words|
34
38
  it "doesn't die on reserved words #{words.join(',')}" do
35
39
  upsert = Upsert.new $conn, nasty.table_name
36
- random = rand(1e3).to_s
40
+ random = rand(1e3)
37
41
  selector = { :fake_primary_key => random, words.first => words.first }
38
42
  setter = words[1..-1].inject({}) { |memo, word| memo[word] = word; memo }
39
43
  assert_creates nasty, [selector.merge(setter)] do
data/spec/sequel_spec.rb CHANGED
@@ -6,17 +6,26 @@ describe Upsert do
6
6
  config = ActiveRecord::Base.connection.instance_variable_get(:@config)
7
7
  config[:adapter] = case config[:adapter]
8
8
  when 'postgresql' then 'postgres'
9
- when 'sqlie3' then 'sqlite'
9
+ when 'sqlite3' then 'sqlite'
10
10
  else config[:adapter]
11
11
  end
12
12
 
13
13
  let(:db) do
14
- params = if RUBY_PLATFORM == 'java'
15
- RawConnectionFactory::CONFIG
16
- else
17
- config.slice(:adapter, :host, :database, :username, :password).merge(:user => config[:username])
18
- end
19
- Sequel.connect(params)
14
+ if RUBY_PLATFORM == "java"
15
+ Sequel.connect(
16
+ RawConnectionFactory::CONFIG,
17
+ :user => RawConnectionFactory::DB_USER,
18
+ :password => RawConnectionFactory::DB_PASSWORD
19
+ )
20
+ elsif config[:adapter] == "sqlite"
21
+ Sequel.sqlite("temp.db")
22
+ else
23
+ Sequel.connect(config.merge(
24
+ :user => config.values_at(:user, :username).compact.first,
25
+ :host => config.values_at(:host, :hostaddr).compact.first,
26
+ :database => config.values_at(:database, :dbname).compact.first
27
+ ))
28
+ end
20
29
  end
21
30
 
22
31
  it "Doesn't explode on connection" do
data/spec/spec_helper.rb CHANGED
@@ -1,106 +1,182 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'bundler/setup'
3
+ Bundler.require(:default, :development)
3
4
 
4
- # require 'pry'
5
5
  require 'shellwords'
6
+ require "sequel"
7
+ Sequel.default_timezone = :utc
8
+ Sequel.extension :migration
6
9
 
7
- require 'active_record'
10
+ require "active_record"
11
+ require "activerecord-import"
8
12
  ActiveRecord::Base.default_timezone = :utc
9
13
 
10
- require 'active_record_inline_schema'
11
-
12
- require 'activerecord-import' if RUBY_VERSION >= '1.9'
13
-
14
- ENV['DB'] ||= 'mysql'
15
- ENV['DB'] = 'postgresql' if ENV['DB'].to_s =~ /postgresql/
14
+ raise "A DB value is required" unless ENV["DB"]
15
+ ENV['DB'] = 'postgresql' if ENV['DB'].to_s =~ /postgresql/i
16
16
  UNIQUE_CONSTRAINT = ENV['UNIQUE_CONSTRAINT'] == 'true'
17
17
 
18
+ raise "please use DB=postgresql NOT postgres" if ENV["DB"] == "postgres"
18
19
 
19
20
  class RawConnectionFactory
20
- DATABASE = 'upsert_test'
21
- CURRENT_USER = (ENV['DB_USER'] || `whoami`.chomp)
22
- PASSWORD = ENV['DB_PASSWORD']
23
-
24
- case ENV['DB']
25
-
26
- when 'postgresql'
27
- Kernel.system %{ dropdb upsert_test }
28
- Kernel.system %{ createdb upsert_test }
29
- if RUBY_PLATFORM == 'java'
30
- CONFIG = "jdbc:postgresql://localhost/#{DATABASE}?user=#{CURRENT_USER}"
31
- require 'jdbc/postgres'
32
- # http://thesymanual.wordpress.com/2011/02/21/connecting-jruby-to-postgresql-with-jdbc-postgre-api/
33
- Jdbc::Postgres.load_driver
34
- # java.sql.DriverManager.register_driver org.postgresql.Driver.new
35
- def new_connection
36
- java.sql.DriverManager.get_connection CONFIG
37
- end
38
- else
39
- CONFIG = { :dbname => DATABASE }
40
- require 'pg'
41
- def new_connection
42
- PG::Connection.new CONFIG
43
- end
44
- end
45
- ActiveRecord::Base.establish_connection :adapter => 'postgresql', :database => DATABASE, :username => CURRENT_USER
46
-
47
- when 'mysql'
48
- password_argument = (PASSWORD.nil?) ? "" : "--password=#{Shellwords.escape(PASSWORD)}"
49
- Kernel.system %{ mysql -h 127.0.0.1 -u #{CURRENT_USER} #{password_argument} -e "DROP DATABASE IF EXISTS #{DATABASE}" }
50
- Kernel.system %{ mysql -h 127.0.0.1 -u #{CURRENT_USER} #{password_argument} -e "CREATE DATABASE #{DATABASE} CHARSET utf8mb4 COLLATE utf8mb4_general_ci" }
51
- if RUBY_PLATFORM == 'java'
52
- CONFIG = "jdbc:mysql://127.0.0.1/#{DATABASE}?user=#{CURRENT_USER}&password=#{PASSWORD}"
53
- require 'jdbc/mysql'
54
- Jdbc::MySQL.load_driver
55
- # java.sql.DriverManager.register_driver com.mysql.jdbc.Driver.new
56
- def new_connection
57
- java.sql.DriverManager.get_connection CONFIG
58
- end
59
- else
60
- require 'mysql2'
61
- def new_connection
62
- config = { :username => CURRENT_USER, :database => DATABASE, :host => "127.0.0.1", :encoding => 'utf8mb4' }
63
- config.merge!(:password => PASSWORD) unless PASSWORD.nil?
64
- Mysql2::Client.new config
65
- end
66
- end
67
- ActiveRecord::Base.establish_connection(
68
- :adapter => RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2',
69
- :user => CURRENT_USER,
70
- :password => PASSWORD,
71
- :host => '127.0.0.1',
72
- :database => DATABASE,
73
- :encoding => 'utf8mb4'
21
+ DB_NAME = ENV['DB_NAME'] || 'upsert_test'
22
+ # You *need* to specific DB_USER on certain combinations of JRuby/JDK as spawning a shell
23
+ # has some oddities
24
+ DB_USER = (ENV['DB_USER'] || `whoami`.chomp).to_s
25
+ raise "A DB_USER value is required" if DB_USER.empty?
26
+ DB_PASSWORD = ENV['DB_PASSWORD']
27
+ DB_HOST = ENV['DB_HOST'] || '127.0.0.1'
28
+
29
+ def self.db_env
30
+ @db_env ||= base_params(nil, false).map { |k, v| [":#{k}", v.to_s.empty? ? nil : Shellwords.escape(v)] }.to_h
31
+ end
32
+
33
+ def self.adapter_name(adapter = nil)
34
+ RUBY_PLATFORM != "java" && adapter == "mysql" ? "mysql2" : adapter
35
+ end
36
+
37
+ def self.base_params(adapter = nil, show_additional_params = true)
38
+ return { :adapter => "sqlite3", :database => "temp.db", cache: "shared" } if adapter == "sqlite3"
39
+ {
40
+ host: DB_HOST,
41
+ database: DB_NAME,
42
+ dbname: DB_NAME,
43
+ username: DB_USER,
44
+ user: DB_USER,
45
+ password: DB_PASSWORD,
46
+ adapter: adapter,
47
+ }.merge(
48
+ show_additional_params ? additional_params(adapter) : {}
74
49
  )
75
- ActiveRecord::Base.connection.execute "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci"
50
+ end
51
+
52
+ def self.additional_params(adapter = nil)
53
+ {
54
+ "mysql" => { encoding: "utf8mb4" },
55
+ "mysql2" => { encoding: "utf8mb4" },
56
+ }.fetch(adapter, {})
57
+ end
58
+
59
+ def self.postgresql_call(string)
60
+ Kernel.system "PGHOST=#{db_env[":host"]} PGUSER=#{db_env[":user"]} PGPASSWORD=#{db_env[":password"]} #{string.gsub(/:[a-z]+/, db_env)}"
61
+ end
62
+
63
+ def self.mysql_call(string)
64
+ Kernel.system "mysql -h #{db_env[":host"]} -u #{db_env[":user"]} --password=#{db_env[":password"]} #{string.gsub(/:[a-z]+/, db_env)}"
65
+ end
76
66
 
77
- when 'sqlite3'
78
- CONFIG = { :adapter => 'sqlite3', :database => 'file::memory:?cache=shared' }
79
- if RUBY_PLATFORM == 'java'
80
- # CONFIG = 'jdbc:sqlite://test.sqlite3'
81
- require 'jdbc/sqlite3'
67
+ SYSTEM_CALLS = {
68
+ "postgresql" => [
69
+ %{ dropdb :dbname },
70
+ %{ createdb :dbname },
71
+ %{ psql -d :dbname -c 'DROP SCHEMA IF EXISTS :dbname2 CASCASE' },
72
+ %{ psql -d :dbname -c 'CREATE SCHEMA :dbname2' },
73
+ ],
74
+ "mysql" => [
75
+ %{ -e "DROP DATABASE IF EXISTS :dbname" },
76
+ %{ -e "DROP DATABASE IF EXISTS :dbname2" },
77
+ %{ -e "CREATE DATABASE :dbname CHARSET utf8mb4 COLLATE utf8mb4_general_ci" },
78
+ %{ -e "CREATE DATABASE :dbname2 CHARSET utf8mb4 COLLATE utf8mb4_general_ci" },
79
+ ]
80
+ }.freeze
81
+
82
+ REQUIRES = {
83
+ "mysql" => "mysql2",
84
+ "postgresql" => "pg",
85
+ "sqlite3" => "sqlite3",
86
+ "java-postgresql" => "jdbc/postgres",
87
+ "java-mysql" => "jdbc/mysql",
88
+ "java-sqlite3" => "jdbc/sqlite3",
89
+ }.freeze
90
+
91
+ NEW_CONNECTION = {
92
+ "postgresql" => ->(base_params) { PG::Connection.new(base_params.except(:database, :username, :adapter)) },
93
+ "mysql" => ->(base_params) { Mysql2::Client.new(base_params) },
94
+ "sqlite3" => ->(base_params) { ActiveRecord::Base.connection.raw_connection },
95
+ }
96
+
97
+ POST_CONNECTION = {
98
+ "mysql" => -> { ActiveRecord::Base.connection.execute "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci" },
99
+ "sqlite3" => -> { [ActiveRecord::Base.connection, ::DB].each { |c| c.execute "ATTACH DATABASE 'temp2.db' AS #{DB_NAME}2" } },
100
+ }
101
+
102
+ SYSTEM_CALLS.fetch(ENV["DB"], []).each do |str|
103
+ send("#{ENV["DB"]}_call", str)
104
+ end
105
+
106
+ if RUBY_PLATFORM == 'java'
107
+ CONFIG = "jdbc:#{ENV["DB"]}://#{DB_HOST}/#{DB_NAME}"
108
+ require REQUIRES["java-#{ENV["DB"]}"]
109
+
110
+ case ENV["DB"]
111
+ when "postgresql" then Jdbc::Postgres.load_driver
112
+ when "mysql" then Jdbc::MySQL.load_driver
113
+ when "sqlite3"
82
114
  Jdbc::SQLite3.load_driver
115
+ CONFIG = "jdbc:sqlite::memory:?cache=shared"
116
+ end
117
+
118
+ def new_connection
119
+ java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("+00:00"))
120
+ java.sql.DriverManager.get_connection CONFIG, DB_USER, DB_PASSWORD
121
+ end
122
+ else
123
+ case ENV['DB']
124
+ when "postgresql", "mysql"
125
+ require REQUIRES[ENV["DB"]]
83
126
  def new_connection
84
- ActiveRecord::Base.connection.raw_connection.connection
127
+ NEW_CONNECTION[ENV["DB"]].call(self.class.base_params(ENV["DB"]))
85
128
  end
86
- else
87
- require 'sqlite3'
129
+ when "sqlite3"
130
+ require REQUIRES[ENV["DB"]]
88
131
  def new_connection
89
- ActiveRecord::Base.connection.raw_connection
132
+ NEW_CONNECTION[ENV["DB"]].call(self.class.base_params(ENV["DB"]))
90
133
  end
134
+ CONFIG = { :adapter => "sqlite3", :database => "temp.db", cache: "shared" }
135
+
91
136
  end
92
- ActiveRecord::Base.establish_connection CONFIG
137
+ end
93
138
 
94
- when 'postgres'
95
- raise "please use DB=postgresql NOT postgres"
139
+ ActiveRecord::Base.establish_connection(
140
+ base_params(adapter_name(ENV["DB"]))
141
+ )
142
+ ari_adapter_name = adapter_name(ENV["DB"]) == "mysql" ? "mysql2" : adapter_name(ENV["DB"])
143
+ require "activerecord-import/active_record/adapters/#{ari_adapter_name}_adapter"
144
+ end
96
145
 
97
- else
98
- raise "not supported"
99
- end
146
+ raise "not supported" unless RawConnectionFactory.instance_methods.include?(:new_connection)
147
+
148
+ config = ActiveRecord::Base.connection.instance_variable_get(:@config)
149
+ config[:adapter] = case config[:adapter]
150
+ when "postgresql" then "postgres"
151
+ when "sqlite3" then "sqlite"
152
+ else config[:adapter]
153
+ end
154
+ params = if RUBY_PLATFORM == "java"
155
+ RawConnectionFactory::CONFIG
156
+ else
157
+ config.merge(
158
+ :user => config.values_at(:user, :username).compact.first,
159
+ :host => config.values_at(:host, :hostaddr).compact.first,
160
+ :database => config.values_at(:database, :dbname).compact.first
161
+ )
162
+ end
163
+ DB = if RUBY_PLATFORM == "java"
164
+ Sequel.connect(
165
+ params,
166
+ :user => RawConnectionFactory::DB_USER,
167
+ :password => RawConnectionFactory::DB_PASSWORD
168
+ )
169
+ elsif ENV["DB"] == "sqlite3"
170
+ Kernel.at_exit { FileUtils.rm(Dir.glob("temp*.db")) }
171
+ Sequel.sqlite("temp.db")
172
+ else
173
+ Sequel.connect(params)
100
174
  end
101
175
 
102
176
  $conn_factory = RawConnectionFactory.new
103
177
  $conn = $conn_factory.new_connection
178
+ RawConnectionFactory::POST_CONNECTION.fetch(ENV["DB"], -> {}).call
179
+
104
180
 
105
181
  require 'logger'
106
182
  require 'fileutils'
@@ -113,49 +189,71 @@ else
113
189
  ActiveRecord::Base.logger.level = Logger::WARN
114
190
  end
115
191
 
116
- class Pet < ActiveRecord::Base
117
- col :name, limit: 191 # utf8mb4 in mysql requirement
118
- col :gender
119
- col :spiel
120
- col :good, :type => :boolean
121
- col :lovability, :type => :float
122
- col :morning_walk_time, :type => :datetime
123
- col :zipped_biography, :type => :binary
124
- col :tag_number, :type => :integer
125
- col :big_tag_number, :type => :bigint
126
- col :birthday, :type => :date
127
- col :home_address, :type => :text
128
- if ENV['DB'] == 'postgresql'
129
- col :tsntz, :type => 'timestamp without time zone'
130
- end
131
- add_index :name, :unique => true
132
- end
133
- if ENV['DB'] == 'postgresql' && UNIQUE_CONSTRAINT
192
+ if ENV['DB'] == 'postgresql'
134
193
  begin
135
- Pet.connection.execute("ALTER TABLE pets DROP CONSTRAINT IF EXISTS unique_name")
194
+ DB << "ALTER TABLE pets DROP CONSTRAINT IF EXISTS unique_name"
136
195
  rescue => e
137
196
  puts e.inspect
138
197
  end
139
198
  end
140
199
 
141
- Pet.auto_upgrade!
142
-
143
- if ENV['DB'] == 'postgresql' && UNIQUE_CONSTRAINT
144
- Pet.connection.execute("ALTER TABLE pets ADD CONSTRAINT unique_name UNIQUE (name)")
200
+ class InternalMigration
201
+ DEFINITIONS = {
202
+ pets: ->(db) {
203
+ primary_key :id
204
+ String :name, { size: 191 }.merge(ENV["DB"] == "mysql" || UNIQUE_CONSTRAINT ? { index: { unique: true } } : {})
205
+ String :gender
206
+ String :spiel
207
+ TrueClass :good
208
+ BigDecimal :lovability, size: [30, 15] # 15 integer digits and 15 fractional digits
209
+ DateTime :morning_walk_time
210
+ File :zipped_biography
211
+ Integer :tag_number
212
+ Bignum :big_tag_number
213
+ Date :birthday
214
+ String :home_address, text: true
215
+
216
+ if db.database_type == :postgres
217
+ column :tsntz, "timestamp without time zone"
218
+ end
219
+ },
220
+ tasks: ->(db) {
221
+ primary_key :id
222
+ String :name
223
+ DateTime :created_at
224
+ DateTime :created_on
225
+ },
226
+ people: ->(db) {
227
+ primary_key :id
228
+ String :"First Name"
229
+ String :"Last Name"
230
+ },
231
+ alphabets: ->(db) {
232
+ ("a".."z").each do |col|
233
+ Integer "the_letter_#{col}".to_sym
234
+ end
235
+ }
236
+ }
145
237
  end
146
238
 
147
- class Task < ActiveRecord::Base
148
- col :name
149
- col :created_at, :type => :datetime
150
- col :created_on, :type => :datetime
239
+ Sequel.migration do
240
+ change do
241
+ db = self
242
+ InternalMigration::DEFINITIONS.each do |table, blk|
243
+ create_table?(table) do
244
+ instance_exec(db, &blk)
245
+ end
246
+ end
247
+ end
248
+ end.apply(DB, :up)
249
+
250
+ if ENV['DB'] == 'postgresql' && UNIQUE_CONSTRAINT
251
+ DB << "ALTER TABLE pets ADD CONSTRAINT unique_name UNIQUE (name)"
151
252
  end
152
- Task.auto_upgrade!
153
253
 
154
- class Person < ActiveRecord::Base
155
- col :"First Name"
156
- col :"Last Name"
254
+ %i[Pet Task Person Alphabet].each do |name|
255
+ Object.const_set(name, Class.new(ActiveRecord::Base))
157
256
  end
158
- Person.auto_upgrade!
159
257
 
160
258
  require 'zlib'
161
259
  require 'benchmark'
@@ -188,7 +286,7 @@ module SpecHelper
188
286
  names.choice
189
287
  end
190
288
  setter = {
191
- :lovability => BigDecimal.new(rand(1e11).to_s, 2),
289
+ :lovability => BigDecimal(rand(1e11).to_s, 2),
192
290
  :tag_number => rand(1e8),
193
291
  :spiel => Faker::Lorem.sentences.join,
194
292
  :good => true,
@@ -278,6 +376,35 @@ module SpecHelper
278
376
  upsert_time.should be < ar_time
279
377
  $stderr.puts " Upsert was #{((ar_time - upsert_time) / ar_time * 100).round}% faster than #{competition}"
280
378
  end
379
+
380
+ def clone_ar_class(klass, table_name)
381
+ u = Upsert.new $conn, klass.table_name
382
+ new_table_name = [*table_name].compact
383
+ # AR's support for quoting of schema and table names is horrendous
384
+ # schema.table and schema.`table` are considiered different names on MySQL, but
385
+ # schema.table and schema."table" are correctly considered the same on Postgres
386
+ sequel_table_name = new_table_name.map(&:to_sym)
387
+ new_table_name[-1] = u.connection.quote_ident(new_table_name[-1]) if new_table_name[-1].to_s.index('.')
388
+ new_table_name = new_table_name.join('.')
389
+
390
+ Sequel.migration do
391
+ change do
392
+ db = self
393
+ create_table?(sequel_table_name.length > 1 ? Sequel.qualify(*sequel_table_name) : sequel_table_name.first) do
394
+ instance_exec(db, &InternalMigration::DEFINITIONS[klass.table_name.to_sym])
395
+ end
396
+ end
397
+ end.apply(DB, :up)
398
+
399
+ cls = Class.new(klass)
400
+ cls.class_eval do
401
+ self.table_name = new_table_name
402
+ def self.quoted_table_name
403
+ new_table_name
404
+ end
405
+ end
406
+ cls
407
+ end
281
408
  end
282
409
 
283
410
  RSpec.configure do |c|