activerecord-oracle_enhanced-adapter 1.4.3 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1162 -2
  3. data/README.md +567 -155
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +3 -1
  6. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +19 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +132 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +345 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +52 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +280 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +64 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +59 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +538 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +46 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +435 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +196 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +164 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +79 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +194 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +709 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +28 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +353 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +33 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +385 -1083
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +20 -0
  30. data/lib/active_record/type/oracle_enhanced/integer.rb +15 -0
  31. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  32. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  34. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  35. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  36. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  37. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  38. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  39. data/lib/activerecord-oracle_enhanced-adapter.rb +5 -13
  40. data/spec/active_record/connection_adapters/{oracle_enhanced_emulate_oracle_adapter_spec.rb → emulation/oracle_adapter_spec.rb} +5 -4
  41. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +469 -0
  42. data/spec/active_record/connection_adapters/{oracle_enhanced_context_index_spec.rb → oracle_enhanced/context_index_spec.rb} +140 -128
  43. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +112 -0
  44. data/spec/active_record/connection_adapters/{oracle_enhanced_dbms_output_spec.rb → oracle_enhanced/dbms_output_spec.rb} +13 -13
  45. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +365 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +196 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1433 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +478 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +385 -550
  51. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +92 -1249
  52. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  53. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +208 -0
  54. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +139 -0
  55. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  56. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +91 -0
  57. data/spec/active_record/oracle_enhanced/type/json_spec.rb +57 -0
  58. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  59. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  60. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +122 -0
  61. data/spec/active_record/oracle_enhanced/type/text_spec.rb +229 -0
  62. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +75 -0
  63. data/spec/spec_config.yaml.template +11 -0
  64. data/spec/spec_helper.rb +100 -93
  65. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  66. data/spec/support/alter_system_user_password.sql +2 -0
  67. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  68. metadata +105 -152
  69. data/.rspec +0 -2
  70. data/Gemfile +0 -52
  71. data/RUNNING_TESTS.md +0 -45
  72. data/Rakefile +0 -59
  73. data/activerecord-oracle_enhanced-adapter.gemspec +0 -130
  74. data/lib/active_record/connection_adapters/oracle_enhanced.rake +0 -105
  75. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +0 -41
  76. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +0 -121
  77. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +0 -151
  78. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +0 -119
  79. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -359
  80. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +0 -25
  81. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +0 -21
  82. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -46
  83. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +0 -572
  84. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +0 -497
  85. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +0 -260
  86. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -227
  87. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +0 -260
  88. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -428
  89. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  90. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +0 -294
  91. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +0 -17
  92. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
  93. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +0 -334
  94. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +0 -19
  95. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +0 -113
  96. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +0 -141
  97. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +0 -378
  98. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +0 -440
  99. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +0 -1400
  100. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +0 -339
@@ -1,151 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters #:nodoc:
3
- class OracleEnhancedColumn < Column
4
-
5
- attr_reader :table_name, :forced_column_type, :nchar, :virtual_column_data_default, :returning_id #:nodoc:
6
-
7
- def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) #:nodoc:
8
- @table_name = table_name
9
- @forced_column_type = forced_column_type
10
- @virtual = virtual
11
- @virtual_column_data_default = default.inspect if virtual
12
- @returning_id = returning_id
13
- default = nil if virtual
14
- super(name, default, sql_type, null)
15
- # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)?
16
- # Define only when needed as adapter "quote" method will check at first if instance variable is defined.
17
- @nchar = true if @type == :string && sql_type[0,1] == 'N'
18
- @object_type = sql_type.include? '.'
19
- end
20
-
21
- def type_cast(value) #:nodoc:
22
- return OracleEnhancedColumn::string_to_raw(value) if type == :raw
23
- return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
24
- super
25
- end
26
-
27
- def virtual?
28
- @virtual
29
- end
30
-
31
- def returning_id?
32
- @returning_id
33
- end
34
-
35
- def lob?
36
- self.sql_type =~ /LOB$/i
37
- end
38
-
39
- def object_type?
40
- @object_type
41
- end
42
-
43
- # convert something to a boolean
44
- # added y as boolean value
45
- def self.value_to_boolean(value) #:nodoc:
46
- if value == true || value == false
47
- value
48
- elsif value.is_a?(String) && value.blank?
49
- nil
50
- else
51
- %w(true t 1 y +).include?(value.to_s.downcase)
52
- end
53
- end
54
-
55
- # convert Time or DateTime value to Date for :date columns
56
- def self.string_to_date(string) #:nodoc:
57
- return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
58
- super
59
- end
60
-
61
- # convert Date value to Time for :datetime columns
62
- def self.string_to_time(string) #:nodoc:
63
- return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
64
- super
65
- end
66
-
67
- # convert RAW column values back to byte strings.
68
- def self.string_to_raw(string) #:nodoc:
69
- string
70
- end
71
-
72
- # Get column comment from schema definition.
73
- # Will work only if using default ActiveRecord connection.
74
- def comment
75
- ActiveRecord::Base.connection.column_comment(@table_name, name)
76
- end
77
-
78
- private
79
-
80
- def simplified_type(field_type)
81
- forced_column_type ||
82
- case field_type
83
- when /decimal|numeric|number/i
84
- if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
85
- :boolean
86
- elsif extract_scale(field_type) == 0 ||
87
- # if column name is ID or ends with _ID
88
- OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
89
- :integer
90
- else
91
- :decimal
92
- end
93
- when /raw/i
94
- :raw
95
- when /char/i
96
- if OracleEnhancedAdapter.emulate_booleans_from_strings &&
97
- OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name)
98
- :boolean
99
- else
100
- :string
101
- end
102
- when /date/i
103
- if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)
104
- :date
105
- else
106
- :datetime
107
- end
108
- when /timestamp/i
109
- :timestamp
110
- when /time/i
111
- :datetime
112
- else
113
- super
114
- end
115
- end
116
-
117
- def guess_date_or_time(value)
118
- value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
119
- Date.new(value.year, value.month, value.day) : value
120
- end
121
-
122
- class << self
123
- protected
124
-
125
- def fallback_string_to_date(string) #:nodoc:
126
- if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
127
- return (string_to_date_or_time_using_format(string).to_date rescue super)
128
- end
129
- super
130
- end
131
-
132
- def fallback_string_to_time(string) #:nodoc:
133
- if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
134
- return (string_to_date_or_time_using_format(string).to_time rescue super)
135
- end
136
- super
137
- end
138
-
139
- def string_to_date_or_time_using_format(string) #:nodoc:
140
- if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
141
- return Time.parse("#{dt[:year]}-#{dt[:mon]}-#{dt[:mday]} #{dt[:hour]}:#{dt[:min]}:#{dt[:sec]}#{dt[:zone]}")
142
- end
143
- DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format).to_date
144
- end
145
-
146
- end
147
- end
148
-
149
- end
150
-
151
- end
@@ -1,119 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- # interface independent methods
4
- class OracleEnhancedConnection #:nodoc:
5
-
6
- def self.create(config)
7
- case ORACLE_ENHANCED_CONNECTION
8
- when :oci
9
- OracleEnhancedOCIConnection.new(config)
10
- when :jdbc
11
- OracleEnhancedJDBCConnection.new(config)
12
- else
13
- nil
14
- end
15
- end
16
-
17
- attr_reader :raw_connection
18
-
19
- # Oracle column names by default are case-insensitive, but treated as upcase;
20
- # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
21
- # their column names when creating Oracle tables, which makes then case-sensitive.
22
- # I don't know anybody who does this, but we'll handle the theoretical case of a
23
- # camelCase column name. I imagine other dbs handle this different, since there's a
24
- # unit test that's currently failing test_oci.
25
- def oracle_downcase(column_name)
26
- return nil if column_name.nil?
27
- column_name =~ /[a-z]/ ? column_name : column_name.downcase
28
- end
29
-
30
- # Used always by JDBC connection as well by OCI connection when describing tables over database link
31
- def describe(name)
32
- name = name.to_s
33
- if name.include?('@')
34
- name, db_link = name.split('@')
35
- default_owner = select_value("SELECT username FROM all_db_links WHERE db_link = '#{db_link.upcase}'")
36
- db_link = "@#{db_link}"
37
- else
38
- db_link = nil
39
- default_owner = @owner
40
- end
41
- real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
42
- if real_name.include?('.')
43
- table_owner, table_name = real_name.split('.')
44
- else
45
- table_owner, table_name = default_owner, real_name
46
- end
47
- sql = <<-SQL
48
- SELECT owner, table_name, 'TABLE' name_type
49
- FROM all_tables#{db_link}
50
- WHERE owner = '#{table_owner}'
51
- AND table_name = '#{table_name}'
52
- UNION ALL
53
- SELECT owner, view_name table_name, 'VIEW' name_type
54
- FROM all_views#{db_link}
55
- WHERE owner = '#{table_owner}'
56
- AND view_name = '#{table_name}'
57
- UNION ALL
58
- SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
59
- FROM all_synonyms#{db_link}
60
- WHERE owner = '#{table_owner}'
61
- AND synonym_name = '#{table_name}'
62
- UNION ALL
63
- SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
64
- FROM all_synonyms#{db_link}
65
- WHERE owner = 'PUBLIC'
66
- AND synonym_name = '#{real_name}'
67
- SQL
68
- if result = select_one(sql)
69
- case result['name_type']
70
- when 'SYNONYM'
71
- describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}#{db_link}")
72
- else
73
- db_link ? [result['owner'], result['table_name'], db_link] : [result['owner'], result['table_name']]
74
- end
75
- else
76
- raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
77
- end
78
- end
79
-
80
- # Returns a record hash with the column names as keys and column values
81
- # as values.
82
- def select_one(sql)
83
- result = select(sql)
84
- result.first if result
85
- end
86
-
87
- # Returns a single value from a record
88
- def select_value(sql)
89
- if result = select_one(sql)
90
- result.values.first
91
- end
92
- end
93
-
94
- # Returns an array of the values of the first column in a select:
95
- # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
96
- def select_values(sql, name = nil)
97
- result = select(sql, name = nil)
98
- result.map { |r| r.values.first }
99
- end
100
-
101
- end
102
-
103
- class OracleEnhancedConnectionException < StandardError #:nodoc:
104
- end
105
-
106
- end
107
- end
108
-
109
- # if MRI or YARV
110
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
111
- ORACLE_ENHANCED_CONNECTION = :oci
112
- require 'active_record/connection_adapters/oracle_enhanced_oci_connection'
113
- # if JRuby
114
- elsif RUBY_ENGINE == 'jruby'
115
- ORACLE_ENHANCED_CONNECTION = :jdbc
116
- require 'active_record/connection_adapters/oracle_enhanced_jdbc_connection'
117
- else
118
- raise "Unsupported Ruby engine #{RUBY_ENGINE}"
119
- end
@@ -1,359 +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
- case ::ActiveRecord::VERSION::MAJOR
333
- when 3
334
- def contains(column, query, options ={})
335
- score_label = options[:label].to_i || 1
336
- where("CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query).
337
- order("SCORE(#{score_label}) DESC")
338
- end
339
- when 2
340
- def contains(column, query, options ={})
341
- score_label = options[:label].to_i || 1
342
- scoped(:conditions => ["CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query],
343
- :order => "SCORE(#{score_label}) DESC")
344
- end
345
- end
346
- end
347
-
348
- end
349
-
350
- end
351
- end
352
-
353
- ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
354
- include ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex
355
- end
356
-
357
- ActiveRecord::Base.class_eval do
358
- extend ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex::BaseClassMethods
359
- end