activerecord_bulkoperation 0.0.2

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +13 -0
  5. data/README.md +1 -0
  6. data/Rakefile +25 -0
  7. data/activerecord_bulkoperation.gemspec +23 -0
  8. data/gemfiles/4.2.gemfile +14 -0
  9. data/lib/activerecord_bulkoperation.rb +20 -0
  10. data/lib/activerecord_bulkoperation/active_record/adapters/abstract_adapter.rb +49 -0
  11. data/lib/activerecord_bulkoperation/active_record/adapters/oracle_enhanced_adapter.rb +10 -0
  12. data/lib/activerecord_bulkoperation/active_record/associations/associations.rb +169 -0
  13. data/lib/activerecord_bulkoperation/adapters/abstract_adapter.rb +43 -0
  14. data/lib/activerecord_bulkoperation/adapters/oracle_enhanced_adapter.rb +44 -0
  15. data/lib/activerecord_bulkoperation/base.rb +53 -0
  16. data/lib/activerecord_bulkoperation/bulkoperation.rb +260 -0
  17. data/lib/activerecord_bulkoperation/connection_adapters/oracle_enhanced/jdbc_connection.rb +111 -0
  18. data/lib/activerecord_bulkoperation/connection_adapters/oracle_enhanced/oci_connection.rb +106 -0
  19. data/lib/activerecord_bulkoperation/group_operations.rb +296 -0
  20. data/lib/activerecord_bulkoperation/group_operations_select.rb +60 -0
  21. data/lib/activerecord_bulkoperation/util/connection_object.rb +22 -0
  22. data/lib/activerecord_bulkoperation/util/entity_hash.rb +78 -0
  23. data/lib/activerecord_bulkoperation/util/flush_dirty_objects.rb +126 -0
  24. data/lib/activerecord_bulkoperation/util/sequence_cache.rb +52 -0
  25. data/lib/activerecord_bulkoperation/util/transaction_object.rb +36 -0
  26. data/lib/activerecord_bulkoperation/version.rb +5 -0
  27. data/test/active_record_connection_test.rb +41 -0
  28. data/test/adapters/oracle_enhanced.rb +1 -0
  29. data/test/bulkoperation_test.rb +176 -0
  30. data/test/database.yml +8 -0
  31. data/test/entity_hash_test.rb +11 -0
  32. data/test/find_group_by_test.rb +132 -0
  33. data/test/flush_dirty_objects_test.rb +11 -0
  34. data/test/models/assembly.rb +3 -0
  35. data/test/models/course.rb +3 -0
  36. data/test/models/group.rb +3 -0
  37. data/test/models/item.rb +2 -0
  38. data/test/models/part.rb +3 -0
  39. data/test/models/product.rb +7 -0
  40. data/test/models/student.rb +3 -0
  41. data/test/models/test_table.rb +2 -0
  42. data/test/postgresql/bulk_test.rb +13 -0
  43. data/test/schema/generic_schema.rb +59 -0
  44. data/test/sequence_cache_test.rb +31 -0
  45. data/test/support/postgresql/bulk_examples.rb +8 -0
  46. data/test/test_helper.rb +45 -0
  47. data/test/transaction_object_test.rb +11 -0
  48. metadata +141 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f75f024004e8d891bd6c3723026f673dca6cf2085726f98971477ab65016516a
4
+ data.tar.gz: 629f7ed52ea5a51855766b0151bcfc3a77aed7239d6981a9a10b63f4c7f0ada6
5
+ SHA512:
6
+ metadata.gz: d652595ee6fc44e055e52e3a53761388d07de4c05832344b993e1674e55574c795dc12c2805f5ea045191cd5d899e3072960d8e17aeae67d15dd4cb9c30eab52
7
+ data.tar.gz: b47a322b80820779c0b62c527ab572e0ede06c7a6c36a5ca195cc1b9ab22226a57b9d298639c738ce2e3ea0e21066e02def2d57b15d1c766fabacd65738e9055
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ log
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ #gemspec
4
+
5
+ version = ENV['AR_VERSION'] || "4.2"
6
+
7
+ if version > "4.0"
8
+ gem "minitest"
9
+ end
10
+
11
+ gem 'mocha'
12
+
13
+ eval_gemfile File.expand_path("../gemfiles/#{version}.gemfile", __FILE__)
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # activerecord_bulkoperation
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ namespace :display do
8
+ task :notice do
9
+ puts
10
+ puts "To run tests you must supply the adapter, see rake -T for more information."
11
+ puts
12
+ end
13
+ end
14
+ task :default => ["display:notice"]
15
+
16
+ ADAPTERS = %w(oracle_enhanced)
17
+ ADAPTERS.each do |adapter|
18
+ namespace :test do
19
+ desc "Runs #{adapter} database tests."
20
+ Rake::TestTask.new(adapter) do |t|
21
+ t.test_files = FileList["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/active_record/*_test.rb", "test/#{adapter}/**/*_test.rb"]
22
+ end
23
+ task adapter
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/activerecord_bulkoperation/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["OSP"]
6
+ gem.email = [""]
7
+ gem.summary = ""
8
+ gem.description = ""
9
+ gem.homepage = ""
10
+ gem.license = "Ruby"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "activerecord_bulkoperation"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = ActiveRecord::Bulkoperation::VERSION
18
+
19
+ gem.required_ruby_version = ">= 1.9.2"
20
+
21
+ gem.add_runtime_dependency "activerecord", "~> 4.2"
22
+ gem.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,14 @@
1
+ gem 'activerecord-oracle_enhanced-adapter', '~> 1.6.0'
2
+
3
+ # a warning will be emitted when the same gems are put in more than one platform
4
+ # but it will still lead to errors if they are missing in either
5
+ platforms :ruby do
6
+ #gem 'mysql', '~> 2.9'
7
+ gem 'ruby-oci8', '~> 2.1'
8
+ end
9
+
10
+ platforms :jruby do
11
+ gem 'activerecord-jdbc-adapter' #, '~> 1.3.0'
12
+ end
13
+
14
+ gem 'activerecord', '~> 4.2'
@@ -0,0 +1,20 @@
1
+ #
2
+ #
3
+ #
4
+ class ActiveRecord::Base
5
+ class << self
6
+ def establish_connection_with_activerecord_bulkoperation(*args)
7
+ establish_connection_without_activerecord_bulkoperation(*args)
8
+ ActiveSupport.run_load_hooks(:active_record_connection_established, connection_pool)
9
+ end
10
+ alias_method_chain :establish_connection, :activerecord_bulkoperation
11
+ end
12
+ end
13
+
14
+ ActiveSupport.on_load(:active_record_connection_established) do |connection_pool|
15
+ if !ActiveRecord.const_defined?(:Bulkoperation, false) || !ActiveRecord::Bulkoperation.respond_to?(:load_from_connection_pool)
16
+ require 'activerecord_bulkoperation/base'
17
+ end
18
+
19
+ ActiveRecord::Bulkoperation.load_from_connection_pool connection_pool
20
+ end
@@ -0,0 +1,49 @@
1
+ require "activerecord_bulkoperation/adapters/abstract_adapter"
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+
6
+ class ConnectionPool
7
+
8
+ alias_method :super_initialize, :initialize
9
+ def initialize(spec)
10
+ super_initialize(spec)
11
+ @timeout = 5
12
+ end
13
+
14
+ def log
15
+ ActiveRecord::Base.logger
16
+ end
17
+
18
+ def set_connection(connection)
19
+ id = ActiveRecord::Base.connection_pool.send :current_connection_id
20
+ hash = ActiveRecord::Base.connection_pool.instance_variable_get('@reserved_connections')
21
+ hash[id] = connection
22
+ end
23
+
24
+ def get_connection
25
+ id = ActiveRecord::Base.connection_pool.send :current_connection_id
26
+ hash = ActiveRecord::Base.connection_pool.instance_variable_get('@reserved_connections')
27
+ hash[id]
28
+ end
29
+
30
+ # after checkout clear the scheduled operation if necessary
31
+ alias_method :super_checkout, :checkout
32
+ def checkout
33
+ @timeout = 5
34
+ connection = super_checkout
35
+ connection.clear_scheduled_operations if connection.respond_to?('clear_scheduled_operations')
36
+ connection
37
+ end
38
+
39
+ # before checkout clear the scheduled operation if necessary
40
+ alias_method :super_checkin, :checkin
41
+ def checkin(connection)
42
+ connection.clear_scheduled_operations if connection.respond_to?('clear_scheduled_operations')
43
+ connection.connection_listeners.each { |l| l.before_close if l.respond_to?('before_close') } if connection.respond_to?('connection_listeners')
44
+ super_checkin(connection)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ require 'activerecord_bulkoperation/adapters/oracle_enhanced_adapter'
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ class OracleEnhancedAdapter # :nodoc:
6
+ include ActiveRecord::Bulkoperation::OracleEnhancedAdapter
7
+ include ActiveRecord::Bulkoperation::AbstractAdapter::InstanceMethods
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,169 @@
1
+ require 'active_record/associations'
2
+
3
+ module ActiveRecord
4
+
5
+ module Persistence
6
+ module ClassMethods
7
+
8
+ alias_method :instantiate_without_save_original, :instantiate
9
+
10
+ def instantiate(attributes, column_types = {})
11
+ record = instantiate_without_save_original(attributes, column_types)
12
+ record.save_original
13
+ record
14
+ end
15
+ end
16
+ end
17
+
18
+ module Associations
19
+
20
+ module ManyToManyTables
21
+ end
22
+
23
+ class CollectionProxy
24
+
25
+ def schedule_merge(record)
26
+ options = proxy_association.reflection.options
27
+ macro = proxy_association.reflection.macro
28
+ if(proxy_association.is_a?(ActiveRecord::Associations::HasManyThroughAssociation))
29
+ handle_has_many_through_schedule_merge(record)
30
+ return
31
+ end
32
+ if(macro == :has_many)
33
+ handle_has_many_schedule_merge(record)
34
+ return
35
+ end
36
+ end
37
+
38
+ def handle_has_many_schedule_merge(record)
39
+ pk = proxy_association.reflection.options[:primary_key] || proxy_association.owner.class.primary_key
40
+ fk = proxy_association.reflection.options[:foreign_key] || "#{proxy_association.owner.class.to_s.underscore.downcase}_id"
41
+ if(pk.is_a?(Array))
42
+ #puts "is a array"
43
+ pk.each do |pk_item|
44
+ pk_item_val = proxy_association.owner[pk_item.to_s]
45
+ record.send("#{pk_item.to_s}=",pk_item_val)
46
+ end
47
+ else
48
+ pk_val = proxy_association.owner[pk.to_s]
49
+ record.send("#{fk}=",pk_val)
50
+ end
51
+
52
+ record.schedule_merge
53
+ self << record
54
+ end
55
+
56
+ def parent_reflection
57
+ proxy_association.reflection.parent_reflection[1]
58
+ end
59
+
60
+ def get_m_t_m_table_name
61
+ parent_reflection.options[:join_table] || [proxy_association.owner.class.table_name,proxy_association.klass.table_name].sort.join('_')
62
+ end
63
+
64
+ def parent_pk
65
+ parent_reflection.options[:primary_key] || proxy_association.owner.class.primary_key
66
+ end
67
+
68
+ def parent_association_pk
69
+ parent_reflection.options[:association_primary_key] || "#{proxy_association.owner.class.to_s.underscore.downcase}_id"
70
+ end
71
+
72
+ def child_pk
73
+ parent_reflection.options[:foreign_key] || proxy_association.klass.primary_key
74
+ end
75
+
76
+ def child_association_pk
77
+ parent_reflection.options[:association_foreign_key] || "#{proxy_association.klass.to_s.underscore.downcase}_id"
78
+ end
79
+
80
+ #TODO remove
81
+ alias_method :count_without_merges, :count
82
+ def count
83
+ count_without_merges + ( @internal_new_count.to_i)
84
+ end
85
+
86
+ def handle_has_many_through_schedule_merge(record)
87
+ # TODO AK doesn't work well
88
+ self << record
89
+
90
+ =begin
91
+ join_model = Class.new(ActiveRecord::Base) do
92
+ class << self;
93
+ attr_accessor :table_info
94
+ end
95
+ def self.table_name
96
+ table_info.table_name
97
+ end
98
+ end
99
+ join_model.table_info = OpenStruct.new(:table_name => get_m_t_m_table_name)
100
+ unless(ManyToManyTables.const_defined?(get_m_t_m_table_name.camelize))
101
+ ManyToManyTables.const_set get_m_t_m_table_name.camelize,join_model
102
+ end
103
+ #puts "RP DEBUG child_pk: #{child_pk} parent_pk: #{parent_pk}"
104
+ #puts "RP DEBUG child_association_pk: #{child_association_pk} parent_association_pk: #{parent_association_pk}"
105
+ #puts "RP DEBUG table_name: #{get_m_t_m_table_name}"
106
+ c = ManyToManyTables.const_get get_m_t_m_table_name.camelize
107
+ record.schedule_merge
108
+ obj = c.new
109
+ puts "AK: #{obj.class.name}"
110
+ puts "AK: #{obj.class.name}.#{child_association_pk} = #{proxy_association.owner.class.name}.#{parent_pk} #{proxy_association.owner.send(parent_pk)}"
111
+ puts "AK: #{obj.class.name}.#{child_association_pk} = #{record.class.name}.#{parent_association_pk} #{record.send(parent_association_pk)}"
112
+ obj.send("#{parent_association_pk}=", proxy_association.owner.send(parent_pk))
113
+ obj.send("#{child_association_pk}=" , record.send(parent_association_pk))
114
+ obj.schedule_merge
115
+ pp obj
116
+ @internal_new_count ||= 0
117
+ @internal_new_count += 1
118
+ =end
119
+ end
120
+
121
+ end
122
+
123
+ class JoinDependency # :nodoc:
124
+
125
+ def instantiate_each(row, &block)
126
+ if row
127
+ primary_id = join_base.record_id(row)
128
+ unless @base_records_hash[primary_id]
129
+ if @base_records_in_order.size > 0
130
+ yield @base_records_in_order.first
131
+ # Die Theorie ist hier ein primary_key der Haupttabelle
132
+ # ist verarbeitet und nun kann der Satz entsorgt werden.
133
+ @base_records_in_order.pop
134
+ # instatiate_each nicht das gesamte Ergebnis durchsucht,
135
+ # wird @base_record_hash nur fuer den Gruppenwechsel
136
+ # verwendet.
137
+ # Bei einem neuen primary_key wird der Hash geleert.
138
+ @base_records_hash = {}
139
+ # record cache leeren
140
+ # join_base.cached_record = {}
141
+ @joins.each { |j| j.cached_record = {} }
142
+ end
143
+ #RP TODO check if instantiate call is correct with an empty hash or where to get the 2nd parameter
144
+ @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row,{}))
145
+ end
146
+ construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
147
+ else
148
+ yield @base_records_in_order.first
149
+ end
150
+ end
151
+
152
+ class JoinBase
153
+ attr_accessor :cached_record
154
+ def extract_record(row,column_names_with_alias)
155
+ # if the :select option is set, only the selected field should be extracted
156
+ # column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an] if row.has_key?(an); record}
157
+ record = {}
158
+ column_names_with_alias.each { |(cn, an)| record[cn] = row[an] if row.key?(an) }
159
+ record
160
+ end
161
+ alias_method :instantiate_without_save_original, :instantiate
162
+ def instantiate(row, aliases)
163
+ instantiate_without_save_original(row, aliases)
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+
@@ -0,0 +1,43 @@
1
+ module ActiveRecord
2
+ module Bulkoperation
3
+ module AbstractAdapter
4
+ module InstanceMethods
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ alias_method :commit_db_transaction_without_callback, :commit_db_transaction
9
+ alias_method :rollback_db_transaction_without_callback, :rollback_db_transaction
10
+ alias_method :rollback_to_savepoint_without_callback, :rollback_to_savepoint
11
+ alias_method :create_savepoint_without_callback, :create_savepoint
12
+
13
+ def commit_db_transaction
14
+ connection_listeners.each { |l| l.before_commit if l.respond_to?('before_commit') }
15
+ commit_db_transaction_without_callback
16
+ connection_listeners.each { |l| l.after_commit if l.respond_to?('after_commit') }
17
+ end
18
+
19
+ def rollback_db_transaction
20
+ rollback_db_transaction_without_callback
21
+ connection_listeners.each { |l| l.after_rollback if l.respond_to?('after_rollback') }
22
+ end
23
+
24
+ def rollback_to_savepoint(name = current_savepoint_name)
25
+ rollback_to_savepoint_without_callback(name)
26
+ connection_listeners.each { |l| l.after_rollback_to_savepoint if l.respond_to?('after_rollback_to_savepoint') }
27
+ end
28
+
29
+ def create_savepoint(name = current_savepoint_name)
30
+ connection_listeners.each { |l| l.before_create_savepoint if l.respond_to?('before_create_savepoint') }
31
+ create_savepoint_without_callback(name)
32
+ end
33
+ end
34
+ end
35
+
36
+ def connection_listeners
37
+ @connection_listeners ||= []
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveRecord
2
+ module Bulkoperation
3
+ module OracleEnhancedAdapter
4
+
5
+ def find_foreign_master_tables_sql_array(table_name)
6
+ sql = "select
7
+ m.table_name
8
+ from
9
+ user_constraints m join user_constraints d on (m.constraint_name = d.r_constraint_name)
10
+ where
11
+ d.table_name = ?"
12
+ [sql, table_name.upcase]
13
+ end
14
+
15
+ def find_foreign_detail_tables_sql_array(table_name)
16
+ sql = "select
17
+ d.table_name
18
+ from
19
+ user_constraints m join user_constraints d on (m.constraint_name = d.r_constraint_name)
20
+ where
21
+ m.table_name = ?"
22
+ [sql, table_name.upcase]
23
+ end
24
+
25
+ def find_detail_references_sql_array(table_name)
26
+ sql = "select
27
+ m.table_name master_table,
28
+ ( select listagg( column_name, ',' ) within group (order by position) from user_cons_columns where constraint_name = m.constraint_name ) master_columns,
29
+ d.table_name detail_table,
30
+ ( select listagg( column_name, ',' ) within group (order by position) from user_cons_columns where constraint_name = d.constraint_name ) detail_columns
31
+ from
32
+ user_constraints m join user_constraints d on( m.constraint_name = d.r_constraint_name )
33
+ where m.table_name = ?"
34
+ [sql, table_name.upcase]
35
+ end
36
+
37
+ def find_by_sequence_name_sql_array(name)
38
+ sql = 'select * from user_sequences where sequence_name = ?'
39
+ [sql, name.upcase]
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ module ActiveRecord
2
+ module Bulkoperation
3
+ AdapterPath = 'activerecord_bulkoperation/active_record/adapters'
4
+
5
+ def self.base_adapter(adapter)
6
+ case adapter
7
+ when 'mysqlspatial' then 'mysql'
8
+ when 'mysql2spatial' then 'mysql2'
9
+ when 'spatialite' then 'sqlite3'
10
+ when 'oracle_enhanced' then 'oracle_enhanced'
11
+ else adapter
12
+ end
13
+ end
14
+
15
+ # Loads the import functionality for a specific database adapter
16
+ def self.require_adapter(adapter)
17
+ require File.join(AdapterPath,'/abstract_adapter')
18
+ begin
19
+ require File.join(AdapterPath,"/#{base_adapter(adapter)}_adapter")
20
+ rescue LoadError
21
+ # fallback
22
+ end
23
+ end
24
+
25
+ # Loads the import functionality for the passed in ActiveRecord connection
26
+ def self.load_from_connection_pool(connection_pool)
27
+ require_adapter connection_pool.spec.config[:adapter]
28
+ end
29
+ end
30
+ end
31
+
32
+ # if MRI or YARV
33
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
34
+ ORACLE_ENHANCED_CONNECTION = :oci if ORACLE_ENHANCED_CONNECTION != :oci
35
+ require 'activerecord_bulkoperation/connection_adapters/oracle_enhanced/oci_connection'
36
+ # if JRuby
37
+ elsif RUBY_ENGINE == 'jruby'
38
+ ORACLE_ENHANCED_CONNECTION = :jdbc if ORACLE_ENHANCED_CONNECTION != :jdbc
39
+ require 'activerecord_bulkoperation/connection_adapters/oracle_enhanced/jdbc_connection'
40
+ else
41
+ raise "Unsupported Ruby engine #{RUBY_ENGINE}"
42
+ end
43
+
44
+ require 'activerecord_bulkoperation/bulkoperation'
45
+ require 'activerecord_bulkoperation/active_record/associations/associations'
46
+ require 'activerecord_bulkoperation/group_operations'
47
+ require 'activerecord_bulkoperation/group_operations_select'
48
+ require 'activerecord_bulkoperation/util/sequence_cache'
49
+ require 'activerecord_bulkoperation/util/entity_hash'
50
+ require 'activerecord_bulkoperation/util/transaction_object'
51
+ require 'activerecord_bulkoperation/util/connection_object'
52
+ require 'activerecord_bulkoperation/util/flush_dirty_objects'
53
+