fast_inserter 0.1.6 → 2.0.0
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/.github/workflows/ruby.yml +55 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +21 -1
- data/README.md +4 -4
- data/fast_inserter.gemspec +3 -3
- data/lib/fast_inserter/fast_inserter_base.rb +53 -16
- data/lib/fast_inserter/version.rb +1 -1
- metadata +12 -13
- data/.travis.yml +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b34b8efaffa71fa07824869fc4786cfbd075c878ba99c96989846bf0e3a5c377
|
4
|
+
data.tar.gz: e688e45fe0316029dab434a56ff1ad5f2c1cdfb7644995d2f4fcc3762ecbf5a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa47c991faaf1170779cac718a85a1d89a28b393feae40014296100281d1ecab918dba51ca64733dc8c193ac7784b2fa5866b9a3642f82219c9bf58007427f4d
|
7
|
+
data.tar.gz: 73b3b9ec678d4588fac02f9477aac000391b34f31d072194be4690798d541823fcccdfda56ac79b854f089e247d2a33377cfc25707384e75594405419a916022
|
@@ -0,0 +1,55 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: push
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build-and-test-job:
|
7
|
+
runs-on: ubuntu-20.04
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby-version: ['2.6.10', '2.7.8', '3.0.6', '3.1.4', '3.2.2', 'ruby-head']
|
12
|
+
database: [sqlite, pg, mysql]
|
13
|
+
|
14
|
+
# Postgres has to be explicitly included. The runner environment already has mysql installed by default.
|
15
|
+
services:
|
16
|
+
postgres:
|
17
|
+
# Pin to this commit: version 14.0
|
18
|
+
image: postgres@sha256:2b8a60d4ae4b3cf9dc5ae823700f699fdabdb0d03901dd480c9a410ca72c4571
|
19
|
+
env:
|
20
|
+
# Password is required. Postgres won't start without it.
|
21
|
+
POSTGRES_PASSWORD: postgres_password
|
22
|
+
ports:
|
23
|
+
- 5432:5432
|
24
|
+
options: >-
|
25
|
+
--health-cmd pg_isready
|
26
|
+
--health-interval 10s
|
27
|
+
--health-timeout 5s
|
28
|
+
--health-retries 5
|
29
|
+
|
30
|
+
env:
|
31
|
+
# CAUTION - the DB env var is also used by the test setup code. Don't change the name of it!
|
32
|
+
DB: ${{ matrix.database }}
|
33
|
+
RV: ${{ matrix.ruby-version }}
|
34
|
+
PGPASSWORD: postgres_password
|
35
|
+
|
36
|
+
steps:
|
37
|
+
- name: Init
|
38
|
+
run: echo "Using Ruby $RV and '$DB' as the database."
|
39
|
+
- name: Checkout
|
40
|
+
# Pin to this commit: v2
|
41
|
+
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
42
|
+
- name: Set up Database
|
43
|
+
run: |
|
44
|
+
if [ "$DB" = "pg" ]; then psql -h localhost -c 'DROP DATABASE IF EXISTS fast_inserter;' -U postgres; fi
|
45
|
+
if [ "$DB" = "pg" ]; then psql -h localhost -c 'create database fast_inserter;' -U postgres; fi
|
46
|
+
if [ "$DB" = "mysql" ]; then sudo systemctl start mysql.service; fi
|
47
|
+
if [ "$DB" = "mysql" ]; then mysql -e 'create database IF NOT EXISTS fast_inserter;' -uroot -proot; fi
|
48
|
+
- name: Set up Ruby
|
49
|
+
uses: ruby/setup-ruby@v1
|
50
|
+
with:
|
51
|
+
ruby-version: ${{ matrix.ruby-version }}
|
52
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
53
|
+
- name: Run tests
|
54
|
+
# The test setup code reads the DB env var to determine which database to use.
|
55
|
+
run: bundle exec rake
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.6
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,24 @@
|
|
1
|
-
##
|
1
|
+
## 2.0.0 (April 20, 2023) ##
|
2
|
+
|
3
|
+
* Drop Ruby version 2.4 and 2.5 support
|
4
|
+
* Add Ruby versions 3.0.6, 3.1.4, 3.2.2
|
5
|
+
* Update development Ruby version to 3.0.6
|
6
|
+
* Update type casting in preparation for rails 7
|
7
|
+
* Account for MySQL type casting in ActiveRecord 7
|
8
|
+
|
9
|
+
*Amy Lin, Tim Kelly*
|
10
|
+
|
11
|
+
## 1.0.0.pre (January 7, 2019) ##
|
12
|
+
|
13
|
+
* Use database IN clause when checking for existing records.
|
14
|
+
|
15
|
+
*Jordon Dornbos*
|
16
|
+
|
17
|
+
## 0.1.6 (July 3, 2018) ##
|
18
|
+
|
19
|
+
* Fix check_for_existing with multiple variable columns.
|
20
|
+
|
21
|
+
*Josh Warfield*
|
2
22
|
|
3
23
|
* Remove support for Ruby 2.2. Add support for Ruby 2.4 and 2.5.
|
4
24
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# FastInserter
|
2
2
|
|
3
|
+
[](https://github.com/joinhandshake/fast_inserter/actions)
|
3
4
|
[](https://badge.fury.io/rb/fast_inserter)
|
4
|
-
[](https://travis-ci.org/joinhandshake/fast_inserter)
|
5
5
|
|
6
6
|
Use raw SQL to insert database records in bulk, fast. Supports uniqueness constraints, timestamps, and checking for existing records.
|
7
7
|
|
@@ -51,9 +51,9 @@ params = {
|
|
51
51
|
options: {
|
52
52
|
timestamps: true,
|
53
53
|
unique: true,
|
54
|
-
check_for_existing: true
|
55
|
-
group_size: 2_000
|
54
|
+
check_for_existing: true
|
56
55
|
},
|
56
|
+
group_size: 2_000,
|
57
57
|
variable_column: 'user_id',
|
58
58
|
values: user_ids
|
59
59
|
}
|
@@ -89,7 +89,7 @@ Queries the table for any values which already exist and removes them from the v
|
|
89
89
|
|
90
90
|
### group_size
|
91
91
|
|
92
|
-
Insertions will be broken up into batches. This specifies the number of records you want to insert per batch. Default is
|
92
|
+
Insertions will be broken up into batches. This specifies the number of records you want to insert per batch. Default is 1,000.
|
93
93
|
|
94
94
|
### variable_column
|
95
95
|
|
data/fast_inserter.gemspec
CHANGED
@@ -21,16 +21,16 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.add_runtime_dependency 'activerecord', '>= 4.1.0'
|
23
23
|
|
24
|
-
spec.required_ruby_version = ">= 2.
|
24
|
+
spec.required_ruby_version = ">= 2.3.8"
|
25
25
|
|
26
26
|
spec.add_development_dependency "bundler"
|
27
|
-
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rake"
|
28
28
|
spec.add_development_dependency "rspec"
|
29
29
|
spec.add_development_dependency "database_cleaner"
|
30
30
|
|
31
31
|
case ENV['DB']
|
32
32
|
when "mysql"; spec.add_development_dependency "mysql2"
|
33
|
-
when "sqlite"; spec.add_development_dependency "sqlite3"
|
33
|
+
when "sqlite"; spec.add_development_dependency "sqlite3", ">= 1.4"
|
34
34
|
when "pg"; spec.add_development_dependency "pg"
|
35
35
|
else spec.add_development_dependency "sqlite3" # Default
|
36
36
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
1
3
|
# Highly based off of https://github.com/sportngin/m2m_fast_insert
|
2
4
|
# Unfortunately, that gem was not up to date for rails 4.
|
3
5
|
#
|
@@ -18,8 +20,7 @@
|
|
18
20
|
# options: {
|
19
21
|
# timestamps: true,
|
20
22
|
# unique: true,
|
21
|
-
# check_for_existing: true
|
22
|
-
# group_size: 1_000
|
23
|
+
# check_for_existing: true
|
23
24
|
# },
|
24
25
|
# variable_column: 'user_id',
|
25
26
|
# values: user_ids
|
@@ -39,11 +40,11 @@
|
|
39
40
|
# A hash representing additional column values to set that you don't want
|
40
41
|
# to include in uniqueness checks or other pre-insertion operations.
|
41
42
|
# group_size: Integer
|
42
|
-
# The number of items you want to insert per batch of records.
|
43
|
+
# The number of items you want to insert per batch of records. Defaults to 1_000.
|
43
44
|
#
|
44
45
|
module FastInserter
|
45
46
|
class Base
|
46
|
-
DEFAULT_GROUP_SIZE =
|
47
|
+
DEFAULT_GROUP_SIZE = 1_000
|
47
48
|
|
48
49
|
def initialize(params)
|
49
50
|
@table_name = params[:table]
|
@@ -58,7 +59,7 @@ module FastInserter
|
|
58
59
|
all_values = params[:values].map { |value| Array(value) }
|
59
60
|
all_values.uniq! if @options[:unique]
|
60
61
|
group_size = Integer(params[:group_size] || ENV['FAST_INSERTER_GROUP_SIZE'] || DEFAULT_GROUP_SIZE)
|
61
|
-
@value_groups = all_values.
|
62
|
+
@value_groups = all_values.each_slice(group_size).to_a
|
62
63
|
end
|
63
64
|
|
64
65
|
# Iterates through the value groups (which is all values in groups of smaller sizes)
|
@@ -92,34 +93,70 @@ module FastInserter
|
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
96
|
+
def existing_values_sql(group_of_values)
|
97
|
+
sql = "SELECT #{@variable_columns.join(', ')} FROM #{@table_name} WHERE #{values_hash_to_sql(@static_columns)}"
|
98
|
+
|
99
|
+
if @variable_columns.length > 1
|
100
|
+
group_of_values_sql = group_of_values.map do |group|
|
101
|
+
values_hash = variable_column_values_to_hash(group)
|
102
|
+
"(#{values_hash_to_sql(values_hash)})"
|
103
|
+
end.join(' OR ')
|
104
|
+
|
105
|
+
sql += " AND (#{group_of_values_sql})"
|
106
|
+
else
|
107
|
+
values_to_check = ActiveRecord::Base.send(:sanitize_sql_array, ['?', group_of_values.flatten])
|
108
|
+
sql += " AND #{@variable_columns.first} IN (#{values_to_check})"
|
109
|
+
end
|
110
|
+
|
111
|
+
sql
|
112
|
+
end
|
113
|
+
|
95
114
|
# Queries for the existing values for a given group of values
|
96
115
|
def existing_values(group_of_values)
|
97
|
-
sql =
|
116
|
+
sql = existing_values_sql(group_of_values)
|
98
117
|
|
99
118
|
# NOTE: There are more elegant ways to get this field out of the resultset, but each database adaptor returns a different type
|
100
119
|
# of result from 'execute(sql)'. Potential classes for 'result' is Array (sqlite), Mysql2::Result (mysql2), PG::Result (pg). Each
|
101
120
|
# result can be enumerated into a list of arrays (mysql) or list of hashes (sqlite, pg)
|
102
121
|
results = ActiveRecord::Base.connection.execute(sql)
|
103
|
-
|
104
|
-
|
105
|
-
# Rather than a giant IN query in the sql statement (which can be bad for database performance),
|
106
|
-
# do the filtering of relevant values here in a ruby select.
|
107
|
-
group_of_values_strings = stringify_values(group_of_values)
|
108
|
-
existing_values & group_of_values_strings
|
122
|
+
stringify_values(results)
|
109
123
|
end
|
110
124
|
|
111
125
|
def stringify_values(results)
|
112
126
|
results.to_a.map do |result|
|
113
127
|
if result.is_a?(Hash)
|
114
|
-
@variable_columns.map { |col|
|
128
|
+
@variable_columns.map { |col| type_cast_column(result[col], column_definitions[col]) }
|
115
129
|
elsif result.is_a?(Array)
|
116
|
-
result.map.with_index { |val, i|
|
130
|
+
result.map.with_index { |val, i| type_cast_column(val, column_definitions[@variable_columns[i]]) }
|
117
131
|
end
|
118
132
|
end
|
119
133
|
end
|
120
134
|
|
121
|
-
|
122
|
-
|
135
|
+
# Passing a column to the method type_cast is deprecated
|
136
|
+
# and will be removed in Rails 7. For now, we can manually
|
137
|
+
# call lookup_cast_type_from_column and serialize our
|
138
|
+
# values with the returned type.
|
139
|
+
def type_cast_column(value, column = nil)
|
140
|
+
if column
|
141
|
+
type = ActiveRecord::Base.connection.lookup_cast_type_from_column(column)
|
142
|
+
value = type.serialize(value)
|
143
|
+
end
|
144
|
+
|
145
|
+
ActiveRecord::Base.connection.type_cast(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
def variable_column_values_to_hash(values)
|
149
|
+
hash = {}
|
150
|
+
|
151
|
+
@variable_columns.each_with_index do |variable_column, index|
|
152
|
+
hash[variable_column] = values[index]
|
153
|
+
end
|
154
|
+
|
155
|
+
hash
|
156
|
+
end
|
157
|
+
|
158
|
+
def values_hash_to_sql(values)
|
159
|
+
values.map do |key, value|
|
123
160
|
if value.nil?
|
124
161
|
"#{key} IS NULL"
|
125
162
|
else
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fast_inserter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Ringwelski
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
- Jordon Dornbos
|
10
10
|
- Matt Hickman
|
11
11
|
- Josh Warfield
|
12
|
-
autorequire:
|
12
|
+
autorequire:
|
13
13
|
bindir: exe
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2023-04-25 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activerecord
|
@@ -46,16 +46,16 @@ dependencies:
|
|
46
46
|
name: rake
|
47
47
|
requirement: !ruby/object:Gem::Requirement
|
48
48
|
requirements:
|
49
|
-
- - "
|
49
|
+
- - ">="
|
50
50
|
- !ruby/object:Gem::Version
|
51
|
-
version: '
|
51
|
+
version: '0'
|
52
52
|
type: :development
|
53
53
|
prerelease: false
|
54
54
|
version_requirements: !ruby/object:Gem::Requirement
|
55
55
|
requirements:
|
56
|
-
- - "
|
56
|
+
- - ">="
|
57
57
|
- !ruby/object:Gem::Version
|
58
|
-
version: '
|
58
|
+
version: '0'
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: rspec
|
61
61
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,10 +110,10 @@ executables: []
|
|
110
110
|
extensions: []
|
111
111
|
extra_rdoc_files: []
|
112
112
|
files:
|
113
|
+
- ".github/workflows/ruby.yml"
|
113
114
|
- ".gitignore"
|
114
115
|
- ".rspec"
|
115
116
|
- ".ruby-version"
|
116
|
-
- ".travis.yml"
|
117
117
|
- CHANGELOG.md
|
118
118
|
- Gemfile
|
119
119
|
- LICENSE.txt
|
@@ -129,7 +129,7 @@ homepage: https://github.com/strydercorp/fast_inserter
|
|
129
129
|
licenses:
|
130
130
|
- MIT
|
131
131
|
metadata: {}
|
132
|
-
post_install_message:
|
132
|
+
post_install_message:
|
133
133
|
rdoc_options: []
|
134
134
|
require_paths:
|
135
135
|
- lib
|
@@ -137,16 +137,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
137
|
requirements:
|
138
138
|
- - ">="
|
139
139
|
- !ruby/object:Gem::Version
|
140
|
-
version: 2.
|
140
|
+
version: 2.3.8
|
141
141
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - ">="
|
144
144
|
- !ruby/object:Gem::Version
|
145
145
|
version: '0'
|
146
146
|
requirements: []
|
147
|
-
|
148
|
-
|
149
|
-
signing_key:
|
147
|
+
rubygems_version: 3.2.33
|
148
|
+
signing_key:
|
150
149
|
specification_version: 4
|
151
150
|
summary: Quickly insert database records in bulk
|
152
151
|
test_files: []
|
data/.travis.yml
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
sudo: required
|
3
|
-
notifications:
|
4
|
-
email: false
|
5
|
-
rvm:
|
6
|
-
- 2.3.0
|
7
|
-
- 2.4.4
|
8
|
-
- 2.5.1
|
9
|
-
- ruby-head
|
10
|
-
env:
|
11
|
-
matrix:
|
12
|
-
- DB=pg
|
13
|
-
- DB=mysql
|
14
|
-
- DB=sqlite
|
15
|
-
before_script:
|
16
|
-
# PG and mysql is simpler.
|
17
|
-
- sh -c "if [ '$DB' = 'pg' ]; then psql -c 'DROP DATABASE IF EXISTS fast_inserter;' -U postgres; fi"
|
18
|
-
- sh -c "if [ '$DB' = 'pg' ]; then psql -c 'create database fast_inserter;' -U postgres; fi"
|
19
|
-
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS fast_inserter;'; fi"
|