activerecord-jdbc-adapter 1.2.5 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +5 -1
  3. data/Appraisals +5 -5
  4. data/Gemfile +9 -1
  5. data/Gemfile.lock +44 -10
  6. data/History.txt +126 -2
  7. data/README.md +246 -0
  8. data/Rakefile +34 -25
  9. data/activerecord-jdbc-adapter.gemspec +1 -1
  10. data/gemfiles/rails23.gemfile +5 -3
  11. data/gemfiles/rails23.gemfile.lock +26 -18
  12. data/gemfiles/rails30.gemfile +4 -2
  13. data/gemfiles/rails30.gemfile.lock +16 -8
  14. data/gemfiles/rails31.gemfile +4 -2
  15. data/gemfiles/rails31.gemfile.lock +16 -9
  16. data/gemfiles/rails32.gemfile +4 -2
  17. data/gemfiles/rails32.gemfile.lock +15 -8
  18. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  19. data/lib/arel/visitors/sql_server.rb +3 -0
  20. data/lib/arjdbc.rb +3 -5
  21. data/lib/arjdbc/db2.rb +1 -0
  22. data/lib/arjdbc/db2/adapter.rb +302 -196
  23. data/lib/arjdbc/db2/connection_methods.rb +18 -0
  24. data/lib/arjdbc/derby/active_record_patch.rb +12 -0
  25. data/lib/arjdbc/derby/adapter.rb +180 -158
  26. data/lib/arjdbc/derby/connection_methods.rb +5 -1
  27. data/lib/arjdbc/firebird/adapter.rb +27 -19
  28. data/lib/arjdbc/h2/adapter.rb +162 -7
  29. data/lib/arjdbc/h2/connection_methods.rb +5 -1
  30. data/lib/arjdbc/hsqldb.rb +1 -1
  31. data/lib/arjdbc/hsqldb/adapter.rb +96 -61
  32. data/lib/arjdbc/hsqldb/connection_methods.rb +5 -1
  33. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  34. data/lib/arjdbc/informix/adapter.rb +56 -55
  35. data/lib/arjdbc/jdbc/adapter.rb +173 -86
  36. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  37. data/lib/arjdbc/jdbc/column.rb +28 -23
  38. data/lib/arjdbc/jdbc/connection.rb +10 -6
  39. data/lib/arjdbc/jdbc/driver.rb +13 -5
  40. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +21 -0
  41. data/lib/arjdbc/mssql.rb +1 -1
  42. data/lib/arjdbc/mssql/adapter.rb +51 -53
  43. data/lib/arjdbc/mssql/connection_methods.rb +8 -1
  44. data/lib/arjdbc/mysql.rb +1 -1
  45. data/lib/arjdbc/mysql/adapter.rb +186 -150
  46. data/lib/arjdbc/mysql/connection_methods.rb +9 -9
  47. data/lib/arjdbc/mysql/explain_support.rb +85 -0
  48. data/lib/arjdbc/oracle.rb +1 -1
  49. data/lib/arjdbc/oracle/adapter.rb +232 -125
  50. data/lib/arjdbc/oracle/connection_methods.rb +2 -2
  51. data/lib/arjdbc/postgresql.rb +1 -1
  52. data/lib/arjdbc/postgresql/adapter.rb +134 -86
  53. data/lib/arjdbc/postgresql/connection_methods.rb +6 -4
  54. data/lib/arjdbc/postgresql/explain_support.rb +55 -0
  55. data/lib/arjdbc/sqlite3.rb +1 -1
  56. data/lib/arjdbc/sqlite3/adapter.rb +176 -108
  57. data/lib/arjdbc/sqlite3/connection_methods.rb +5 -5
  58. data/lib/arjdbc/sqlite3/explain_support.rb +32 -0
  59. data/lib/arjdbc/sybase/adapter.rb +7 -6
  60. data/lib/arjdbc/version.rb +1 -1
  61. data/pom.xml +1 -1
  62. data/rakelib/02-test.rake +9 -11
  63. data/rakelib/rails.rake +18 -10
  64. data/src/java/arjdbc/db2/DB2Module.java +70 -0
  65. data/src/java/arjdbc/derby/DerbyModule.java +24 -5
  66. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +66 -0
  67. data/src/java/arjdbc/jdbc/AdapterJavaService.java +14 -7
  68. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +111 -89
  69. data/src/java/arjdbc/mysql/MySQLModule.java +79 -70
  70. data/src/java/arjdbc/oracle/OracleModule.java +74 -0
  71. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +5 -10
  72. data/src/java/arjdbc/sqlite3/SQLite3Module.java +77 -0
  73. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +127 -0
  74. data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +25 -111
  75. data/src/java/arjdbc/util/QuotingUtils.java +104 -0
  76. data/test/abstract_db_create.rb +6 -6
  77. data/test/activerecord/connection_adapters/type_conversion_test.rb +2 -2
  78. data/test/assets/flowers.jpg +0 -0
  79. data/test/binary.rb +67 -0
  80. data/test/db/db2.rb +30 -7
  81. data/test/db/jdbc.rb +4 -2
  82. data/test/db/oracle.rb +18 -27
  83. data/test/db2_binary_test.rb +6 -0
  84. data/test/db2_serialize_test.rb +6 -0
  85. data/test/db2_simple_test.rb +20 -25
  86. data/test/db2_test.rb +71 -0
  87. data/test/derby_binary_test.rb +6 -0
  88. data/test/derby_migration_test.rb +42 -35
  89. data/test/derby_reset_column_information_test.rb +1 -0
  90. data/test/derby_row_locking_test.rb +17 -0
  91. data/test/derby_schema_dump_test.rb +9 -0
  92. data/test/derby_serialize_test.rb +6 -0
  93. data/test/derby_simple_test.rb +59 -17
  94. data/test/generic_jdbc_connection_test.rb +112 -5
  95. data/test/h2_binary_test.rb +6 -0
  96. data/test/h2_change_column_test.rb +1 -1
  97. data/test/h2_schema_dump_test.rb +25 -0
  98. data/test/h2_serialize_test.rb +6 -0
  99. data/test/h2_simple_test.rb +23 -9
  100. data/test/has_many_through.rb +18 -4
  101. data/test/hsqldb_binary_test.rb +6 -0
  102. data/test/hsqldb_schema_dump_test.rb +15 -0
  103. data/test/hsqldb_serialize_test.rb +6 -0
  104. data/test/hsqldb_simple_test.rb +1 -0
  105. data/test/informix_simple_test.rb +1 -1
  106. data/test/jdbc/db2.rb +23 -0
  107. data/test/jdbc/oracle.rb +23 -0
  108. data/test/jdbc_common.rb +3 -110
  109. data/test/jndi_callbacks_test.rb +0 -2
  110. data/test/jndi_test.rb +2 -0
  111. data/test/models/binary.rb +18 -0
  112. data/test/models/custom_pk_name.rb +1 -0
  113. data/test/models/data_types.rb +11 -2
  114. data/test/models/entry.rb +1 -1
  115. data/test/models/string_id.rb +2 -2
  116. data/test/models/thing.rb +1 -1
  117. data/test/models/topic.rb +32 -0
  118. data/test/mssql_legacy_types_test.rb +1 -1
  119. data/test/mssql_limit_offset_test.rb +13 -3
  120. data/test/mssql_serialize_test.rb +6 -0
  121. data/test/mysql_binary_test.rb +6 -0
  122. data/test/mysql_schema_dump_test.rb +220 -0
  123. data/test/mysql_serialize_test.rb +6 -0
  124. data/test/mysql_simple_test.rb +22 -2
  125. data/test/mysql_test.rb +93 -0
  126. data/test/oracle_binary_test.rb +6 -0
  127. data/test/oracle_limit_test.rb +2 -1
  128. data/test/oracle_serialize_test.rb +6 -0
  129. data/test/oracle_simple_test.rb +61 -0
  130. data/test/oracle_specific_test.rb +77 -26
  131. data/test/postgres_binary_test.rb +6 -0
  132. data/test/postgres_native_type_mapping_test.rb +12 -11
  133. data/test/postgres_nonseq_pkey_test.rb +1 -0
  134. data/test/postgres_reserved_test.rb +1 -0
  135. data/test/postgres_reset_column_information_test.rb +1 -0
  136. data/test/postgres_row_locking_test.rb +21 -0
  137. data/test/postgres_schema_dump_test.rb +88 -0
  138. data/test/postgres_schema_search_path_test.rb +1 -0
  139. data/test/postgres_simple_test.rb +62 -89
  140. data/test/postgres_table_alias_length_test.rb +1 -0
  141. data/test/postgres_test.rb +31 -0
  142. data/test/postgres_type_conversion_test.rb +16 -16
  143. data/test/row_locking.rb +69 -64
  144. data/test/schema_dump.rb +168 -0
  145. data/test/serialize.rb +277 -0
  146. data/test/simple.rb +326 -122
  147. data/test/sqlite3_serialize_test.rb +6 -0
  148. data/test/sqlite3_simple_test.rb +51 -84
  149. data/test/sqlite3_type_conversion_test.rb +101 -0
  150. data/test/test_helper.rb +224 -0
  151. metadata +325 -366
  152. data/README.rdoc +0 -214
  153. data/test/db/logger.rb +0 -3
  154. data/test/derby_multibyte_test.rb +0 -11
  155. data/test/mysql_info_test.rb +0 -123
@@ -1,22 +1,22 @@
1
- # Don't need to load native mysql adapter
2
- $LOADED_FEATURES << "active_record/connection_adapters/mysql_adapter.rb"
3
- $LOADED_FEATURES << "active_record/connection_adapters/mysql2_adapter.rb"
4
-
5
1
  class ActiveRecord::Base
6
2
  class << self
7
3
  def mysql_connection(config)
8
- require 'active_record/connection_adapters/jdbcmysql_adapter'
4
+ begin
5
+ require 'jdbc/mysql'
6
+ ::Jdbc::MySQL.load_driver(:require) if defined?(::Jdbc::MySQL.load_driver)
7
+ rescue LoadError # assuming driver.jar is on the class-path
8
+ end
9
9
 
10
10
  config[:port] ||= 3306
11
+ config[:url] ||= "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}"
12
+ config[:driver] ||= defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
13
+ config[:adapter_class] = ActiveRecord::ConnectionAdapters::MysqlAdapter
14
+ config[:adapter_spec] = ::ArJdbc::MySQL
11
15
  options = (config[:options] ||= {})
12
16
  options['zeroDateTimeBehavior'] ||= 'convertToNull'
13
17
  options['jdbcCompliantTruncation'] ||= 'false'
14
18
  options['useUnicode'] ||= 'true'
15
19
  options['characterEncoding'] = config[:encoding] || 'utf8'
16
- config[:url] ||= "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}"
17
- config[:driver] ||= defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
18
- config[:adapter_class] = ActiveRecord::ConnectionAdapters::MysqlAdapter
19
- config[:adapter_spec] = ::ArJdbc::MySQL
20
20
  connection = jdbc_connection(config)
21
21
  ::ArJdbc::MySQL.kill_cancel_timer(connection.raw_connection)
22
22
  connection
@@ -0,0 +1,85 @@
1
+ module ::ArJdbc
2
+ module MySQL
3
+ module ExplainSupport
4
+ def supports_explain?
5
+ true
6
+ end
7
+
8
+ def explain(arel, binds = [])
9
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
10
+ start = Time.now.to_f
11
+ raw_result = execute(sql, "EXPLAIN", binds)
12
+ elapsed = Time.now.to_f - start
13
+ # TODO we should refactor to exce_query once it returns Result ASAP :
14
+ keys = raw_result[0] ? raw_result[0].keys : {}
15
+ rows = raw_result.map { |hash| hash.values }
16
+ ExplainPrettyPrinter.new.pp ActiveRecord::Result.new(keys, rows), elapsed
17
+ end
18
+
19
+ class ExplainPrettyPrinter # :nodoc:
20
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
21
+ # MySQL shell:
22
+ #
23
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
24
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
25
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
26
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
27
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
28
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
29
+ # 2 rows in set (0.00 sec)
30
+ #
31
+ # This is an exercise in Ruby hyperrealism :).
32
+ def pp(result, elapsed)
33
+ widths = compute_column_widths(result)
34
+ separator = build_separator(widths)
35
+
36
+ pp = []
37
+
38
+ pp << separator
39
+ pp << build_cells(result.columns, widths)
40
+ pp << separator
41
+
42
+ result.rows.each do |row|
43
+ pp << build_cells(row, widths)
44
+ end
45
+
46
+ pp << separator
47
+ pp << build_footer(result.rows.length, elapsed)
48
+
49
+ pp.join("\n") + "\n"
50
+ end
51
+
52
+ private
53
+
54
+ def compute_column_widths(result)
55
+ [].tap do |widths|
56
+ result.columns.each_with_index do |column, i|
57
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
58
+ widths << cells_in_column.map(&:length).max
59
+ end
60
+ end
61
+ end
62
+
63
+ def build_separator(widths)
64
+ padding = 1
65
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
66
+ end
67
+
68
+ def build_cells(items, widths)
69
+ cells = []
70
+ items.each_with_index do |item, i|
71
+ item = 'NULL' if item.nil?
72
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
73
+ cells << item.to_s.send(justifier, widths[i])
74
+ end
75
+ '| ' + cells.join(' | ') + ' |'
76
+ end
77
+
78
+ def build_footer(nrows, elapsed)
79
+ rows_label = nrows == 1 ? 'row' : 'rows'
80
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,3 +1,3 @@
1
1
  require 'arjdbc/jdbc'
2
- require 'arjdbc/oracle/connection_methods'
3
2
  require 'arjdbc/oracle/adapter'
3
+ require 'arjdbc/oracle/connection_methods'
@@ -1,57 +1,58 @@
1
- module ActiveRecord::ConnectionAdapters
2
- OracleAdapter = Class.new(AbstractAdapter) unless const_defined?(:OracleAdapter)
3
- end
1
+ require 'arjdbc/jdbc/serialized_attributes_helper'
4
2
 
5
- module ::ArJdbc
3
+ module ArJdbc
6
4
  module Oracle
5
+
6
+ @@_lob_callback_added = nil
7
+
7
8
  def self.extended(mod)
8
- unless defined?(@lob_callback_added)
9
+ unless @@_lob_callback_added
9
10
  ActiveRecord::Base.class_eval do
10
11
  def after_save_with_oracle_lob
11
- self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each do |c|
12
- value = self[c.name]
13
- if respond_to?(:unserializable_attribute?)
14
- value = value.to_yaml if unserializable_attribute?(c.name, c)
15
- else
16
- value = value.to_yaml if value.is_a?(Hash)
17
- end
18
- next if value.nil? || (value == '')
19
-
20
- connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
12
+ self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each do |column|
13
+ value = ::ArJdbc::SerializedAttributesHelper.dump_column_value(self, column)
14
+ next if value.nil? || (value == '')
15
+
16
+ connection.write_large_object(
17
+ column.type == :binary, column.name,
18
+ self.class.table_name, self.class.primary_key,
19
+ quote_value(id), value
20
+ )
21
21
  end
22
22
  end
23
23
  end
24
24
 
25
25
  ActiveRecord::Base.after_save :after_save_with_oracle_lob
26
- @lob_callback_added = true
26
+
27
+ @@_lob_callback_added = true
27
28
  end
28
29
 
29
- unless ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_methods(false).detect {|m| m.to_s == "prefetch_primary_key?"}
30
+ unless ActiveRecord::ConnectionAdapters::AbstractAdapter.
31
+ instance_methods(false).detect { |m| m.to_s == "prefetch_primary_key?" }
30
32
  require 'arjdbc/jdbc/quoted_primary_key'
31
33
  ActiveRecord::Base.extend ArJdbc::QuotedPrimaryKeyExtension
32
34
  end
33
-
35
+
34
36
  (class << mod; self; end).class_eval do
35
37
  alias_chained_method :insert, :query_dirty, :ora_insert
36
38
  alias_chained_method :columns, :query_cache, :ora_columns
37
-
38
- # Prevent ORA-01795 for in clauses with more than 1000
39
- def in_clause_length
40
- 1000
41
- end
42
39
  end
43
40
  end
44
-
45
41
 
46
42
  def self.column_selector
47
- [/oracle/i, lambda {|cfg,col| col.extend(::ArJdbc::Oracle::Column)}]
43
+ [ /oracle/i, lambda { |cfg, column| column.extend(::ArJdbc::Oracle::Column) } ]
48
44
  end
49
45
 
50
46
  def self.jdbc_connection_class
51
47
  ::ActiveRecord::ConnectionAdapters::OracleJdbcConnection
52
48
  end
53
49
 
50
+ def jdbc_column_class
51
+ ::ActiveRecord::ConnectionAdapters::OracleColumn
52
+ end
53
+
54
54
  module Column
55
+
55
56
  def primary=(val)
56
57
  super
57
58
  if val && @sql_type =~ /^NUMBER$/i
@@ -59,42 +60,52 @@ module ::ArJdbc
59
60
  end
60
61
  end
61
62
 
63
+ def extract_limit(sql_type)
64
+ case sql_type
65
+ when /^(clob|date)/i; nil
66
+ else super
67
+ end
68
+ end
69
+
62
70
  def type_cast(value)
63
71
  return nil if value.nil?
64
72
  case type
65
- when :datetime then ArJdbc::Oracle::Column.string_to_time(value, self.class)
73
+ when :datetime then ArJdbc::Oracle::Column.string_to_time(value)
74
+ when :timestamp then ArJdbc::Oracle::Column.string_to_time(value)
66
75
  else
67
76
  super
68
77
  end
69
78
  end
70
79
 
71
- def extract_limit(sql_type)
72
- case sql_type
73
- when /^(clob|date)/i; nil
74
- else super
75
- end
76
- end
77
-
78
80
  def type_cast_code(var_name)
79
81
  case type
80
- when :datetime then "ArJdbc::Oracle::Column.string_to_time(#{var_name}, self.class)"
82
+ when :datetime then "ArJdbc::Oracle::Column.string_to_time(#{var_name})"
83
+ when :timestamp then "ArJdbc::Oracle::Column.string_to_time(#{var_name})"
81
84
  else
82
85
  super
83
86
  end
84
87
  end
85
88
 
86
- def self.string_to_time(string, klass)
87
- time = klass.string_to_time(string)
88
- guess_date_or_time(time)
89
+ def self.string_to_time(string)
90
+ return string unless string.is_a?(String)
91
+ return nil if string.empty?
92
+ return Time.now if string.index('CURRENT') == 0 # TODO seems very wrong
93
+
94
+ ::ActiveRecord::ConnectionAdapters::JdbcColumn.string_to_time(string)
95
+ end
96
+
97
+ def self.string_to_dummy_time(string)
98
+ ::ActiveRecord::ConnectionAdapters::JdbcColumn.string_to_dummy_time(string)
89
99
  end
90
100
 
91
101
  def self.guess_date_or_time(value)
92
- return value if Date === value
93
- (value && value.hour == 0 && value.min == 0 && value.sec == 0) ?
94
- Date.new(value.year, value.month, value.day) : value
102
+ return value if value.is_a? Date
103
+ ( value && value.hour == 0 && value.min == 0 && value.sec == 0 ) ?
104
+ Date.new(value.year, value.month, value.day) : value
95
105
  end
96
106
 
97
107
  private
108
+
98
109
  def simplified_type(field_type)
99
110
  case field_type
100
111
  when /^number\(1\)$/i then :boolean
@@ -102,29 +113,32 @@ module ::ArJdbc
102
113
  when /float|double/i then :float
103
114
  when /int/i then :integer
104
115
  when /num|dec|real/i then extract_scale(field_type) == 0 ? :integer : :decimal
105
- when /date|time/i then :datetime
106
- when /clob/i then :text
107
- when /blob/i then :binary
116
+ # Oracle TIMESTAMP stores the date and time to up to 9 digits of sub-second precision
117
+ when /TIMESTAMP/i then :timestamp
118
+ # Oracle DATE stores the date and time to the second
119
+ when /DATE|TIME/i then :datetime
120
+ when /CLOB/i then :text
121
+ when /BLOB/i then :binary
122
+ else
123
+ super
108
124
  end
109
125
  end
110
126
 
111
127
  # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
112
128
  def default_value(value)
113
129
  return nil unless value
130
+ value = value.strip # Not sure why we need this for Oracle?
131
+ upcase = value.upcase
114
132
 
115
- # Not sure why we need this for Oracle?
116
- value = value.strip
117
-
118
- return nil if value == "null"
119
-
120
- # sysdate default should be treated like a null value
121
- return nil if value.downcase == "sysdate"
122
-
133
+ return nil if upcase == "NULL"
134
+ # SYSDATE default should be treated like a NULL value
135
+ return nil if upcase == "SYSDATE"
123
136
  # jdbc returns column default strings with actual single quotes around the value.
124
137
  return $1 if value =~ /^'(.*)'$/
125
138
 
126
139
  value
127
140
  end
141
+
128
142
  end
129
143
 
130
144
  def adapter_name
@@ -139,9 +153,17 @@ module ::ArJdbc
139
153
  columns(table_name).detect {|c| c.primary } if table_name
140
154
  end
141
155
 
142
- def table_alias_length
143
- 30
156
+ # Prevent ORA-01795 for in clauses with more than 1000
157
+ def in_clause_length # :nodoc:
158
+ 1000
144
159
  end
160
+ alias_method :ids_in_list_limit, :in_clause_length
161
+
162
+ # maximum length of Oracle identifiers is 30
163
+ def table_alias_length; 30; end # :nodoc:
164
+ def table_name_length; 30; end # :nodoc:
165
+ def index_name_length; 30; end # :nodoc:
166
+ def column_name_length; 30; end # :nodoc:
145
167
 
146
168
  def default_sequence_name(table, column = nil) #:nodoc:
147
169
  "#{table}_seq"
@@ -173,10 +195,34 @@ module ::ArJdbc
173
195
  def drop_database(name)
174
196
  recreate_database(name)
175
197
  end
176
-
198
+
199
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
200
+ case type.to_sym
201
+ when :binary
202
+ # { BLOB | BINARY LARGE OBJECT } [ ( length [{K |M |G }] ) ]
203
+ # although Oracle does not like limit (length) with BLOB (or CLOB) :
204
+ #
205
+ # CREATE TABLE binaries (data BLOB, short_data BLOB(1024));
206
+ # ORA-00907: missing right parenthesis *
207
+ #
208
+ # TODO do we need to worry about NORMAL vs. non IN-TABLE BLOBs ?!
209
+ # http://dba.stackexchange.com/questions/8770/improve-blob-writing-performance-in-oracle-11g
210
+ # - if the LOB is smaller than 3900 bytes it can be stored inside the
211
+ # table row; by default this is enabled,
212
+ # unless you specify DISABLE STORAGE IN ROW
213
+ # - normal LOB - stored in a separate segment, outside of table,
214
+ # you may even put it in another tablespace;
215
+ super(type, nil, nil, nil)
216
+ when :text
217
+ super(type, nil, nil, nil)
218
+ else
219
+ super
220
+ end
221
+ end
222
+
177
223
  def next_sequence_value(sequence_name)
178
224
  # avoid #select or #select_one so that the sequence values aren't cached
179
- execute("select #{sequence_name}.nextval id from dual").first['id'].to_i
225
+ execute("SELECT #{quote_table_name(sequence_name)}.nextval id FROM dual").first['id'].to_i
180
226
  end
181
227
 
182
228
  def sql_literal?(value)
@@ -184,7 +230,7 @@ module ::ArJdbc
184
230
  end
185
231
 
186
232
  def ora_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) #:nodoc:
187
- if (id_value && !sql_literal?(id_value)) || pk.nil?
233
+ if (id_value && ! sql_literal?(id_value)) || pk.nil?
188
234
  # Pre-assigned id or table without a primary key
189
235
  # Presence of #to_sql means an Arel literal bind variable
190
236
  # that should use #execute_id_insert below
@@ -196,7 +242,7 @@ module ::ArJdbc
196
242
  sequence_name ||= default_sequence_name(table)
197
243
  id_value = next_sequence_value(sequence_name)
198
244
  log(sql, name) do
199
- @connection.execute_id_insert(sql,id_value)
245
+ @connection.execute_id_insert(sql, id_value)
200
246
  end
201
247
  end
202
248
  id_value
@@ -206,23 +252,15 @@ module ::ArJdbc
206
252
  @connection.indexes(table, name, @connection.connection.meta_data.user_name)
207
253
  end
208
254
 
209
- def _execute(sql, name = nil)
210
- case sql.strip
211
- when /\A\(?\s*(select|show)/i then
212
- @connection.execute_query(sql)
213
- else
214
- @connection.execute_update(sql)
215
- end
216
- end
217
-
218
- def modify_types(tp)
219
- tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
220
- tp[:integer] = { :name => "NUMBER", :limit => 38 }
221
- tp[:datetime] = { :name => "DATE" }
222
- tp[:timestamp] = { :name => "DATE" }
223
- tp[:time] = { :name => "DATE" }
224
- tp[:date] = { :name => "DATE" }
225
- tp
255
+ def modify_types(types)
256
+ super(types)
257
+ types[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
258
+ types[:integer] = { :name => "NUMBER", :limit => 38 }
259
+ types[:datetime] = { :name => "DATE" }
260
+ types[:timestamp] = { :name => "TIMESTAMP" }
261
+ types[:time] = { :name => "DATE" }
262
+ types[:date] = { :name => "DATE" }
263
+ types
226
264
  end
227
265
 
228
266
  def add_limit_offset!(sql, options) #:nodoc:
@@ -235,16 +273,41 @@ module ::ArJdbc
235
273
  end
236
274
  end
237
275
 
238
- def current_database #:nodoc:
239
- select_one("select sys_context('userenv','db_name') db from dual")["db"]
276
+ def current_user # :nodoc:
277
+ @current_user ||= execute("SELECT sys_context('userenv', 'session_user') su FROM dual").first['su']
278
+ end
279
+
280
+ def current_database # :nodoc:
281
+ @current_database ||= execute("SELECT sys_context('userenv', 'db_name') db FROM dual").first['db']
282
+ end
283
+
284
+ def current_schema # :nodoc:
285
+ execute("SELECT sys_context('userenv', 'current_schema') schema FROM dual").first['schema']
240
286
  end
241
287
 
288
+ def current_schema=(schema_owner)
289
+ execute("ALTER SESSION SET current_schema=#{schema_owner}")
290
+ end
291
+
292
+ def create_savepoint # :nodoc:
293
+ execute("SAVEPOINT #{current_savepoint_name}")
294
+ end
295
+
296
+ def rollback_to_savepoint # :nodoc:
297
+ execute("ROLLBACK TO #{current_savepoint_name}")
298
+ end
299
+
300
+ def release_savepoint # :nodoc:
301
+ # no RELEASE SAVEPOINT statement in Oracle
302
+ end
303
+
242
304
  def remove_index(table_name, options = {}) #:nodoc:
243
305
  execute "DROP INDEX #{index_name(table_name, options)}"
244
306
  end
245
307
 
246
308
  def change_column_default(table_name, column_name, default) #:nodoc:
247
- execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
309
+ execute "ALTER TABLE #{quote_table_name(table_name)} " +
310
+ "MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
248
311
  end
249
312
 
250
313
  def add_column_options!(sql, options) #:nodoc:
@@ -256,17 +319,21 @@ module ::ArJdbc
256
319
  end
257
320
 
258
321
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
259
- change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
322
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} " <<
323
+ "MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}"
260
324
  add_column_options!(change_column_sql, options)
261
325
  execute(change_column_sql)
262
326
  end
263
327
 
264
328
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
265
- execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
329
+ execute "ALTER TABLE #{quote_table_name(table_name)} " <<
330
+ "RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
266
331
  end
267
-
268
- def remove_column(table_name, column_name) #:nodoc:
269
- execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
332
+
333
+ def remove_column(table_name, *column_names) #:nodoc:
334
+ for column_name in column_names.flatten
335
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
336
+ end
270
337
  end
271
338
 
272
339
  def structure_dump #:nodoc:
@@ -277,19 +344,16 @@ module ::ArJdbc
277
344
  select_all("select table_name from user_tables").inject(s) do |structure, table|
278
345
  ddl = "create table #{table.to_a.first.last} (\n "
279
346
  cols = select_all(%Q{
280
- select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
281
- from user_tab_columns
282
- where table_name = '#{table.to_a.first.last}'
283
- order by column_id
284
- }).map do |row|
285
- row = row.inject({}) do |h,args|
286
- h[args[0].downcase] = args[1]
287
- h
288
- end
347
+ select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
348
+ from user_tab_columns
349
+ where table_name = '#{table.to_a.first.last}'
350
+ order by column_id
351
+ }).map do |row|
352
+ row = row.inject({}) { |h, args| h[ args[0].downcase ] = args[1]; h }
289
353
  col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
290
- if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
354
+ if row['data_type'] == 'NUMBER' and ! row['data_precision'].nil?
291
355
  col << "(#{row['data_precision'].to_i}"
292
- col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
356
+ col << ",#{row['data_scale'].to_i}" if ! row['data_scale'].nil?
293
357
  col << ')'
294
358
  elsif row['data_type'].include?('CHAR')
295
359
  col << "(#{row['data_length'].to_i})"
@@ -329,8 +393,7 @@ module ::ArJdbc
329
393
 
330
394
  # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
331
395
  # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
332
- order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
333
- order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
396
+ order_columns = extract_order_columns(order_by).map do |c, i|
334
397
  "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
335
398
  end
336
399
  sql = "DISTINCT #{columns}, "
@@ -339,32 +402,41 @@ module ::ArJdbc
339
402
 
340
403
  # ORDER BY clause for the passed order option.
341
404
  #
342
- # Uses column aliases as defined by #distinct.
405
+ # Uses column aliases as defined by {#distinct}.
343
406
  def add_order_by_for_association_limiting!(sql, options)
344
407
  return sql if options[:order].blank?
345
408
 
346
- order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
347
- order.map! {|s| $1 if s =~ / (.*)/}
348
- order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
409
+ order_columns = extract_order_columns(options[:order]) do |columns|
410
+ columns.map! { |s| $1 if s =~ / (.*)/ }; columns
411
+ end
412
+ order = order_columns.map { |s, i| "alias_#{i}__ #{s}" } # @see {#distinct}
349
413
 
350
- sql << "ORDER BY #{order}"
414
+ sql << "ORDER BY #{order.join(', ')}"
351
415
  end
352
-
416
+
417
+ def extract_order_columns(order_by)
418
+ columns = order_by.split(',')
419
+ columns.map!(&:strip); columns.reject!(&:blank?)
420
+ columns = yield(columns) if block_given?
421
+ columns.zip( (0...columns.size).to_a )
422
+ end
423
+ private :extract_order_columns
424
+
353
425
  def tables
354
426
  @connection.tables(nil, oracle_schema)
355
427
  end
356
428
 
357
- def ora_columns(table_name, name=nil)
358
- @connection.columns_internal(table_name, name, oracle_schema)
429
+ # NOTE: better to use current_schema instead of the configured one ?!
430
+
431
+ def ora_columns(table_name, name = nil)
432
+ @connection.columns_internal(table_name.to_s, name, oracle_schema)
359
433
  end
360
434
 
361
435
  # QUOTING ==================================================
362
- #
363
- # see: abstract/quoting.rb
364
436
 
365
437
  # See ACTIVERECORD_JDBC-33 for details -- better to not quote
366
438
  # table names, esp. if they have schemas.
367
- def quote_table_name(name) #:nodoc:
439
+ def quote_table_name(name) # :nodoc:
368
440
  name.to_s
369
441
  end
370
442
 
@@ -377,12 +449,8 @@ module ::ArJdbc
377
449
  def quote_column_name(name) #:nodoc:
378
450
  name.to_s =~ /^[a-z0-9_$#]+$/ ? name.to_s : "\"#{name}\""
379
451
  end
380
-
381
- def quote_string(string) #:nodoc:
382
- string.gsub(/'/, "''")
383
- end
384
-
385
- def quote(value, column = nil) #:nodoc:
452
+
453
+ def quote(value, column = nil) # :nodoc:
386
454
  # Arel 2 passes SqlLiterals through
387
455
  return value if sql_literal?(value)
388
456
 
@@ -405,19 +473,55 @@ module ::ArJdbc
405
473
  quoted
406
474
  end
407
475
  end
476
+
477
+ def supports_migrations? # :nodoc:
478
+ true
479
+ end
408
480
 
409
- def quoted_true #:nodoc:
410
- '1'
481
+ def supports_primary_key? # :nodoc:
482
+ true
411
483
  end
412
484
 
413
- def quoted_false #:nodoc:
414
- '0'
485
+ def supports_savepoints? # :nodoc:
486
+ true
487
+ end
488
+
489
+ def supports_explain? # :nodoc:
490
+ true
415
491
  end
416
492
 
493
+ def explain(arel, binds = [])
494
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel)}"
495
+ return if sql =~ /FROM all_/
496
+ exec_query(sql, 'EXPLAIN', binds)
497
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
498
+ end
499
+
500
+ def select(sql, name = nil, binds = [])
501
+ records = execute(sql, name, binds)
502
+ for column in records
503
+ column.delete('raw_rnum_')
504
+ end
505
+ records
506
+ end
507
+
417
508
  private
418
- # In Oracle, schemas are usually created under your username:
509
+
510
+ def _execute(sql, name = nil)
511
+ if self.class.select?(sql)
512
+ @connection.execute_query(sql)
513
+ else
514
+ @connection.execute_update(sql)
515
+ end
516
+ end
517
+
518
+ # In Oracle, schemas are usually created under your username :
419
519
  # http://www.oracle.com/technology/obe/2day_dba/schema/schema.htm
420
- # But allow separate configuration as "schema:" anyway (GH #53)
520
+ #
521
+ # A schema is the set of objects (tables, views, indexes, etc) that belongs
522
+ # to an user, often used as another way to refer to an Oracle user account.
523
+ #
524
+ # But allow separate configuration as "schema:" anyway (see #53)
421
525
  def oracle_schema
422
526
  if @config[:schema]
423
527
  @config[:schema].to_s
@@ -426,13 +530,16 @@ module ::ArJdbc
426
530
  end
427
531
  end
428
532
 
429
- def select(sql, name = nil, binds = [])
430
- records = execute(sql, name, binds)
431
- records.each do |col|
432
- col.delete('raw_rnum_')
433
- end
434
- records
435
- end
436
533
  end
437
534
  end
438
535
 
536
+ module ActiveRecord::ConnectionAdapters
537
+ OracleAdapter = Class.new(AbstractAdapter) unless const_defined?(:OracleAdapter)
538
+
539
+ class OracleColumn < JdbcColumn
540
+ include ArJdbc::Oracle::Column
541
+
542
+ def call_discovered_column_callbacks(*)
543
+ end
544
+ end
545
+ end