activerecord_bulkoperation 0.0.2

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