activerecord-oracle_enhanced-adapter 1.5.6 → 1.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis/oracle/download.sh +14 -0
  3. data/.travis/oracle/install.sh +31 -0
  4. data/.travis/setup_accounts.sh +9 -0
  5. data/.travis.yml +39 -0
  6. data/Gemfile +8 -8
  7. data/History.md +189 -0
  8. data/README.md +388 -178
  9. data/RUNNING_TESTS.md +11 -6
  10. data/VERSION +1 -1
  11. data/activerecord-oracle_enhanced-adapter.gemspec +29 -26
  12. data/lib/active_record/connection_adapters/{oracle_enhanced_column.rb → oracle_enhanced/column.rb} +14 -63
  13. data/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb +66 -0
  14. data/lib/active_record/connection_adapters/{oracle_enhanced_connection.rb → oracle_enhanced/connection.rb} +2 -2
  15. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +347 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +260 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/dirty.rb +40 -0
  18. data/lib/active_record/connection_adapters/{oracle_enhanced_jdbc_connection.rb → oracle_enhanced/jdbc_connection.rb} +13 -4
  19. data/lib/active_record/connection_adapters/{oracle_enhanced_oci_connection.rb → oracle_enhanced/oci_connection.rb} +11 -5
  20. data/lib/active_record/connection_adapters/{oracle_enhanced_procedures.rb → oracle_enhanced/procedures.rb} +1 -1
  21. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_creation.rb → oracle_enhanced/schema_creation.rb} +34 -35
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +95 -0
  23. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_dumper.rb → oracle_enhanced/schema_dumper.rb} +14 -37
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +562 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +65 -0
  26. data/lib/active_record/connection_adapters/{oracle_enhanced_structure_dump.rb → oracle_enhanced/structure_dump.rb} +63 -14
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +1 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +171 -73
  29. data/lib/active_record/oracle_enhanced/type/integer.rb +13 -0
  30. data/lib/active_record/oracle_enhanced/type/raw.rb +13 -0
  31. data/lib/active_record/oracle_enhanced/type/timestamp.rb +11 -0
  32. data/lib/activerecord-oracle_enhanced-adapter.rb +1 -1
  33. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +127 -49
  34. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +46 -5
  35. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +11 -3
  36. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +3 -3
  37. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +151 -78
  38. data/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +4 -4
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +10 -16
  40. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +1 -1
  41. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +5 -5
  42. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +65 -181
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +114 -11
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +17 -1
  45. data/spec/spec_config.yaml.template +11 -0
  46. data/spec/spec_helper.rb +31 -12
  47. data/spec/support/alter_system_user_password.sql +2 -0
  48. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  49. metadata +37 -27
  50. data/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +0 -77
  51. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -350
  52. data/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +0 -262
  53. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -45
  54. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -197
  55. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -450
  56. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  57. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
  58. /data/lib/active_record/connection_adapters/{oracle_enhanced_cpk.rb → oracle_enhanced/cpk.rb} +0 -0
  59. /data/lib/active_record/connection_adapters/{oracle_enhanced_database_tasks.rb → oracle_enhanced/database_tasks.rb} +0 -0
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,17 @@
1
- require 'rubygems'
1
+ require "rubygems"
2
2
  require "bundler"
3
+ require "yaml"
3
4
  Bundler.setup(:default, :development)
4
5
 
5
6
  $:.unshift(File.expand_path('../../lib', __FILE__))
7
+ config_path = File.expand_path('../spec_config.yaml', __FILE__)
8
+ if File.exist?(config_path)
9
+ puts "==> Loading config from #{config_path}"
10
+ config = YAML.load_file(config_path)
11
+ else
12
+ puts "==> Loading config from ENV or use default"
13
+ config = {"rails" => {}, "database" => {}}
14
+ end
6
15
 
7
16
  require 'rspec'
8
17
 
@@ -13,11 +22,8 @@ elsif RUBY_ENGINE == 'jruby'
13
22
  puts "==> Running specs with JRuby version #{JRUBY_VERSION}"
14
23
  end
15
24
 
16
- ENV['RAILS_GEM_VERSION'] ||= '4.0-master'
17
25
  NO_COMPOSITE_PRIMARY_KEYS = true
18
26
 
19
- puts "==> Running specs with Rails version #{ENV['RAILS_GEM_VERSION']}"
20
-
21
27
  require 'active_record'
22
28
 
23
29
  require 'action_dispatch'
@@ -32,6 +38,8 @@ require 'logger'
32
38
  require 'active_record/connection_adapters/oracle_enhanced_adapter'
33
39
  require 'ruby-plsql'
34
40
 
41
+ puts "==> Effective ActiveRecord version #{ActiveRecord::VERSION::STRING}"
42
+
35
43
  module LoggerSpecHelper
36
44
  def set_logger
37
45
  @logger = MockLogger.new
@@ -109,12 +117,13 @@ module SchemaSpecHelper
109
117
  end
110
118
  end
111
119
 
112
- DATABASE_NAME = ENV['DATABASE_NAME'] || 'orcl'
113
- DATABASE_HOST = ENV['DATABASE_HOST']
114
- DATABASE_PORT = ENV['DATABASE_PORT']
115
- DATABASE_USER = ENV['DATABASE_USER'] || 'oracle_enhanced'
116
- DATABASE_PASSWORD = ENV['DATABASE_PASSWORD'] || 'oracle_enhanced'
117
- DATABASE_SYS_PASSWORD = ENV['DATABASE_SYS_PASSWORD'] || 'admin'
120
+ DATABASE_NAME = config["database"]["name"] || ENV['DATABASE_NAME'] || 'orcl'
121
+ DATABASE_HOST = config["database"]["host"] || ENV['DATABASE_HOST'] || "127.0.0.1"
122
+ DATABASE_PORT = config["database"]["port"] || ENV['DATABASE_PORT'] || 1521
123
+ DATABASE_USER = config["database"]["user"] || ENV['DATABASE_USER'] || 'oracle_enhanced'
124
+ DATABASE_PASSWORD = config["database"]["password"] || ENV['DATABASE_PASSWORD'] || 'oracle_enhanced'
125
+ DATABASE_SCHEMA = config["database"]["schema"] || ENV['DATABASE_SCHEMA'] || 'oracle_enhanced_schema'
126
+ DATABASE_SYS_PASSWORD = config["database"]["sys_password"] || ENV['DATABASE_SYS_PASSWORD'] || 'admin'
118
127
 
119
128
  CONNECTION_PARAMS = {
120
129
  :adapter => "oracle_enhanced",
@@ -125,6 +134,16 @@ CONNECTION_PARAMS = {
125
134
  :password => DATABASE_PASSWORD
126
135
  }
127
136
 
137
+ CONNECTION_WITH_SCHEMA_PARAMS = {
138
+ :adapter => "oracle_enhanced",
139
+ :database => DATABASE_NAME,
140
+ :host => DATABASE_HOST,
141
+ :port => DATABASE_PORT,
142
+ :username => DATABASE_USER,
143
+ :password => DATABASE_PASSWORD,
144
+ :schema => DATABASE_SCHEMA
145
+ }
146
+
128
147
  SYS_CONNECTION_PARAMS = {
129
148
  :adapter => "oracle_enhanced",
130
149
  :database => DATABASE_NAME,
@@ -144,11 +163,11 @@ SYSTEM_CONNECTION_PARAMS = {
144
163
  :password => DATABASE_SYS_PASSWORD
145
164
  }
146
165
 
147
- DATABASE_NON_DEFAULT_TABLESPACE = ENV['DATABASE_NON_DEFAULT_TABLESPACE'] || "SYSTEM"
166
+ DATABASE_NON_DEFAULT_TABLESPACE = config["database"]["non_default_tablespace"] || ENV['DATABASE_NON_DEFAULT_TABLESPACE'] || "SYSTEM"
148
167
 
149
168
  # set default time zone in TZ environment variable
150
169
  # which will be used to set session time zone
151
- ENV['TZ'] ||= 'Europe/Riga'
170
+ ENV['TZ'] ||= config["timezone"] || 'Europe/Riga'
152
171
 
153
172
  # ActiveRecord::Base.logger = Logger.new(STDOUT)
154
173
 
@@ -0,0 +1,2 @@
1
+ alter user sys identified by admin;
2
+ alter user system identified by admin;
@@ -0,0 +1,31 @@
1
+ alter database default tablespace USERS;
2
+
3
+ CREATE USER oracle_enhanced IDENTIFIED BY oracle_enhanced;
4
+
5
+ GRANT unlimited tablespace, create session, create table, create sequence,
6
+ create procedure, create trigger, create view, create materialized view,
7
+ create database link, create synonym, create type, ctxapp TO oracle_enhanced;
8
+
9
+ CREATE USER oracle_enhanced_schema IDENTIFIED BY oracle_enhanced_schema;
10
+
11
+ GRANT unlimited tablespace, create session, create table, create sequence,
12
+ create procedure, create trigger, create view, create materialized view,
13
+ create database link, create synonym, create type, ctxapp TO oracle_enhanced_schema;
14
+
15
+ CREATE USER arunit IDENTIFIED BY arunit;
16
+
17
+ GRANT unlimited tablespace, create session, create table, create sequence,
18
+ create procedure, create trigger, create view, create materialized view,
19
+ create database link, create synonym, create type, ctxapp TO arunit;
20
+
21
+ CREATE USER arunit2 IDENTIFIED BY arunit2;
22
+
23
+ GRANT unlimited tablespace, create session, create table, create sequence,
24
+ create procedure, create trigger, create view, create materialized view,
25
+ create database link, create synonym, create type, ctxapp TO arunit2;
26
+
27
+ CREATE USER ruby IDENTIFIED BY oci8;
28
+ GRANT connect, resource, create view,create synonym TO ruby;
29
+ GRANT EXECUTE ON dbms_lock TO ruby;
30
+ GRANT CREATE VIEW TO ruby;
31
+ GRANT unlimited tablespace to ruby;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-oracle_enhanced-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.6
4
+ version: 1.6.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raimonds Simanovskis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-31 00:00:00.000000000 Z
11
+ date: 2017-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jeweler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.8'
19
+ version: '2.0'
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: '1.8'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '2.4'
33
+ version: '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: '2.4'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rdoc
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -202,6 +202,10 @@ extra_rdoc_files:
202
202
  - README.md
203
203
  files:
204
204
  - ".rspec"
205
+ - ".travis.yml"
206
+ - ".travis/oracle/download.sh"
207
+ - ".travis/oracle/install.sh"
208
+ - ".travis/setup_accounts.sh"
205
209
  - Gemfile
206
210
  - History.md
207
211
  - License.txt
@@ -211,25 +215,28 @@ files:
211
215
  - VERSION
212
216
  - activerecord-oracle_enhanced-adapter.gemspec
213
217
  - lib/active_record/connection_adapters/emulation/oracle_adapter.rb
218
+ - lib/active_record/connection_adapters/oracle_enhanced/column.rb
219
+ - lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb
220
+ - lib/active_record/connection_adapters/oracle_enhanced/connection.rb
221
+ - lib/active_record/connection_adapters/oracle_enhanced/context_index.rb
222
+ - lib/active_record/connection_adapters/oracle_enhanced/cpk.rb
223
+ - lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb
224
+ - lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb
225
+ - lib/active_record/connection_adapters/oracle_enhanced/dirty.rb
226
+ - lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb
227
+ - lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb
228
+ - lib/active_record/connection_adapters/oracle_enhanced/procedures.rb
229
+ - lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb
230
+ - lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb
231
+ - lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb
232
+ - lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb
233
+ - lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb
234
+ - lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb
235
+ - lib/active_record/connection_adapters/oracle_enhanced/version.rb
214
236
  - lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
215
- - lib/active_record/connection_adapters/oracle_enhanced_column.rb
216
- - lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb
217
- - lib/active_record/connection_adapters/oracle_enhanced_connection.rb
218
- - lib/active_record/connection_adapters/oracle_enhanced_context_index.rb
219
- - lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
220
- - lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb
221
- - lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb
222
- - lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
223
- - lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
224
- - lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
225
- - lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
226
- - lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb
227
- - lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb
228
- - lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb
229
- - lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb
230
- - lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb
231
- - lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb
232
- - lib/active_record/connection_adapters/oracle_enhanced_version.rb
237
+ - lib/active_record/oracle_enhanced/type/integer.rb
238
+ - lib/active_record/oracle_enhanced/type/raw.rb
239
+ - lib/active_record/oracle_enhanced/type/timestamp.rb
233
240
  - lib/activerecord-oracle_enhanced-adapter.rb
234
241
  - spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
235
242
  - spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
@@ -244,7 +251,10 @@ files:
244
251
  - spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb
245
252
  - spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb
246
253
  - spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb
254
+ - spec/spec_config.yaml.template
247
255
  - spec/spec_helper.rb
256
+ - spec/support/alter_system_user_password.sql
257
+ - spec/support/create_oracle_enhanced_users.sql
248
258
  homepage: http://github.com/rsim/oracle-enhanced
249
259
  licenses:
250
260
  - MIT
@@ -265,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
275
  version: '0'
266
276
  requirements: []
267
277
  rubyforge_project:
268
- rubygems_version: 2.4.6
278
+ rubygems_version: 2.6.11
269
279
  signing_key:
270
280
  specification_version: 4
271
281
  summary: Oracle enhanced adapter for ActiveRecord
@@ -1,77 +0,0 @@
1
- module ActiveRecord #:nodoc:
2
- module ConnectionAdapters #:nodoc:
3
- module OracleEnhancedColumnDumper #:nodoc:
4
-
5
- def self.included(base) #:nodoc:
6
- base.class_eval do
7
- private
8
- alias_method_chain :column_spec, :oracle_enhanced
9
- alias_method_chain :prepare_column_options, :oracle_enhanced
10
- alias_method_chain :migration_keys, :oracle_enhanced
11
-
12
- def oracle_enhanced_adapter?
13
- # return original method if not using 'OracleEnhanced'
14
- if (rails_env = defined?(Rails.env) ? Rails.env : (defined?(RAILS_ENV) ? RAILS_ENV : nil)) &&
15
- ActiveRecord::Base.configurations[rails_env] &&
16
- ActiveRecord::Base.configurations[rails_env]['adapter'] != 'oracle_enhanced'
17
- return false
18
- else
19
- return true
20
- end
21
- end
22
- end
23
- end
24
-
25
- def column_spec_with_oracle_enhanced(column, types)
26
- # return original method if not using 'OracleEnhanced'
27
- return column_spec_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter?
28
-
29
- spec = prepare_column_options(column, types)
30
- (spec.keys - [:name, :type]).each do |k|
31
- key_s = (k == :virtual_type ? "type: " : "#{k.to_s}: ")
32
- spec[k] = key_s + spec[k]
33
- end
34
- spec
35
- end
36
-
37
- def prepare_column_options_with_oracle_enhanced(column, types)
38
- # return original method if not using 'OracleEnhanced'
39
- return prepare_column_options_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter?
40
-
41
- spec = {}
42
-
43
- spec[:name] = column.name.inspect
44
- spec[:type] = column.virtual? ? 'virtual' : column.type.to_s
45
- spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && column.type != :decimal
46
- spec[:precision] = column.precision.inspect if !column.precision.nil?
47
- spec[:scale] = column.scale.inspect if !column.scale.nil?
48
- spec[:null] = 'false' if !column.null
49
- spec[:as] = column.virtual_column_data_default if column.virtual?
50
- spec[:default] = default_string(column.default) if column.has_default? && !column.virtual?
51
-
52
- if column.virtual?
53
- # Supports backwards compatibility with older OracleEnhancedAdapter versions where 'NUMBER' virtual column type is not included in dump
54
- if column.sql_type != "NUMBER" || ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.number_datatype_coercion != :decimal
55
- spec[:virtual_type] = column.type.inspect
56
- end
57
- end
58
-
59
- spec
60
- end
61
-
62
- def migration_keys_with_oracle_enhanced
63
- # TODO `& column_specs.map(&:keys).flatten` should be exetuted here
64
- # return original method if not using 'OracleEnhanced'
65
- return migration_keys_without_oracle_enhanced unless oracle_enhanced_adapter?
66
-
67
- [:name, :limit, :precision, :scale, :default, :null, :as, :virtual_type]
68
- end
69
-
70
-
71
- end
72
- end
73
- end
74
-
75
- ActiveRecord::ConnectionAdapters::ColumnDumper.class_eval do
76
- include ActiveRecord::ConnectionAdapters::OracleEnhancedColumnDumper
77
- end
@@ -1,350 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module OracleEnhancedContextIndex
4
-
5
- # Define full text index with Oracle specific CONTEXT index type
6
- #
7
- # Oracle CONTEXT index by default supports full text indexing of one column.
8
- # This method allows full text index creation also on several columns
9
- # as well as indexing related table columns by generating stored procedure
10
- # that concatenates all columns for indexing as well as generating trigger
11
- # that will update main index column to trigger reindexing of record.
12
- #
13
- # Use +contains+ ActiveRecord model instance method to add CONTAINS where condition
14
- # and order by score of matched results.
15
- #
16
- # Options:
17
- #
18
- # * <tt>:name</tt>
19
- # * <tt>:index_column</tt>
20
- # * <tt>:index_column_trigger_on</tt>
21
- # * <tt>:tablespace</tt>
22
- # * <tt>:sync</tt> - 'MANUAL', 'EVERY "interval-string"' or 'ON COMMIT' (defaults to 'MANUAL').
23
- # * <tt>:lexer</tt> - Lexer options (e.g. <tt>:type => 'BASIC_LEXER', :base_letter => true</tt>).
24
- # * <tt>:wordlist</tt> - Wordlist options (e.g. <tt>:type => 'BASIC_WORDLIST', :prefix_index => true</tt>).
25
- # * <tt>:transactional</tt> - When +true+, the CONTAINS operator will process inserted and updated rows.
26
- #
27
- # ===== Examples
28
- #
29
- # ====== Creating single column index
30
- # add_context_index :posts, :title
31
- # search with
32
- # Post.contains(:title, 'word')
33
- #
34
- # ====== Creating index on several columns
35
- # add_context_index :posts, [:title, :body]
36
- # search with (use first column as argument for contains method but it will search in all index columns)
37
- # Post.contains(:title, 'word')
38
- #
39
- # ====== Creating index on several columns with dummy index column and commit option
40
- # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT'
41
- # search with
42
- # Post.contains(:all_text, 'word')
43
- #
44
- # ====== Creating index with trigger option (will reindex when specified columns are updated)
45
- # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT',
46
- # :index_column_trigger_on => [:created_at, :updated_at]
47
- # search with
48
- # Post.contains(:all_text, 'word')
49
- #
50
- # ====== Creating index on multiple tables
51
- # add_context_index :posts,
52
- # [:title, :body,
53
- # # specify aliases always with AS keyword
54
- # "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id"
55
- # ],
56
- # :name => 'post_and_comments_index',
57
- # :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count],
58
- # :sync => 'ON COMMIT'
59
- # search in any table columns
60
- # Post.contains(:all_text, 'word')
61
- # search in specified column
62
- # Post.contains(:all_text, "aaa within title")
63
- # Post.contains(:all_text, "bbb within comment_author")
64
- #
65
- # ====== Creating index using lexer
66
- # add_context_index :posts, :title, :lexer => { :type => 'BASIC_LEXER', :base_letter => true, ... }
67
- #
68
- # ====== Creating index using wordlist
69
- # add_context_index :posts, :title, :wordlist => { :type => 'BASIC_WORDLIST', :prefix_index => true, ... }
70
- #
71
- # ====== Creating transactional index (will reindex changed rows when querying)
72
- # add_context_index :posts, :title, :transactional => true
73
- #
74
- def add_context_index(table_name, column_name, options = {})
75
- self.all_schema_indexes = nil
76
- column_names = Array(column_name)
77
- index_name = options[:name] || index_name(table_name, :column => options[:index_column] || column_names,
78
- # CONEXT index name max length is 25
79
- :identifier_max_length => 25)
80
-
81
- quoted_column_name = quote_column_name(options[:index_column] || column_names.first)
82
- if options[:index_column_trigger_on]
83
- raise ArgumentError, "Option :index_column should be specified together with :index_column_trigger_on option" \
84
- unless options[:index_column]
85
- create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on])
86
- end
87
-
88
- sql = "CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
89
- sql << " (#{quoted_column_name})"
90
- sql << " INDEXTYPE IS CTXSYS.CONTEXT"
91
- parameters = []
92
- if column_names.size > 1
93
- procedure_name = default_datastore_procedure(index_name)
94
- datastore_name = default_datastore_name(index_name)
95
- create_datastore_procedure(table_name, procedure_name, column_names, options)
96
- create_datastore_preference(datastore_name, procedure_name)
97
- parameters << "DATASTORE #{datastore_name} SECTION GROUP CTXSYS.AUTO_SECTION_GROUP"
98
- end
99
- if options[:tablespace]
100
- storage_name = default_storage_name(index_name)
101
- create_storage_preference(storage_name, options[:tablespace])
102
- parameters << "STORAGE #{storage_name}"
103
- end
104
- if options[:sync]
105
- parameters << "SYNC(#{options[:sync]})"
106
- end
107
- if options[:lexer] && (lexer_type = options[:lexer][:type])
108
- lexer_name = default_lexer_name(index_name)
109
- (lexer_options = options[:lexer].dup).delete(:type)
110
- create_lexer_preference(lexer_name, lexer_type, lexer_options)
111
- parameters << "LEXER #{lexer_name}"
112
- end
113
- if options[:wordlist] && (wordlist_type = options[:wordlist][:type])
114
- wordlist_name = default_wordlist_name(index_name)
115
- (wordlist_options = options[:wordlist].dup).delete(:type)
116
- create_wordlist_preference(wordlist_name, wordlist_type, wordlist_options)
117
- parameters << "WORDLIST #{wordlist_name}"
118
- end
119
- if options[:transactional]
120
- parameters << "TRANSACTIONAL"
121
- end
122
- unless parameters.empty?
123
- sql << " PARAMETERS ('#{parameters.join(' ')}')"
124
- end
125
- execute sql
126
- end
127
-
128
- # Drop full text index with Oracle specific CONTEXT index type
129
- def remove_context_index(table_name, options = {})
130
- self.all_schema_indexes = nil
131
- unless Hash === options # if column names passed as argument
132
- options = {:column => Array(options)}
133
- end
134
- index_name = options[:name] || index_name(table_name,
135
- :column => options[:index_column] || options[:column], :identifier_max_length => 25)
136
- execute "DROP INDEX #{index_name}"
137
- drop_ctx_preference(default_datastore_name(index_name))
138
- drop_ctx_preference(default_storage_name(index_name))
139
- procedure_name = default_datastore_procedure(index_name)
140
- execute "DROP PROCEDURE #{quote_table_name(procedure_name)}" rescue nil
141
- drop_index_column_trigger(index_name)
142
- end
143
-
144
- private
145
-
146
- def create_datastore_procedure(table_name, procedure_name, column_names, options)
147
- quoted_table_name = quote_table_name(table_name)
148
- select_queries, column_names = column_names.partition { |c| c.to_s =~ /^\s*SELECT\s+/i }
149
- select_queries = select_queries.map { |s| s.strip.gsub(/\s+/, ' ') }
150
- keys, selected_columns = parse_select_queries(select_queries)
151
- quoted_column_names = (column_names+keys).map{|col| quote_column_name(col)}
152
- execute compress_lines(<<-SQL)
153
- CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)}
154
- (p_rowid IN ROWID,
155
- p_clob IN OUT NOCOPY CLOB) IS
156
- -- add_context_index_parameters #{(column_names+select_queries).inspect}#{!options.empty? ? ', ' << options.inspect[1..-2] : ''}
157
- #{
158
- selected_columns.map do |cols|
159
- cols.map do |col|
160
- raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28
161
- "l_#{col} VARCHAR2(32767);\n"
162
- end.join
163
- end.join
164
- } BEGIN
165
- FOR r1 IN (
166
- SELECT #{quoted_column_names.join(', ')}
167
- FROM #{quoted_table_name}
168
- WHERE #{quoted_table_name}.ROWID = p_rowid
169
- ) LOOP
170
- #{
171
- (column_names.map do |col|
172
- col = col.to_s
173
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" <<
174
- "IF LENGTH(r1.#{col}) > 0 THEN\n" <<
175
- "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" <<
176
- "END IF;\n" <<
177
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '</#{col}>');\n"
178
- end.join) <<
179
- (selected_columns.zip(select_queries).map do |cols, query|
180
- (cols.map do |col|
181
- "l_#{col} := '';\n"
182
- end.join) <<
183
- "FOR r2 IN (\n" <<
184
- query.gsub(/:(\w+)/,"r1.\\1") << "\n) LOOP\n" <<
185
- (cols.map do |col|
186
- "l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n"
187
- end.join) <<
188
- "END LOOP;\n" <<
189
- (cols.map do |col|
190
- col = col.to_s
191
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" <<
192
- "IF LENGTH(l_#{col}) > 0 THEN\n" <<
193
- "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" <<
194
- "END IF;\n" <<
195
- "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '</#{col}>');\n"
196
- end.join)
197
- end.join)
198
- }
199
- END LOOP;
200
- END;
201
- SQL
202
- end
203
-
204
- def parse_select_queries(select_queries)
205
- keys = []
206
- selected_columns = []
207
- select_queries.each do |query|
208
- # get primary or foreign keys like :id or :something_id
209
- keys << (query.scan(/:\w+/).map{|k| k[1..-1].downcase.to_sym})
210
- select_part = query.scan(/^select\s.*\sfrom/i).first
211
- selected_columns << select_part.scan(/\sas\s+(\w+)/i).map{|c| c.first}
212
- end
213
- [keys.flatten.uniq, selected_columns]
214
- end
215
-
216
- def create_datastore_preference(datastore_name, procedure_name)
217
- drop_ctx_preference(datastore_name)
218
- execute <<-SQL
219
- BEGIN
220
- CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE');
221
- CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}');
222
- END;
223
- SQL
224
- end
225
-
226
- def create_storage_preference(storage_name, tablespace)
227
- drop_ctx_preference(storage_name)
228
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n"
229
- ['I_TABLE_CLAUSE', 'K_TABLE_CLAUSE', 'R_TABLE_CLAUSE',
230
- 'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause|
231
- default_clause = case clause
232
- when 'R_TABLE_CLAUSE'; 'LOB(DATA) STORE AS (CACHE) '
233
- when 'I_INDEX_CLAUSE'; 'COMPRESS 2 '
234
- else ''
235
- end
236
- sql << "CTX_DDL.SET_ATTRIBUTE('#{storage_name}', '#{clause}', '#{default_clause}TABLESPACE #{tablespace}');\n"
237
- end
238
- sql << "END;\n"
239
- execute sql
240
- end
241
-
242
- def create_lexer_preference(lexer_name, lexer_type, options)
243
- drop_ctx_preference(lexer_name)
244
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{lexer_name}', '#{lexer_type}');\n"
245
- options.each do |key, value|
246
- plsql_value = case value
247
- when String; "'#{value}'"
248
- when true; "'YES'"
249
- when false; "'NO'"
250
- when nil; 'NULL'
251
- else value
252
- end
253
- sql << "CTX_DDL.SET_ATTRIBUTE('#{lexer_name}', '#{key}', #{plsql_value});\n"
254
- end
255
- sql << "END;\n"
256
- execute sql
257
- end
258
-
259
- def create_wordlist_preference(wordlist_name, wordlist_type, options)
260
- drop_ctx_preference(wordlist_name)
261
- sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{wordlist_name}', '#{wordlist_type}');\n"
262
- options.each do |key, value|
263
- plsql_value = case value
264
- when String; "'#{value}'"
265
- when true; "'YES'"
266
- when false; "'NO'"
267
- when nil; 'NULL'
268
- else value
269
- end
270
- sql << "CTX_DDL.SET_ATTRIBUTE('#{wordlist_name}', '#{key}', #{plsql_value});\n"
271
- end
272
- sql << "END;\n"
273
- execute sql
274
- end
275
-
276
- def drop_ctx_preference(preference_name)
277
- execute "BEGIN CTX_DDL.DROP_PREFERENCE('#{preference_name}'); END;" rescue nil
278
- end
279
-
280
- def create_index_column_trigger(table_name, index_name, index_column, index_column_source)
281
- trigger_name = default_index_column_trigger_name(index_name)
282
- columns = Array(index_column_source)
283
- quoted_column_names = columns.map{|col| quote_column_name(col)}.join(', ')
284
- execute compress_lines(<<-SQL)
285
- CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
286
- BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW
287
- BEGIN
288
- :new.#{quote_column_name(index_column)} := '1';
289
- END;
290
- SQL
291
- end
292
-
293
- def drop_index_column_trigger(index_name)
294
- trigger_name = default_index_column_trigger_name(index_name)
295
- execute "DROP TRIGGER #{quote_table_name(trigger_name)}" rescue nil
296
- end
297
-
298
- def default_datastore_procedure(index_name)
299
- "#{index_name}_prc"
300
- end
301
-
302
- def default_datastore_name(index_name)
303
- "#{index_name}_dst"
304
- end
305
-
306
- def default_storage_name(index_name)
307
- "#{index_name}_sto"
308
- end
309
-
310
- def default_index_column_trigger_name(index_name)
311
- "#{index_name}_trg"
312
- end
313
-
314
- def default_lexer_name(index_name)
315
- "#{index_name}_lex"
316
- end
317
-
318
- def default_wordlist_name(index_name)
319
- "#{index_name}_wl"
320
- end
321
-
322
- module BaseClassMethods
323
- # Declare that model table has context index defined.
324
- # As a result <tt>contains</tt> class scope method is defined.
325
- def has_context_index
326
- extend ContextIndexClassMethods
327
- end
328
- end
329
-
330
- module ContextIndexClassMethods
331
- # Add context index condition.
332
- def contains(column, query, options ={})
333
- score_label = options[:label].to_i || 1
334
- where("CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query).
335
- order("SCORE(#{score_label}) DESC")
336
- end
337
- end
338
-
339
- end
340
-
341
- end
342
- end
343
-
344
- ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
345
- include ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex
346
- end
347
-
348
- ActiveRecord::Base.class_eval do
349
- extend ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex::BaseClassMethods
350
- end