upsert 2.1.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.
- checksums.yaml +5 -5
- data/.ruby-version +1 -0
- data/.standard.yml +1 -0
- data/.travis.yml +60 -12
- data/CHANGELOG +39 -0
- data/Gemfile +12 -1
- data/LICENSE +3 -1
- data/README.md +47 -6
- data/Rakefile +7 -1
- data/lib/upsert.rb +54 -11
- data/lib/upsert/column_definition/mysql.rb +2 -2
- data/lib/upsert/column_definition/postgresql.rb +9 -8
- data/lib/upsert/column_definition/sqlite3.rb +3 -3
- data/lib/upsert/connection/Java_ComMysqlJdbc_JDBC4Connection.rb +11 -5
- data/lib/upsert/connection/Java_OrgPostgresqlJdbc_PgConnection.rb +33 -0
- data/lib/upsert/connection/PG_Connection.rb +10 -1
- data/lib/upsert/connection/jdbc.rb +20 -1
- data/lib/upsert/connection/postgresql.rb +2 -3
- data/lib/upsert/merge_function.rb +5 -4
- data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc_PgConnection.rb +27 -0
- data/lib/upsert/merge_function/PG_Connection.rb +11 -42
- data/lib/upsert/merge_function/postgresql.rb +215 -1
- data/lib/upsert/merge_function/sqlite3.rb +10 -0
- data/lib/upsert/version.rb +1 -1
- data/spec/active_record_upsert_spec.rb +10 -0
- data/spec/correctness_spec.rb +34 -5
- data/spec/database_functions_spec.rb +16 -9
- data/spec/database_spec.rb +7 -0
- data/spec/hstore_spec.rb +56 -55
- data/spec/jruby_spec.rb +9 -0
- data/spec/logger_spec.rb +8 -6
- data/spec/postgresql_spec.rb +94 -0
- data/spec/reserved_words_spec.rb +21 -17
- data/spec/sequel_spec.rb +26 -7
- data/spec/spec_helper.rb +251 -92
- data/spec/speed_spec.rb +3 -32
- data/spec/threaded_spec.rb +35 -12
- data/spec/type_safety_spec.rb +2 -1
- data/travis/install_postgres.sh +18 -0
- data/travis/run_docker_db.sh +20 -0
- data/travis/tune_mysql.sh +7 -0
- data/upsert-java.gemspec +13 -0
- data/upsert.gemspec +9 -57
- data/upsert.gemspec.common +107 -0
- metadata +53 -40
- data/lib/upsert/connection/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +0 -15
- data/lib/upsert/merge_function/Java_OrgPostgresqlJdbc4_Jdbc4Connection.rb +0 -39
data/spec/jruby_spec.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
it "works correct with large ints" do
|
4
|
+
u = Upsert.new($conn, :pets)
|
5
|
+
Pet.create(:name => "Jerry", :big_tag_number => 2)
|
6
|
+
u.row({ :name => 'Jerry' }, :big_tag_number => 3599657714)
|
7
|
+
Pet.find_by_name('Jerry').big_tag_number.should == 3599657714
|
8
|
+
end
|
9
|
+
end if RUBY_PLATFORM == 'java'
|
data/spec/logger_spec.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
describe Upsert do
|
3
|
+
MUTEX_FOR_PERFORM = Mutex.new
|
3
4
|
describe "logger" do
|
4
5
|
it "logs where you tell it" do
|
5
6
|
begin
|
6
7
|
old_logger = Upsert.logger
|
7
8
|
io = StringIO.new
|
8
|
-
|
9
|
+
MUTEX_FOR_PERFORM.synchronize do
|
9
10
|
Upsert.logger = Logger.new(io)
|
10
11
|
|
11
12
|
Upsert.logger.warn "hello"
|
12
13
|
|
13
14
|
io.rewind
|
14
|
-
io.read.chomp.should
|
15
|
+
io.read.chomp.should =~ /hello/
|
15
16
|
end
|
16
17
|
ensure
|
17
18
|
Upsert.logger = old_logger
|
@@ -19,12 +20,12 @@ describe Upsert do
|
|
19
20
|
end
|
20
21
|
|
21
22
|
it "logs queries" do
|
23
|
+
old_logger = Upsert.logger
|
22
24
|
begin
|
23
|
-
old_logger = Upsert.logger
|
24
25
|
io = StringIO.new
|
25
|
-
|
26
|
+
MUTEX_FOR_PERFORM.synchronize do
|
26
27
|
Upsert.logger = Logger.new(io)
|
27
|
-
|
28
|
+
|
28
29
|
u = Upsert.new($conn, :pets)
|
29
30
|
u.row(:name => 'Jerry')
|
30
31
|
|
@@ -37,7 +38,8 @@ describe Upsert do
|
|
37
38
|
log.should =~ /call #{Upsert::MergeFunction::NAME_PREFIX}_pets_SEL_name/i
|
38
39
|
when /p.*g/i
|
39
40
|
# [54ae2eea857] Possibly much more useful debug output
|
40
|
-
log
|
41
|
+
# TODO: Should check for both upsert and non-upsert log output
|
42
|
+
log.should =~ /selector:|SHOW server_version/i
|
41
43
|
else
|
42
44
|
raise "not sure"
|
43
45
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'upsert/merge_function/postgresql'
|
3
|
+
|
4
|
+
describe Upsert do
|
5
|
+
version = 'postgresql' == ENV['DB'] ? Upsert::MergeFunction::Postgresql.extract_version(
|
6
|
+
Pet.connection.select_value("SHOW server_version")
|
7
|
+
) : 0
|
8
|
+
|
9
|
+
let(:upsert) do
|
10
|
+
Upsert.new($conn, :pets)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "uses the native method if available (#{(UNIQUE_CONSTRAINT && version >= 90500).inspect})" do
|
14
|
+
p = Pet.create(:name => 'Jerry', :tag_number => 5)
|
15
|
+
upsert.row({ :name => 'Jerry'}, :tag_number => 6 )
|
16
|
+
expect(upsert.instance_variable_get(:@merge_function_cache).values.first.use_pg_native?).to(
|
17
|
+
UNIQUE_CONSTRAINT && version >= 90500 ? be_truthy : be_falsey
|
18
|
+
)
|
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
|
94
|
+
end if ENV['DB'] == 'postgresql'
|
data/spec/reserved_words_spec.rb
CHANGED
@@ -7,33 +7,37 @@ describe Upsert do
|
|
7
7
|
end.map do |path|
|
8
8
|
IO.readlines(path)
|
9
9
|
end.flatten.map(&:chomp).select(&:present?).uniq
|
10
|
-
|
10
|
+
|
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
|
-
|
15
|
-
|
16
|
-
|
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.
|
21
|
-
|
22
|
-
words.each do |word|
|
23
|
-
col word
|
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
|
-
|
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)
|
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
|
@@ -43,4 +47,4 @@ describe Upsert do
|
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
46
|
-
end
|
50
|
+
end
|
data/spec/sequel_spec.rb
CHANGED
@@ -4,17 +4,36 @@ require 'sequel'
|
|
4
4
|
describe Upsert do
|
5
5
|
describe "Plays nice with Sequel" do
|
6
6
|
config = ActiveRecord::Base.connection.instance_variable_get(:@config)
|
7
|
-
case
|
8
|
-
|
9
|
-
|
7
|
+
config[:adapter] = case config[:adapter]
|
8
|
+
when 'postgresql' then 'postgres'
|
9
|
+
when 'sqlite3' then 'sqlite'
|
10
|
+
else config[:adapter]
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:db) do
|
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
|
10
29
|
end
|
11
30
|
|
12
31
|
it "Doesn't explode on connection" do
|
13
|
-
expect {
|
32
|
+
expect { db }.to_not raise_error
|
14
33
|
end
|
15
34
|
|
16
35
|
it "Doesn't explode when using DB.pool.hold" do
|
17
|
-
|
36
|
+
db.pool.hold do |conn|
|
18
37
|
expect {
|
19
38
|
upsert = Upsert.new(conn, :pets)
|
20
39
|
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
@@ -25,7 +44,7 @@ describe Upsert do
|
|
25
44
|
end
|
26
45
|
|
27
46
|
it "Doesn't explode when using DB.synchronize" do
|
28
|
-
|
47
|
+
db.synchronize do |conn|
|
29
48
|
expect {
|
30
49
|
upsert = Upsert.new(conn, :pets)
|
31
50
|
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
@@ -35,4 +54,4 @@ describe Upsert do
|
|
35
54
|
end
|
36
55
|
end
|
37
56
|
end
|
38
|
-
end
|
57
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,94 +1,182 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require 'bundler/setup'
|
3
|
+
Bundler.require(:default, :development)
|
3
4
|
|
4
|
-
require '
|
5
|
+
require 'shellwords'
|
6
|
+
require "sequel"
|
7
|
+
Sequel.default_timezone = :utc
|
8
|
+
Sequel.extension :migration
|
5
9
|
|
6
|
-
require
|
10
|
+
require "active_record"
|
11
|
+
require "activerecord-import"
|
7
12
|
ActiveRecord::Base.default_timezone = :utc
|
8
13
|
|
9
|
-
|
14
|
+
raise "A DB value is required" unless ENV["DB"]
|
15
|
+
ENV['DB'] = 'postgresql' if ENV['DB'].to_s =~ /postgresql/i
|
16
|
+
UNIQUE_CONSTRAINT = ENV['UNIQUE_CONSTRAINT'] == 'true'
|
10
17
|
|
11
|
-
|
12
|
-
|
13
|
-
ENV['DB'] ||= 'mysql'
|
18
|
+
raise "please use DB=postgresql NOT postgres" if ENV["DB"] == "postgres"
|
14
19
|
|
15
20
|
class RawConnectionFactory
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
Kernel.system %{ mysql -u #{CURRENT_USER} #{password_argument} -e "CREATE DATABASE #{DATABASE} CHARSET utf8" }
|
47
|
-
if RUBY_PLATFORM == 'java'
|
48
|
-
CONFIG = "jdbc:mysql://127.0.0.1/#{DATABASE}?user=#{CURRENT_USER}&password=#{PASSWORD}"
|
49
|
-
require 'jdbc/mysql'
|
50
|
-
Jdbc::MySQL.load_driver
|
51
|
-
# java.sql.DriverManager.register_driver com.mysql.jdbc.Driver.new
|
52
|
-
def new_connection
|
53
|
-
java.sql.DriverManager.get_connection CONFIG
|
54
|
-
end
|
55
|
-
else
|
56
|
-
require 'mysql2'
|
57
|
-
def new_connection
|
58
|
-
config = { :username => CURRENT_USER, :database => DATABASE, :host => "127.0.0.1" }
|
59
|
-
config.merge!(:password => PASSWORD) unless PASSWORD.empty?
|
60
|
-
Mysql2::Client.new config
|
61
|
-
end
|
62
|
-
end
|
63
|
-
ActiveRecord::Base.establish_connection "#{RUBY_PLATFORM == 'java' ? 'mysql' : 'mysql2'}://#{CURRENT_USER}:#{PASSWORD}@127.0.0.1/#{DATABASE}"
|
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) : {}
|
49
|
+
)
|
50
|
+
end
|
64
51
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
66
|
+
|
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"
|
70
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"]]
|
71
126
|
def new_connection
|
72
|
-
|
127
|
+
NEW_CONNECTION[ENV["DB"]].call(self.class.base_params(ENV["DB"]))
|
73
128
|
end
|
74
|
-
|
75
|
-
require
|
129
|
+
when "sqlite3"
|
130
|
+
require REQUIRES[ENV["DB"]]
|
76
131
|
def new_connection
|
77
|
-
|
132
|
+
NEW_CONNECTION[ENV["DB"]].call(self.class.base_params(ENV["DB"]))
|
78
133
|
end
|
134
|
+
CONFIG = { :adapter => "sqlite3", :database => "temp.db", cache: "shared" }
|
135
|
+
|
79
136
|
end
|
80
|
-
|
137
|
+
end
|
81
138
|
|
82
|
-
|
83
|
-
|
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
|
84
145
|
|
85
|
-
|
86
|
-
|
87
|
-
|
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)
|
88
174
|
end
|
89
175
|
|
90
176
|
$conn_factory = RawConnectionFactory.new
|
91
177
|
$conn = $conn_factory.new_connection
|
178
|
+
RawConnectionFactory::POST_CONNECTION.fetch(ENV["DB"], -> {}).call
|
179
|
+
|
92
180
|
|
93
181
|
require 'logger'
|
94
182
|
require 'fileutils'
|
@@ -101,30 +189,71 @@ else
|
|
101
189
|
ActiveRecord::Base.logger.level = Logger::WARN
|
102
190
|
end
|
103
191
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
col :lovability, :type => :float
|
110
|
-
col :morning_walk_time, :type => :datetime
|
111
|
-
col :zipped_biography, :type => :binary
|
112
|
-
col :tag_number, :type => :integer
|
113
|
-
col :birthday, :type => :date
|
114
|
-
col :home_address, :type => :text
|
115
|
-
if ENV['DB'] == 'postgresql'
|
116
|
-
col :tsntz, :type => 'timestamp without time zone'
|
192
|
+
if ENV['DB'] == 'postgresql'
|
193
|
+
begin
|
194
|
+
DB << "ALTER TABLE pets DROP CONSTRAINT IF EXISTS unique_name"
|
195
|
+
rescue => e
|
196
|
+
puts e.inspect
|
117
197
|
end
|
118
|
-
add_index :name, :unique => true
|
119
198
|
end
|
120
|
-
Pet.auto_upgrade!
|
121
199
|
|
122
|
-
class
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
+
}
|
237
|
+
end
|
238
|
+
|
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)"
|
252
|
+
end
|
253
|
+
|
254
|
+
%i[Pet Task Person Alphabet].each do |name|
|
255
|
+
Object.const_set(name, Class.new(ActiveRecord::Base))
|
126
256
|
end
|
127
|
-
Task.auto_upgrade!
|
128
257
|
|
129
258
|
require 'zlib'
|
130
259
|
require 'benchmark'
|
@@ -157,7 +286,7 @@ module SpecHelper
|
|
157
286
|
names.choice
|
158
287
|
end
|
159
288
|
setter = {
|
160
|
-
:lovability => BigDecimal
|
289
|
+
:lovability => BigDecimal(rand(1e11).to_s, 2),
|
161
290
|
:tag_number => rand(1e8),
|
162
291
|
:spiel => Faker::Lorem.sentences.join,
|
163
292
|
:good => true,
|
@@ -176,7 +305,7 @@ module SpecHelper
|
|
176
305
|
def assert_same_result(records, &blk)
|
177
306
|
blk.call(records)
|
178
307
|
ref1 = Pet.order(:name).all.map { |pet| pet.attributes.except('id') }
|
179
|
-
|
308
|
+
|
180
309
|
Pet.delete_all
|
181
310
|
|
182
311
|
Upsert.batch($conn, :pets) do |upsert|
|
@@ -197,6 +326,7 @@ module SpecHelper
|
|
197
326
|
expected_records.each do |selector, setter|
|
198
327
|
setter ||= {}
|
199
328
|
found = model.where(selector).map { |record| record.attributes.except('id') }
|
329
|
+
expect(found).to_not be_empty, { :selector => selector, :setter => setter }.inspect
|
200
330
|
expected = [ selector.stringify_keys.merge(setter.stringify_keys) ]
|
201
331
|
compare_attribute_sets expected, found
|
202
332
|
end
|
@@ -230,7 +360,7 @@ module SpecHelper
|
|
230
360
|
Pet.delete_all
|
231
361
|
sleep 1
|
232
362
|
# --
|
233
|
-
|
363
|
+
|
234
364
|
ar_time = Benchmark.realtime { blk.call(records) }
|
235
365
|
|
236
366
|
Pet.delete_all
|
@@ -246,6 +376,35 @@ module SpecHelper
|
|
246
376
|
upsert_time.should be < ar_time
|
247
377
|
$stderr.puts " Upsert was #{((ar_time - upsert_time) / ar_time * 100).round}% faster than #{competition}"
|
248
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
|
249
408
|
end
|
250
409
|
|
251
410
|
RSpec.configure do |c|
|