upsert 2.1.0 → 2.9.10
Sign up to get free protection for your applications and to get access to all the features.
- 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|
|