active_data_frame 0.1.8 → 0.1.11
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/LICENSE +21 -0
- data/active_data_frame.gemspec +5 -5
- data/lib/active_data_frame/data_frame_proxy.rb +2 -2
- data/lib/active_data_frame/database.rb +62 -20
- data/lib/active_data_frame/row.rb +5 -15
- data/lib/active_data_frame/table.rb +1 -1
- data/lib/active_data_frame/version.rb +1 -1
- metadata +17 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4dc73f14e73f597961f8b8092c29090dd962efe8f8d5adc1f5e4550fbd3c4d57
|
4
|
+
data.tar.gz: e6c9e266e39898c063f7e0c02b94856970c64f73e80fbacccfca924b259562e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5aa335d4cd4f887212b9ee3e46a96ab172d682af1f35380bcc51f160b1938faa21f82472d7baad0c49f2c49477b8567a2e8c45b5a50cc8f11be8673a60495982
|
7
|
+
data.tar.gz: dd246279e53dbb9d41e9894c842c1b62f8d4f7c2e724f7db2ca058983d22c25ecf4c1e24fa13d4fa3582e4fa8f92a4466facdbeaee52f238146f891ec2c4227d
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Wouter Coppieters
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/active_data_frame.gemspec
CHANGED
@@ -20,16 +20,16 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency 'pry'
|
23
|
+
spec.add_development_dependency 'bundler', '~> 2.4'
|
24
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
25
|
+
spec.add_development_dependency 'pry-byebug'
|
26
|
+
spec.add_development_dependency 'pry'
|
27
27
|
spec.add_development_dependency 'pg'
|
28
28
|
spec.add_development_dependency 'sqlite3'
|
29
29
|
spec.add_development_dependency 'mysql2'
|
30
30
|
spec.add_development_dependency 'minitest', '~>5.11'
|
31
31
|
spec.add_development_dependency 'minitest-reporters', '~> 1.1', '>= 1.1.0'
|
32
32
|
spec.add_development_dependency 'minitest-around', '0.4.1'
|
33
|
-
spec.add_runtime_dependency 'activerecord', '~>
|
33
|
+
spec.add_runtime_dependency 'activerecord', '~> 7.0'
|
34
34
|
spec.add_runtime_dependency 'rmatrix', '~> 0.1.20', '>=0.1.20'
|
35
35
|
end
|
@@ -76,7 +76,7 @@ module ActiveDataFrame
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def method_missing(name, *args, &block)
|
79
|
-
if name.to_s.
|
79
|
+
if name.to_s.end_with?(?=)
|
80
80
|
is_assignment = true
|
81
81
|
name = name.to_s.gsub(/=$/,'').to_sym
|
82
82
|
end
|
@@ -92,7 +92,7 @@ module ActiveDataFrame
|
|
92
92
|
ranges.map do |range|
|
93
93
|
case range
|
94
94
|
when Range then range
|
95
|
-
when
|
95
|
+
when Integer then range..range
|
96
96
|
else raise "Unexpected index for data frame proxy #{range}, expecting either a Range or an Integer"
|
97
97
|
end
|
98
98
|
end
|
@@ -16,7 +16,7 @@ module ActiveDataFrame
|
|
16
16
|
unless sql.empty?
|
17
17
|
ActiveRecord::Base.transaction do
|
18
18
|
ActiveDataFrame::DataFrameProxy.suppress_logs do
|
19
|
-
case ActiveRecord::Base.
|
19
|
+
case ActiveRecord::Base.connection_db_config.adapter
|
20
20
|
when 'sqlite3'.freeze
|
21
21
|
ActiveRecord::Base.connection.raw_connection.execute_batch sql
|
22
22
|
when 'mysql2'
|
@@ -59,25 +59,56 @@ module ActiveDataFrame
|
|
59
59
|
flush! unless self.batching
|
60
60
|
end
|
61
61
|
|
62
|
-
def bulk_upsert(
|
62
|
+
def bulk_upsert(upserts, scope=nil)
|
63
63
|
Database.batch do
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
64
|
+
case ActiveRecord::Base.connection_db_config.adapter
|
65
|
+
when 'postgresql'.freeze
|
66
|
+
upserts.group_by(&:keys).each do |columns, value_list|
|
67
|
+
columns = columns - [:data_frame_id, :period_index]
|
68
|
+
inserts = ''
|
69
|
+
value_list.each do |row|
|
70
|
+
df_id, period_index, *values = row.values
|
71
|
+
inserts << "(#{values.map{|v| v.inspect.gsub('"',"'") }.join(',')}, #{df_id}, #{period_index}, '#{data_frame_type.name}'),"
|
72
|
+
end
|
73
|
+
sql = %Q{
|
74
|
+
INSERT INTO #{block_type.table_name} (#{columns.join(',')}, data_frame_id, period_index, data_frame_type)
|
75
|
+
VALUES #{inserts[0..-2]}
|
76
|
+
ON CONFLICT(data_frame_id, period_index, data_frame_type) DO UPDATE
|
77
|
+
SET #{columns.map{|c| "#{c} = excluded.#{c} "}.join(',')}
|
78
|
+
}
|
79
|
+
Database.execute sql
|
80
|
+
end
|
81
|
+
when 'mysql2'.freeze
|
82
|
+
upserts.group_by(&:keys).each do |columns, rows|
|
83
|
+
update = rows.map(&:values).map{|df_id, period_index, *values| [period_index, [values, df_id]] }
|
84
|
+
bulk_update(update, columns - [:data_frame_id, :period_index])
|
85
|
+
end
|
86
|
+
else
|
87
|
+
all_update_indices = scope[].pluck(:data_frame_id, :period_index)
|
88
|
+
grouped_update_indices = all_update_indices.group_by(&:first).transform_values{|value| Set.new(value.map!(&:last)) }
|
89
|
+
updates, inserts = upserts.partition{|upsert| grouped_update_indices[upsert[:data_frame_id]]&.include?(upsert[:period_index]) }
|
90
|
+
updates.group_by(&:keys).each do |columns, rows|
|
91
|
+
update = rows.map(&:values).map{|df_id, period_index, *values| [period_index, [values, df_id]] }
|
92
|
+
bulk_update(update, columns - [:data_frame_id, :period_index])
|
93
|
+
end
|
94
|
+
inserts.group_by(&:keys).each do |columns, rows|
|
95
|
+
insert = rows.map(&:values).map{|df_id, period_index, *values| [period_index, [values, df_id]] }
|
96
|
+
bulk_insert(insert, columns - [:data_frame_id, :period_index])
|
97
|
+
end
|
71
98
|
end
|
72
99
|
end
|
73
100
|
end
|
101
|
+
|
74
102
|
##
|
75
|
-
#
|
103
|
+
# Fast update block data for all blocks in a single call.
|
104
|
+
# Uses UPDATE + SET in PostgreSQL
|
105
|
+
# Uses INSERT ON CONFLICT for MySQL (Upsert)
|
106
|
+
# Uses UPDATE with CASE on others
|
76
107
|
##
|
77
108
|
def bulk_update(existing, columns=block_type::COLUMNS)
|
78
109
|
existing.each_slice(ActiveDataFrame.update_max_batch_size) do |existing_slice|
|
79
110
|
# puts "Updating slice of #{existing_slice.length}"
|
80
|
-
case ActiveRecord::Base.
|
111
|
+
case ActiveRecord::Base.connection_db_config.adapter
|
81
112
|
when 'postgresql'.freeze
|
82
113
|
#
|
83
114
|
# PostgreSQL Supports the fast setting of multiple update values that differ
|
@@ -143,6 +174,7 @@ module ActiveDataFrame
|
|
143
174
|
end
|
144
175
|
end
|
145
176
|
|
177
|
+
|
146
178
|
def bulk_delete(id, indices)
|
147
179
|
indices.each_slice(ActiveDataFrame.delete_max_batch_size) do |slice|
|
148
180
|
# puts "Deleting slice of #{slice.length}"
|
@@ -152,20 +184,30 @@ module ActiveDataFrame
|
|
152
184
|
|
153
185
|
##
|
154
186
|
# Insert block data for all blocks in a single call
|
187
|
+
# PostgreSQL uses COPY, others use multi-statement insert
|
155
188
|
##
|
156
189
|
def bulk_insert(new_blocks, columns=block_type::COLUMNS)
|
157
190
|
new_blocks.each_slice(ActiveDataFrame.insert_max_batch_size) do |new_blocks_slice|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
191
|
+
if ActiveRecord::Base.connection_db_config.adapter == 'postgresql'
|
192
|
+
copy_statement = "COPY #{block_type.table_name} (#{columns.join(',')},data_frame_id,period_index,data_frame_type) FROM STDIN CSV"
|
193
|
+
db_conn = ActiveRecord::Base.connection.raw_connection
|
194
|
+
db_conn.copy_data(copy_statement) do
|
195
|
+
new_blocks_slice.each do |period_index, (values, df_id)|
|
196
|
+
db_conn.put_copy_data((values + [df_id, period_index, data_frame_type.name]).join(',') << "\n")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
else
|
200
|
+
inserts = ''
|
201
|
+
new_blocks_slice.each do |period_index, (values, df_id)|
|
202
|
+
inserts << \
|
203
|
+
case ActiveRecord::Base.connection_db_config.adapter
|
204
|
+
when 'mysql2' then "(#{values.map{|v| v.inspect.gsub('"',"'") }.join(',')}, #{df_id}, #{period_index}, '#{data_frame_type.name}'),"
|
205
|
+
else "(#{values.map{|v| v.inspect.gsub('"',"'") }.join(',')}, #{df_id}, #{period_index}, '#{data_frame_type.name}'),"
|
206
|
+
end
|
165
207
|
end
|
208
|
+
sql = "INSERT INTO #{block_type.table_name} (#{columns.join(',')}, data_frame_id, period_index, data_frame_type) VALUES #{inserts[0..-2]}"
|
209
|
+
Database.execute sql
|
166
210
|
end
|
167
|
-
sql = "INSERT INTO #{block_type.table_name} (#{columns.join(',')}, data_frame_id, period_index, data_frame_type) VALUES #{inserts[0..-2]}"
|
168
|
-
Database.execute sql
|
169
211
|
end
|
170
212
|
end
|
171
213
|
end
|
@@ -13,7 +13,7 @@ module ActiveDataFrame
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.set_all(scope, block_type, data_frame_type, from, values, trim: false)
|
16
|
-
if trim || ActiveRecord::Base.
|
16
|
+
if trim || ActiveRecord::Base.connection_db_config.adapter === 'mysql2'
|
17
17
|
case values
|
18
18
|
when Hash then scope.where(id: values.keys)
|
19
19
|
.each{|instance| Row.new(block_type, data_frame_type, instance)
|
@@ -31,9 +31,7 @@ module ActiveDataFrame
|
|
31
31
|
bounds = get_bounds(from, to, block_type)
|
32
32
|
scope = block_type.where(data_frame_type: data_frame_type.name, data_frame_id: rows.select(:id))
|
33
33
|
scope = scope.where(data_frame_id: values.keys) if values.kind_of?(Hash)
|
34
|
-
|
35
|
-
grouped_update_indices = all_update_indices.group_by(&:first).transform_values{|value| Set.new(value.map!(&:last)) }
|
36
|
-
instance_ids = rows.pluck(:id)
|
34
|
+
instance_ids = rows.loaded? ? rows.map(&:id) : rows.pluck(:id)
|
37
35
|
instance_ids &= values.keys if values.kind_of?(Hash)
|
38
36
|
upserts = to_enum(:iterate_bounds, [bounds], block_type).flat_map do |index, left, right, cursor, size|
|
39
37
|
instance_ids.map do |instance_id|
|
@@ -42,13 +40,12 @@ module ActiveDataFrame
|
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
45
|
-
|
46
|
-
Database.for_types(block: block_type, df: data_frame_type).bulk_upsert(update, insert)
|
43
|
+
Database.for_types(block: block_type, df: data_frame_type).bulk_upsert(upserts, ->{scope.where(period_index: bounds.from.index..bounds.to.index)})
|
47
44
|
values
|
48
45
|
end
|
49
46
|
|
50
47
|
def set(from, values, trim: false)
|
51
|
-
if trim || ActiveRecord::Base.
|
48
|
+
if trim || ActiveRecord::Base.connection_db_config.adapter === 'mysql2'
|
52
49
|
patch(from, values)
|
53
50
|
else
|
54
51
|
upsert(from, values)
|
@@ -58,16 +55,10 @@ module ActiveDataFrame
|
|
58
55
|
def upsert(from, values)
|
59
56
|
to = (from + values.length) - 1
|
60
57
|
bounds = get_bounds(from, to)
|
61
|
-
update_indices = Set.new(scope.where(period_index: bounds.from.index..bounds.to.index).order(period_index: :asc).pluck(:period_index))
|
62
|
-
# Detect blocks in bounds:
|
63
|
-
# - If existing and covered, do an update without load
|
64
|
-
# - If existing and uncovered, do a small write (without load)
|
65
|
-
# - If not existing, insert!
|
66
58
|
upserts = to_enum(:iterate_bounds, [bounds]).map do |index, left, right, cursor, size|
|
67
59
|
[[:data_frame_id, self.instance.id], [:period_index, index], *(left.succ..right.succ).map{|v| :"t#{v}" }.zip(values[cursor...cursor + size])].to_h
|
68
60
|
end
|
69
|
-
|
70
|
-
database.bulk_upsert(update, insert)
|
61
|
+
database.bulk_upsert(upserts, ->{ scope.where(period_index: bounds.from.index..bounds.to.index)})
|
71
62
|
values
|
72
63
|
end
|
73
64
|
|
@@ -99,7 +90,6 @@ module ActiveDataFrame
|
|
99
90
|
end
|
100
91
|
end
|
101
92
|
|
102
|
-
|
103
93
|
database.bulk_delete(self.instance.id, deleted_indices) unless deleted_indices.size.zero?
|
104
94
|
database.bulk_update(existing) unless existing.size.zero?
|
105
95
|
database.bulk_insert(new_blocks) unless new_blocks.size.zero?
|
@@ -104,7 +104,7 @@ module ActiveDataFrame
|
|
104
104
|
ActiveRecord::Base.connection.execute(as_sql)
|
105
105
|
end
|
106
106
|
|
107
|
-
case ActiveRecord::Base.
|
107
|
+
case ActiveRecord::Base.connection_db_config.adapter
|
108
108
|
when 'postgresql'.freeze
|
109
109
|
res.each_row {|pi, data_frame_id, *values| existing_blocks[pi][data_frame_id] = values }
|
110
110
|
when 'mysql2'.freeze
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_data_frame
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,68 +16,56 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.4'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry-byebug
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 3.4.0
|
48
45
|
- - ">="
|
49
46
|
- !ruby/object:Gem::Version
|
50
|
-
version:
|
47
|
+
version: '0'
|
51
48
|
type: :development
|
52
49
|
prerelease: false
|
53
50
|
version_requirements: !ruby/object:Gem::Requirement
|
54
51
|
requirements:
|
55
|
-
- - "~>"
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version: 3.4.0
|
58
52
|
- - ">="
|
59
53
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
54
|
+
version: '0'
|
61
55
|
- !ruby/object:Gem::Dependency
|
62
56
|
name: pry
|
63
57
|
requirement: !ruby/object:Gem::Requirement
|
64
58
|
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: 0.10.2
|
68
59
|
- - ">="
|
69
60
|
- !ruby/object:Gem::Version
|
70
|
-
version: 0
|
61
|
+
version: '0'
|
71
62
|
type: :development
|
72
63
|
prerelease: false
|
73
64
|
version_requirements: !ruby/object:Gem::Requirement
|
74
65
|
requirements:
|
75
|
-
- - "~>"
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: 0.10.2
|
78
66
|
- - ">="
|
79
67
|
- !ruby/object:Gem::Version
|
80
|
-
version: 0
|
68
|
+
version: '0'
|
81
69
|
- !ruby/object:Gem::Dependency
|
82
70
|
name: pg
|
83
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -174,14 +162,14 @@ dependencies:
|
|
174
162
|
requirements:
|
175
163
|
- - "~>"
|
176
164
|
- !ruby/object:Gem::Version
|
177
|
-
version: '
|
165
|
+
version: '7.0'
|
178
166
|
type: :runtime
|
179
167
|
prerelease: false
|
180
168
|
version_requirements: !ruby/object:Gem::Requirement
|
181
169
|
requirements:
|
182
170
|
- - "~>"
|
183
171
|
- !ruby/object:Gem::Version
|
184
|
-
version: '
|
172
|
+
version: '7.0'
|
185
173
|
- !ruby/object:Gem::Dependency
|
186
174
|
name: rmatrix
|
187
175
|
requirement: !ruby/object:Gem::Requirement
|
@@ -212,6 +200,7 @@ files:
|
|
212
200
|
- ".gitignore"
|
213
201
|
- CODE_OF_CONDUCT.md
|
214
202
|
- Gemfile
|
203
|
+
- LICENSE
|
215
204
|
- README.md
|
216
205
|
- Rakefile
|
217
206
|
- active_data_frame-0.1.1.gem
|
@@ -237,7 +226,7 @@ files:
|
|
237
226
|
homepage: https://github.com/wouterken/active_data_frame
|
238
227
|
licenses: []
|
239
228
|
metadata: {}
|
240
|
-
post_install_message:
|
229
|
+
post_install_message:
|
241
230
|
rdoc_options: []
|
242
231
|
require_paths:
|
243
232
|
- lib
|
@@ -252,9 +241,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
252
241
|
- !ruby/object:Gem::Version
|
253
242
|
version: '0'
|
254
243
|
requirements: []
|
255
|
-
|
256
|
-
|
257
|
-
signing_key:
|
244
|
+
rubygems_version: 3.4.6
|
245
|
+
signing_key:
|
258
246
|
specification_version: 4
|
259
247
|
summary: An active data frame helper
|
260
248
|
test_files: []
|