activerecord-jdbc-adapter-onsite 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +14 -0
  3. data/Appraisals +16 -0
  4. data/Gemfile +11 -0
  5. data/Gemfile.lock +45 -0
  6. data/History.txt +488 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.rdoc +214 -0
  9. data/Rakefile +62 -0
  10. data/activerecord-jdbc-adapter.gemspec +23 -0
  11. data/bench/bench_attributes.rb +13 -0
  12. data/bench/bench_attributes_new.rb +14 -0
  13. data/bench/bench_create.rb +12 -0
  14. data/bench/bench_find_all.rb +12 -0
  15. data/bench/bench_find_all_mt.rb +25 -0
  16. data/bench/bench_model.rb +85 -0
  17. data/bench/bench_new.rb +12 -0
  18. data/bench/bench_new_valid.rb +12 -0
  19. data/bench/bench_valid.rb +13 -0
  20. data/gemfiles/rails23.gemfile +10 -0
  21. data/gemfiles/rails23.gemfile.lock +38 -0
  22. data/gemfiles/rails30.gemfile +9 -0
  23. data/gemfiles/rails30.gemfile.lock +33 -0
  24. data/gemfiles/rails31.gemfile +9 -0
  25. data/gemfiles/rails31.gemfile.lock +35 -0
  26. data/gemfiles/rails32.gemfile +9 -0
  27. data/gemfiles/rails32.gemfile.lock +35 -0
  28. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  29. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  30. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  31. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  32. data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
  33. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  34. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  36. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  37. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  39. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  40. data/lib/activerecord-jdbc-adapter.rb +8 -0
  41. data/lib/arel/engines/sql/compilers/db2_compiler.rb +9 -0
  42. data/lib/arel/engines/sql/compilers/derby_compiler.rb +6 -0
  43. data/lib/arel/engines/sql/compilers/h2_compiler.rb +6 -0
  44. data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +15 -0
  45. data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +6 -0
  46. data/lib/arel/engines/sql/compilers/mssql_compiler.rb +46 -0
  47. data/lib/arel/visitors/compat.rb +13 -0
  48. data/lib/arel/visitors/db2.rb +17 -0
  49. data/lib/arel/visitors/derby.rb +32 -0
  50. data/lib/arel/visitors/firebird.rb +24 -0
  51. data/lib/arel/visitors/hsqldb.rb +26 -0
  52. data/lib/arel/visitors/sql_server.rb +46 -0
  53. data/lib/arjdbc.rb +24 -0
  54. data/lib/arjdbc/db2.rb +2 -0
  55. data/lib/arjdbc/db2/adapter.rb +541 -0
  56. data/lib/arjdbc/derby.rb +7 -0
  57. data/lib/arjdbc/derby/adapter.rb +358 -0
  58. data/lib/arjdbc/derby/connection_methods.rb +19 -0
  59. data/lib/arjdbc/discover.rb +92 -0
  60. data/lib/arjdbc/firebird.rb +2 -0
  61. data/lib/arjdbc/firebird/adapter.rb +140 -0
  62. data/lib/arjdbc/h2.rb +4 -0
  63. data/lib/arjdbc/h2/adapter.rb +54 -0
  64. data/lib/arjdbc/h2/connection_methods.rb +13 -0
  65. data/lib/arjdbc/hsqldb.rb +4 -0
  66. data/lib/arjdbc/hsqldb/adapter.rb +184 -0
  67. data/lib/arjdbc/hsqldb/connection_methods.rb +15 -0
  68. data/lib/arjdbc/informix.rb +3 -0
  69. data/lib/arjdbc/informix/adapter.rb +142 -0
  70. data/lib/arjdbc/informix/connection_methods.rb +11 -0
  71. data/lib/arjdbc/jdbc.rb +2 -0
  72. data/lib/arjdbc/jdbc/adapter.rb +356 -0
  73. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  74. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  75. data/lib/arjdbc/jdbc/callbacks.rb +44 -0
  76. data/lib/arjdbc/jdbc/column.rb +47 -0
  77. data/lib/arjdbc/jdbc/compatibility.rb +51 -0
  78. data/lib/arjdbc/jdbc/connection.rb +134 -0
  79. data/lib/arjdbc/jdbc/connection_methods.rb +16 -0
  80. data/lib/arjdbc/jdbc/core_ext.rb +24 -0
  81. data/lib/arjdbc/jdbc/discover.rb +18 -0
  82. data/lib/arjdbc/jdbc/driver.rb +35 -0
  83. data/lib/arjdbc/jdbc/extension.rb +47 -0
  84. data/lib/arjdbc/jdbc/java.rb +14 -0
  85. data/lib/arjdbc/jdbc/jdbc.rake +131 -0
  86. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +88 -0
  87. data/lib/arjdbc/jdbc/quoted_primary_key.rb +28 -0
  88. data/lib/arjdbc/jdbc/railtie.rb +9 -0
  89. data/lib/arjdbc/jdbc/rake_tasks.rb +10 -0
  90. data/lib/arjdbc/jdbc/require_driver.rb +16 -0
  91. data/lib/arjdbc/jdbc/type_converter.rb +126 -0
  92. data/lib/arjdbc/mimer.rb +2 -0
  93. data/lib/arjdbc/mimer/adapter.rb +142 -0
  94. data/lib/arjdbc/mssql.rb +4 -0
  95. data/lib/arjdbc/mssql/adapter.rb +477 -0
  96. data/lib/arjdbc/mssql/connection_methods.rb +31 -0
  97. data/lib/arjdbc/mssql/limit_helpers.rb +101 -0
  98. data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
  99. data/lib/arjdbc/mssql/tsql_helper.rb +61 -0
  100. data/lib/arjdbc/mysql.rb +4 -0
  101. data/lib/arjdbc/mysql/adapter.rb +505 -0
  102. data/lib/arjdbc/mysql/connection_methods.rb +28 -0
  103. data/lib/arjdbc/oracle.rb +3 -0
  104. data/lib/arjdbc/oracle/adapter.rb +432 -0
  105. data/lib/arjdbc/oracle/connection_methods.rb +12 -0
  106. data/lib/arjdbc/postgresql.rb +4 -0
  107. data/lib/arjdbc/postgresql/adapter.rb +861 -0
  108. data/lib/arjdbc/postgresql/connection_methods.rb +23 -0
  109. data/lib/arjdbc/sqlite3.rb +4 -0
  110. data/lib/arjdbc/sqlite3/adapter.rb +389 -0
  111. data/lib/arjdbc/sqlite3/connection_methods.rb +35 -0
  112. data/lib/arjdbc/sybase.rb +2 -0
  113. data/lib/arjdbc/sybase/adapter.rb +46 -0
  114. data/lib/arjdbc/version.rb +8 -0
  115. data/lib/generators/jdbc/USAGE +10 -0
  116. data/lib/generators/jdbc/jdbc_generator.rb +9 -0
  117. data/lib/jdbc_adapter.rb +2 -0
  118. data/lib/jdbc_adapter/rake_tasks.rb +3 -0
  119. data/lib/jdbc_adapter/version.rb +3 -0
  120. data/lib/pg.rb +26 -0
  121. data/pom.xml +57 -0
  122. data/rails_generators/jdbc_generator.rb +15 -0
  123. data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
  124. data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
  125. data/rakelib/bundler_ext.rb +11 -0
  126. data/rakelib/compile.rake +23 -0
  127. data/rakelib/db.rake +39 -0
  128. data/rakelib/rails.rake +41 -0
  129. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +69 -0
  130. data/src/java/arjdbc/derby/DerbyModule.java +324 -0
  131. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +70 -0
  132. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +74 -0
  133. data/src/java/arjdbc/jdbc/AdapterJavaService.java +68 -0
  134. data/src/java/arjdbc/jdbc/JdbcConnectionFactory.java +36 -0
  135. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1346 -0
  136. data/src/java/arjdbc/jdbc/SQLBlock.java +48 -0
  137. data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +127 -0
  138. data/src/java/arjdbc/mysql/MySQLModule.java +134 -0
  139. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +161 -0
  140. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +85 -0
  141. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +82 -0
  142. data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +126 -0
  143. data/test/abstract_db_create.rb +135 -0
  144. data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
  145. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
  146. data/test/activerecord/jall.sh +7 -0
  147. data/test/activerecord/jtest.sh +3 -0
  148. data/test/db/db2.rb +11 -0
  149. data/test/db/derby.rb +12 -0
  150. data/test/db/h2.rb +11 -0
  151. data/test/db/hsqldb.rb +13 -0
  152. data/test/db/informix.rb +11 -0
  153. data/test/db/jdbc.rb +12 -0
  154. data/test/db/jndi_config.rb +40 -0
  155. data/test/db/logger.rb +3 -0
  156. data/test/db/mssql.rb +9 -0
  157. data/test/db/mysql.rb +10 -0
  158. data/test/db/oracle.rb +34 -0
  159. data/test/db/postgres.rb +18 -0
  160. data/test/db/sqlite3.rb +11 -0
  161. data/test/db2_reset_column_information_test.rb +8 -0
  162. data/test/db2_simple_test.rb +66 -0
  163. data/test/derby_migration_test.rb +68 -0
  164. data/test/derby_multibyte_test.rb +12 -0
  165. data/test/derby_reset_column_information_test.rb +8 -0
  166. data/test/derby_row_locking_test.rb +9 -0
  167. data/test/derby_simple_test.rb +139 -0
  168. data/test/generic_jdbc_connection_test.rb +29 -0
  169. data/test/h2_change_column_test.rb +68 -0
  170. data/test/h2_simple_test.rb +41 -0
  171. data/test/has_many_through.rb +79 -0
  172. data/test/helper.rb +108 -0
  173. data/test/hsqldb_simple_test.rb +6 -0
  174. data/test/informix_simple_test.rb +48 -0
  175. data/test/jdbc_common.rb +28 -0
  176. data/test/jndi_callbacks_test.rb +36 -0
  177. data/test/jndi_test.rb +25 -0
  178. data/test/manualTestDatabase.rb +191 -0
  179. data/test/models/add_not_null_column_to_table.rb +9 -0
  180. data/test/models/auto_id.rb +15 -0
  181. data/test/models/custom_pk_name.rb +14 -0
  182. data/test/models/data_types.rb +30 -0
  183. data/test/models/entry.rb +40 -0
  184. data/test/models/mixed_case.rb +22 -0
  185. data/test/models/reserved_word.rb +15 -0
  186. data/test/models/string_id.rb +17 -0
  187. data/test/models/thing.rb +16 -0
  188. data/test/models/validates_uniqueness_of_string.rb +19 -0
  189. data/test/mssql_db_create_test.rb +26 -0
  190. data/test/mssql_identity_insert_test.rb +19 -0
  191. data/test/mssql_ignore_system_views_test.rb +27 -0
  192. data/test/mssql_legacy_types_test.rb +58 -0
  193. data/test/mssql_limit_offset_test.rb +136 -0
  194. data/test/mssql_multibyte_test.rb +18 -0
  195. data/test/mssql_null_test.rb +14 -0
  196. data/test/mssql_reset_column_information_test.rb +8 -0
  197. data/test/mssql_row_locking_sql_test.rb +159 -0
  198. data/test/mssql_row_locking_test.rb +9 -0
  199. data/test/mssql_simple_test.rb +55 -0
  200. data/test/mysql_db_create_test.rb +27 -0
  201. data/test/mysql_index_length_test.rb +58 -0
  202. data/test/mysql_info_test.rb +123 -0
  203. data/test/mysql_multibyte_test.rb +10 -0
  204. data/test/mysql_nonstandard_primary_key_test.rb +42 -0
  205. data/test/mysql_reset_column_information_test.rb +8 -0
  206. data/test/mysql_simple_test.rb +125 -0
  207. data/test/oracle_reset_column_information_test.rb +8 -0
  208. data/test/oracle_simple_test.rb +18 -0
  209. data/test/oracle_specific_test.rb +83 -0
  210. data/test/postgres_db_create_test.rb +32 -0
  211. data/test/postgres_drop_db_test.rb +16 -0
  212. data/test/postgres_information_schema_leak_test.rb +29 -0
  213. data/test/postgres_mixed_case_test.rb +29 -0
  214. data/test/postgres_native_type_mapping_test.rb +93 -0
  215. data/test/postgres_nonseq_pkey_test.rb +38 -0
  216. data/test/postgres_reserved_test.rb +22 -0
  217. data/test/postgres_reset_column_information_test.rb +8 -0
  218. data/test/postgres_schema_search_path_test.rb +48 -0
  219. data/test/postgres_simple_test.rb +168 -0
  220. data/test/postgres_table_alias_length_test.rb +15 -0
  221. data/test/postgres_type_conversion_test.rb +34 -0
  222. data/test/row_locking.rb +90 -0
  223. data/test/simple.rb +731 -0
  224. data/test/sqlite3_reset_column_information_test.rb +8 -0
  225. data/test/sqlite3_simple_test.rb +316 -0
  226. data/test/sybase_jtds_simple_test.rb +28 -0
  227. data/test/sybase_reset_column_information_test.rb +8 -0
  228. metadata +288 -0
@@ -0,0 +1,88 @@
1
+ module ArJdbc
2
+ module MissingFunctionalityHelper
3
+ #Taken from SQLite adapter
4
+
5
+ def alter_table(table_name, options = {}) #:nodoc:
6
+ table_name = table_name.to_s.downcase
7
+ altered_table_name = "altered_#{table_name}"
8
+ caller = lambda {|definition| yield definition if block_given?}
9
+
10
+ transaction do
11
+ # A temporary table might improve performance here, but
12
+ # it doesn't seem to maintain indices across the whole move.
13
+ move_table(table_name, altered_table_name,
14
+ options)
15
+ move_table(altered_table_name, table_name, &caller)
16
+ end
17
+ end
18
+
19
+ def move_table(from, to, options = {}, &block) #:nodoc:
20
+ copy_table(from, to, options, &block)
21
+ drop_table(from)
22
+ end
23
+
24
+ def copy_table(from, to, options = {}) #:nodoc:
25
+ options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
26
+ create_table(to, options) do |definition|
27
+ @definition = definition
28
+ columns(from).each do |column|
29
+ column_name = options[:rename] ?
30
+ (options[:rename][column.name] ||
31
+ options[:rename][column.name.to_sym] ||
32
+ column.name) : column.name
33
+
34
+ @definition.column(column_name, column.type,
35
+ :limit => column.limit, :default => column.default,
36
+ :precision => column.precision, :scale => column.scale,
37
+ :null => column.null)
38
+ end
39
+ @definition.primary_key(primary_key(from)) if primary_key(from)
40
+ yield @definition if block_given?
41
+ end
42
+
43
+ copy_table_indexes(from, to, options[:rename] || {})
44
+ copy_table_contents(from, to,
45
+ @definition.columns.map {|column| column.name},
46
+ options[:rename] || {})
47
+ end
48
+
49
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
50
+ indexes(from).each do |index|
51
+ name = index.name.downcase
52
+ if to == "altered_#{from}"
53
+ name = "temp_#{name}"
54
+ elsif from == "altered_#{to}"
55
+ name = name[5..-1]
56
+ end
57
+
58
+ to_column_names = columns(to).map(&:name)
59
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
60
+ to_column_names.include?(column)
61
+ end
62
+
63
+ unless columns.empty?
64
+ # index name can't be the same
65
+ opts = { :name => name.gsub(/(_?)(#{from})_/, "\\1#{to}_") }
66
+ opts[:unique] = true if index.unique
67
+ add_index(to, columns, opts)
68
+ end
69
+ end
70
+ end
71
+
72
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
73
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
74
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
75
+ from_columns = columns(from).collect {|col| col.name}
76
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
77
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
78
+
79
+ quoted_to = quote_table_name(to)
80
+ execute("SELECT * FROM #{quote_table_name(from)}").each do |row|
81
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
82
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
83
+ sql << ')'
84
+ execute sql
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ module ArJdbc
2
+ module QuotedPrimaryKeyExtension
3
+ def self.extended(base)
4
+ # Rails 3 method Rails 2 method
5
+ meth = [:arel_attributes_values, :attributes_with_quotes].detect do |m|
6
+ base.private_instance_methods.include?(m.to_s)
7
+ end
8
+ pk_hash_key = "self.class.primary_key"
9
+ pk_hash_value = '"?"'
10
+ if meth == :arel_attributes_values
11
+ pk_hash_key = "self.class.arel_table[#{pk_hash_key}]"
12
+ pk_hash_value = "Arel::SqlLiteral.new(#{pk_hash_value})"
13
+ end
14
+ if meth
15
+ base.module_eval <<-PK, __FILE__, __LINE__
16
+ alias :#{meth}_pre_pk :#{meth}
17
+ def #{meth}(include_primary_key = true, *args) #:nodoc:
18
+ aq = #{meth}_pre_pk(include_primary_key, *args)
19
+ if connection.is_a?(ArJdbc::Oracle) || connection.is_a?(ArJdbc::Mimer)
20
+ aq[#{pk_hash_key}] = #{pk_hash_value} if include_primary_key && aq[#{pk_hash_key}].nil?
21
+ end
22
+ aq
23
+ end
24
+ PK
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails/railtie'
2
+
3
+ module ::ArJdbc
4
+ class Railtie < ::Rails::Railtie
5
+ rake_tasks do
6
+ load File.expand_path('../rake_tasks.rb', __FILE__)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ if defined?(Rake.application) && Rake.application && ENV["SKIP_AR_JDBC_RAKE_REDEFINES"].nil?
2
+ jdbc_rakefile = File.dirname(__FILE__) + "/jdbc.rake"
3
+ if Rake.application.lookup("db:create")
4
+ # rails tasks already defined; load the override tasks now
5
+ load jdbc_rakefile
6
+ else
7
+ # rails tasks not loaded yet; load as an import
8
+ Rake.application.add_import(jdbc_rakefile)
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module Kernel
2
+ # load a JDBC driver library/gem, failing silently. If failed, trust
3
+ # that the driver jar is already present through some other means
4
+ def jdbc_require_driver(path, gem_name = nil)
5
+ gem_name ||= path.sub('/', '-')
6
+ 2.times do
7
+ begin
8
+ require path
9
+ break
10
+ rescue LoadError
11
+ require 'rubygems'
12
+ begin; gem gem_name; rescue LoadError; end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,126 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ # I want to use JDBC's DatabaseMetaData#getTypeInfo to choose the best native types to
4
+ # use for ActiveRecord's Adapter#native_database_types in a database-independent way,
5
+ # but apparently a database driver can return multiple types for a given
6
+ # java.sql.Types constant. So this type converter uses some heuristics to try to pick
7
+ # the best (most common) type to use. It's not great, it would be better to just
8
+ # delegate to each database's existin AR adapter's native_database_types method, but I
9
+ # wanted to try to do this in a way that didn't pull in all the other adapters as
10
+ # dependencies. Suggestions appreciated.
11
+ class JdbcTypeConverter
12
+ # The basic ActiveRecord types, mapped to an array of procs that are used to #select
13
+ # the best type. The procs are used as selectors in order until there is only one
14
+ # type left. If all the selectors are applied and there is still more than one
15
+ # type, an exception will be raised.
16
+ AR_TO_JDBC_TYPES = {
17
+ :string => [ lambda {|r| Jdbc::Types::VARCHAR == r['data_type'].to_i},
18
+ lambda {|r| r['type_name'] =~ /^varchar/i},
19
+ lambda {|r| r['type_name'] =~ /^varchar$/i},
20
+ lambda {|r| r['type_name'] =~ /varying/i}],
21
+ :text => [ lambda {|r| [Jdbc::Types::LONGVARCHAR, Jdbc::Types::CLOB].include?(r['data_type'].to_i)},
22
+ lambda {|r| r['type_name'] =~ /^text$/i}, # For Informix
23
+ lambda {|r| r['type_name'] =~ /sub_type 1$/i}, # For FireBird
24
+ lambda {|r| r['type_name'] =~ /^(text|clob)$/i},
25
+ lambda {|r| r['type_name'] =~ /^character large object$/i},
26
+ lambda {|r| r['sql_data_type'] == 2005}],
27
+ :integer => [ lambda {|r| Jdbc::Types::INTEGER == r['data_type'].to_i},
28
+ lambda {|r| r['type_name'] =~ /^integer$/i},
29
+ lambda {|r| r['type_name'] =~ /^int4$/i},
30
+ lambda {|r| r['type_name'] =~ /^int$/i}],
31
+ :decimal => [ lambda {|r| Jdbc::Types::DECIMAL == r['data_type'].to_i},
32
+ lambda {|r| r['type_name'] =~ /^decimal$/i},
33
+ lambda {|r| r['type_name'] =~ /^numeric$/i},
34
+ lambda {|r| r['type_name'] =~ /^number$/i},
35
+ lambda {|r| r['type_name'] =~ /^real$/i},
36
+ lambda {|r| r['precision'] == '38'},
37
+ lambda {|r| r['data_type'].to_i == Jdbc::Types::DECIMAL}],
38
+ :float => [ lambda {|r| [Jdbc::Types::FLOAT,Jdbc::Types::DOUBLE, Jdbc::Types::REAL].include?(r['data_type'].to_i)},
39
+ lambda {|r| r['data_type'].to_i == Jdbc::Types::REAL}, #Prefer REAL to DOUBLE for Postgresql
40
+ lambda {|r| r['type_name'] =~ /^float/i},
41
+ lambda {|r| r['type_name'] =~ /^double$/i},
42
+ lambda {|r| r['type_name'] =~ /^real$/i},
43
+ lambda {|r| r['precision'] == '15'}],
44
+ :datetime => [ lambda {|r| Jdbc::Types::TIMESTAMP == r['data_type'].to_i},
45
+ lambda {|r| r['type_name'] =~ /^datetime$/i},
46
+ lambda {|r| r['type_name'] =~ /^timestamp$/i},
47
+ lambda {|r| r['type_name'] =~ /^date/i},
48
+ lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver
49
+ :timestamp => [ lambda {|r| Jdbc::Types::TIMESTAMP == r['data_type'].to_i},
50
+ lambda {|r| r['type_name'] =~ /^timestamp$/i},
51
+ lambda {|r| r['type_name'] =~ /^datetime/i},
52
+ lambda {|r| r['type_name'] =~ /^date/i},
53
+ lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver
54
+ :time => [ lambda {|r| Jdbc::Types::TIME == r['data_type'].to_i},
55
+ lambda {|r| r['type_name'] =~ /^time$/i},
56
+ lambda {|r| r['type_name'] =~ /^datetime/i}, # For Informix
57
+ lambda {|r| r['type_name'] =~ /^date/i},
58
+ lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver
59
+ :date => [ lambda {|r| Jdbc::Types::DATE == r['data_type'].to_i},
60
+ lambda {|r| r['type_name'] =~ /^date$/i},
61
+ lambda {|r| r['type_name'] =~ /^date/i},
62
+ lambda {|r| r['type_name'] =~ /^integer/i}], #Num of milliseconds for SQLite3 JDBC Driver3
63
+ :binary => [ lambda {|r| [Jdbc::Types::LONGVARBINARY,Jdbc::Types::BINARY,Jdbc::Types::BLOB].include?(r['data_type'].to_i)},
64
+ lambda {|r| r['type_name'] =~ /^blob/i},
65
+ lambda {|r| r['type_name'] =~ /sub_type 0$/i}, # For FireBird
66
+ lambda {|r| r['type_name'] =~ /^varbinary$/i}, # We want this sucker for Mimer
67
+ lambda {|r| r['type_name'] =~ /^binary$/i}, ],
68
+ :boolean => [ lambda {|r| [Jdbc::Types::TINYINT].include?(r['data_type'].to_i)},
69
+ lambda {|r| r['type_name'] =~ /^bool/i},
70
+ lambda {|r| r['data_type'].to_i == Jdbc::Types::BIT},
71
+ lambda {|r| r['type_name'] =~ /^tinyint$/i},
72
+ lambda {|r| r['type_name'] =~ /^decimal$/i},
73
+ lambda {|r| r['type_name'] =~ /^integer$/i}]
74
+ }
75
+
76
+ def initialize(types)
77
+ @types = types
78
+ @types.each {|t| t['type_name'] ||= t['local_type_name']} # Sybase driver seems to want 'local_type_name'
79
+ end
80
+
81
+ def choose_best_types
82
+ type_map = {}
83
+ @types.each do |row|
84
+ name = row['type_name'].downcase
85
+ k = name.to_sym
86
+ type_map[k] = { :name => name }
87
+ set_limit_to_nonzero_precision(type_map[k], row)
88
+ end
89
+
90
+ AR_TO_JDBC_TYPES.keys.each do |k|
91
+ typerow = choose_type(k)
92
+ type_map[k] = { :name => typerow['type_name'].downcase }
93
+ case k
94
+ when :integer, :string, :decimal
95
+ set_limit_to_nonzero_precision(type_map[k], typerow)
96
+ when :boolean
97
+ type_map[k][:limit] = 1
98
+ end
99
+ end
100
+ type_map
101
+ end
102
+
103
+ def choose_type(ar_type)
104
+ procs = AR_TO_JDBC_TYPES[ar_type]
105
+ types = @types
106
+ procs.each do |p|
107
+ new_types = types.reject {|r| r["data_type"].to_i == Jdbc::Types::OTHER}
108
+ new_types = new_types.select(&p)
109
+ new_types = new_types.inject([]) do |typs,t|
110
+ typs << t unless typs.detect {|el| el['type_name'] == t['type_name']}
111
+ typs
112
+ end
113
+ return new_types.first if new_types.length == 1
114
+ types = new_types if new_types.length > 0
115
+ end
116
+ raise "unable to choose type for #{ar_type} from:\n#{types.collect{|t| t['type_name']}.inspect}"
117
+ end
118
+
119
+ def set_limit_to_nonzero_precision(map, row)
120
+ if row['precision'] && row['precision'].to_i > 0
121
+ map[:limit] = row['precision'].to_i
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,2 @@
1
+ require 'arjdbc/jdbc'
2
+ require 'arjdbc/mimer/adapter'
@@ -0,0 +1,142 @@
1
+ module ArJdbc
2
+ module Mimer
3
+ def self.extended(mod)
4
+ require 'arjdbc/jdbc/quoted_primary_key'
5
+ ActiveRecord::Base.extend ArJdbc::QuotedPrimaryKeyExtension
6
+ end
7
+
8
+ def modify_types(tp)
9
+ tp[:primary_key] = "INTEGER NOT NULL PRIMARY KEY"
10
+ tp[:boolean][:limit] = nil
11
+ tp[:string][:limit] = 255
12
+ tp[:binary] = {:name => "BINARY VARYING", :limit => 4096}
13
+ tp[:text] = {:name => "VARCHAR", :limit => 4096}
14
+ tp[:datetime] = { :name => "TIMESTAMP" }
15
+ tp[:timestamp] = { :name => "TIMESTAMP" }
16
+ tp[:time] = { :name => "TIMESTAMP" }
17
+ tp[:date] = { :name => "TIMESTAMP" }
18
+ tp
19
+ end
20
+
21
+ def default_sequence_name(table, column) #:nodoc:
22
+ "#{table}_seq"
23
+ end
24
+
25
+ def create_table(name, options = {}) #:nodoc:
26
+ super(name, options)
27
+ execute "CREATE SEQUENCE #{name}_seq" unless options[:id] == false
28
+ end
29
+
30
+ def drop_table(name, options = {}) #:nodoc:
31
+ super(name) rescue nil
32
+ execute "DROP SEQUENCE #{name}_seq" rescue nil
33
+ end
34
+
35
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
36
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"
37
+ end
38
+
39
+ def change_column_default(table_name, column_name, default) #:nodoc:
40
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{quote(default)}"
41
+ end
42
+
43
+ def remove_index(table_name, options = {}) #:nodoc:
44
+ execute "DROP INDEX #{index_name(table_name, options)}"
45
+ end
46
+
47
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) #:nodoc:
48
+ if pk.nil? # Who called us? What does the sql look like? No idea!
49
+ execute sql, name, binds
50
+ elsif id_value # Pre-assigned id
51
+ log(sql, name) { @connection.execute_insert sql,pk }
52
+ else # Assume the sql contains a bind-variable for the id
53
+ id_value = select_one("SELECT NEXT_VALUE OF #{sequence_name} AS val FROM MIMER.ONEROW")['val']
54
+ log(sql, name) {
55
+ execute_prepared_insert(sql,id_value)
56
+ }
57
+ end
58
+ id_value
59
+ end
60
+
61
+ def execute_prepared_insert(sql, id)
62
+ @stmts ||= {}
63
+ @stmts[sql] ||= @connection.ps(sql)
64
+ stmt = @stmts[sql]
65
+ stmt.setLong(1,id)
66
+ stmt.executeUpdate
67
+ id
68
+ end
69
+
70
+ def quote(value, column = nil) #:nodoc:
71
+ return value.quoted_id if value.respond_to?(:quoted_id)
72
+
73
+ if String === value && column && column.type == :binary
74
+ return "X'#{quote_string(value.unpack("C*").collect {|v| v.to_s(16)}.join)}'"
75
+ end
76
+ case value
77
+ when String
78
+ %Q{'#{quote_string(value)}'}
79
+ when NilClass
80
+ 'NULL'
81
+ when TrueClass
82
+ '1'
83
+ when FalseClass
84
+ '0'
85
+ when Numeric
86
+ value.to_s
87
+ when Date, Time
88
+ %Q{TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
89
+ else
90
+ %Q{'#{quote_string(value.to_yaml)}'}
91
+ end
92
+ end
93
+
94
+ def quoted_true
95
+ '1'
96
+ end
97
+
98
+ def quoted_false
99
+ '0'
100
+ end
101
+
102
+ def add_limit_offset!(sql, options) # :nodoc:
103
+ @limit = options[:limit]
104
+ @offset = options[:offset]
105
+ end
106
+
107
+ def select_all(sql, name = nil, binds = [])
108
+ @offset ||= 0
109
+ if !@limit || @limit == -1
110
+ range = @offset..-1
111
+ else
112
+ range = @offset...(@offset+@limit)
113
+ end
114
+ select(sql, name, binds)[range]
115
+ ensure
116
+ @limit = @offset = nil
117
+ end
118
+
119
+ def select_one(sql, name = nil)
120
+ @offset ||= 0
121
+ select(sql, name)[@offset]
122
+ ensure
123
+ @limit = @offset = nil
124
+ end
125
+
126
+ def _execute(sql, name = nil)
127
+ if sql =~ /^select/i
128
+ @offset ||= 0
129
+ if !@limit || @limit == -1
130
+ range = @offset..-1
131
+ else
132
+ range = @offset...(@offset+@limit)
133
+ end
134
+ @connection.execute_query(sql)[range]
135
+ else
136
+ @connection.execute_update(sql)
137
+ end
138
+ ensure
139
+ @limit = @offset = nil
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,4 @@
1
+ require 'arjdbc/jdbc'
2
+ jdbc_require_driver 'jdbc/jtds', 'jdbc-mssql'
3
+ require 'arjdbc/mssql/connection_methods'
4
+ require 'arjdbc/mssql/adapter'
@@ -0,0 +1,477 @@
1
+ require 'arjdbc/mssql/tsql_helper'
2
+ require 'arjdbc/mssql/limit_helpers'
3
+ require 'arjdbc/mssql/lock_helpers'
4
+ require 'strscan'
5
+
6
+ module ::ArJdbc
7
+ module MsSQL
8
+ include TSqlMethods
9
+ include LimitHelpers
10
+
11
+ def self.extended(mod)
12
+ unless defined?(@lob_callback_added)
13
+ ActiveRecord::Base.class_eval do
14
+ def after_save_with_mssql_lob
15
+ self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
16
+ value = self[c.name]
17
+ if coder = self.class.serialized_attributes[c.name]
18
+ if coder.respond_to?(:dump)
19
+ value = coder.dump(value)
20
+ else
21
+ value = value.to_yaml
22
+ end
23
+ end
24
+ next if value.nil? || (value == '')
25
+
26
+ connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
27
+ end
28
+ end
29
+ end
30
+
31
+ ActiveRecord::Base.after_save :after_save_with_mssql_lob
32
+ @lob_callback_added = true
33
+ end
34
+ mod.add_version_specific_add_limit_offset
35
+ end
36
+
37
+ def self.column_selector
38
+ [/sqlserver|tds|Microsoft SQL/i, lambda {|cfg,col| col.extend(::ArJdbc::MsSQL::Column)}]
39
+ end
40
+
41
+ def self.jdbc_connection_class
42
+ ::ActiveRecord::ConnectionAdapters::MssqlJdbcConnection
43
+ end
44
+
45
+ def self.arel2_visitors(config)
46
+ require 'arel/visitors/sql_server'
47
+ visitor_class = config[:sqlserver_version] == "2000" ? ::Arel::Visitors::SQLServer2000 : ::Arel::Visitors::SQLServer
48
+ {}.tap {|v| %w(mssql sqlserver jdbcmssql).each {|x| v[x] = visitor_class } }
49
+ end
50
+
51
+ def sqlserver_version
52
+ @sqlserver_version ||= select_value("select @@version")[/Microsoft SQL Server\s+(\d{4})/, 1]
53
+ end
54
+
55
+ def add_version_specific_add_limit_offset
56
+ config[:sqlserver_version] = version = sqlserver_version
57
+ if version == "2000"
58
+ extend LimitHelpers::SqlServer2000AddLimitOffset
59
+ else
60
+ extend LimitHelpers::SqlServerAddLimitOffset
61
+ end
62
+ end
63
+
64
+ def modify_types(tp) #:nodoc:
65
+ super(tp)
66
+ tp[:string] = {:name => "NVARCHAR", :limit => 255}
67
+ if sqlserver_version == "2000"
68
+ tp[:text] = {:name => "NTEXT"}
69
+ else
70
+ tp[:text] = {:name => "NVARCHAR(MAX)"}
71
+ end
72
+ tp
73
+ end
74
+
75
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
76
+ # MSSQL's NVARCHAR(n | max) column supports either a number between 1 and
77
+ # 4000, or the word "MAX", which corresponds to 2**30-1 UCS-2 characters.
78
+ #
79
+ # It does not accept NVARCHAR(1073741823) here, so we have to change it
80
+ # to NVARCHAR(MAX), even though they are logically equivalent.
81
+ #
82
+ # MSSQL Server 2000 is skipped here because I don't know how it will behave.
83
+ #
84
+ # See: http://msdn.microsoft.com/en-us/library/ms186939.aspx
85
+ if type.to_s == 'string' and limit == 1073741823 and sqlserver_version != "2000"
86
+ 'NVARCHAR(MAX)'
87
+ elsif %w( boolean date datetime ).include?(type.to_s)
88
+ super(type) # cannot specify limit/precision/scale with these types
89
+ else
90
+ super
91
+ end
92
+ end
93
+
94
+ module Column
95
+ include LockHelpers::SqlServerAddLock
96
+
97
+ attr_accessor :identity, :is_special
98
+
99
+ def simplified_type(field_type)
100
+ case field_type
101
+ when /int|bigint|smallint|tinyint/i then :integer
102
+ when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
103
+ when /float|double|money|real|smallmoney/i then :decimal
104
+ when /datetime|smalldatetime/i then :datetime
105
+ when /timestamp/i then :timestamp
106
+ when /time/i then :time
107
+ when /date/i then :date
108
+ when /text|ntext|xml/i then :text
109
+ when /binary|image|varbinary/i then :binary
110
+ when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
111
+ when /bit/i then :boolean
112
+ when /uniqueidentifier/i then :string
113
+ else
114
+ super
115
+ end
116
+ end
117
+
118
+ def default_value(value)
119
+ return $1 if value =~ /^\(N?'(.*)'\)$/
120
+ value
121
+ end
122
+
123
+ def type_cast(value)
124
+ return nil if value.nil?
125
+ case type
126
+ when :integer then value.delete('()').to_i rescue unquote(value).to_i rescue value ? 1 : 0
127
+ when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
128
+ when :decimal then self.class.value_to_decimal(unquote(value))
129
+ when :datetime then cast_to_datetime(value)
130
+ when :timestamp then cast_to_time(value)
131
+ when :time then cast_to_time(value)
132
+ when :date then cast_to_date(value)
133
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or unquote(value)=="1"
134
+ when :binary then unquote value
135
+ else value
136
+ end
137
+ end
138
+
139
+ def extract_limit(sql_type)
140
+ case sql_type
141
+ when /text|ntext|xml|binary|image|varbinary|bit/
142
+ nil
143
+ else
144
+ super
145
+ end
146
+ end
147
+
148
+ def is_utf8?
149
+ sql_type =~ /nvarchar|ntext|nchar/i
150
+ end
151
+
152
+ def unquote(value)
153
+ value.to_s.sub(/\A\([\(\']?/, "").sub(/[\'\)]?\)\Z/, "")
154
+ end
155
+
156
+ def cast_to_time(value)
157
+ return value if value.is_a?(Time)
158
+ DateTime.parse(value).to_time rescue nil
159
+ end
160
+
161
+ def cast_to_date(value)
162
+ return value if value.is_a?(Date)
163
+ return Date.parse(value) rescue nil
164
+ end
165
+
166
+ def cast_to_datetime(value)
167
+ if value.is_a?(Time)
168
+ if value.year != 0 and value.month != 0 and value.day != 0
169
+ return value
170
+ else
171
+ return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
172
+ end
173
+ end
174
+ if value.is_a?(DateTime)
175
+ begin
176
+ # Attempt to convert back to a Time, but it could fail for dates significantly in the past/future.
177
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
178
+ rescue ArgumentError
179
+ return value
180
+ end
181
+ end
182
+
183
+ return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
184
+
185
+ return value.is_a?(Date) ? value : nil
186
+ end
187
+
188
+ # These methods will only allow the adapter to insert binary data with a length of 7K or less
189
+ # because of a SQL Server statement length policy.
190
+ def self.string_to_binary(value)
191
+ ''
192
+ end
193
+
194
+ end
195
+
196
+ def quote(value, column = nil)
197
+ return value.quoted_id if value.respond_to?(:quoted_id)
198
+
199
+ case value
200
+ # SQL Server 2000 doesn't let you insert an integer into a NVARCHAR
201
+ # column, so we include Integer here.
202
+ when String, ActiveSupport::Multibyte::Chars, Integer
203
+ value = value.to_s
204
+ if column && column.type == :binary
205
+ "'#{quote_string(ArJdbc::MsSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
206
+ elsif column && [:integer, :float].include?(column.type)
207
+ value = column.type == :integer ? value.to_i : value.to_f
208
+ value.to_s
209
+ elsif !column.respond_to?(:is_utf8?) || column.is_utf8?
210
+ "N'#{quote_string(value)}'" # ' (for ruby-mode)
211
+ else
212
+ super
213
+ end
214
+ when TrueClass then '1'
215
+ when FalseClass then '0'
216
+ else super
217
+ end
218
+ end
219
+
220
+ def quote_string(string)
221
+ string.gsub(/\'/, "''")
222
+ end
223
+
224
+ def quote_table_name(name)
225
+ quote_column_name(name)
226
+ end
227
+
228
+ def quote_column_name(name)
229
+ "[#{name}]"
230
+ end
231
+
232
+ def quoted_true
233
+ quote true
234
+ end
235
+
236
+ def quoted_false
237
+ quote false
238
+ end
239
+
240
+ def adapter_name #:nodoc:
241
+ 'MsSQL'
242
+ end
243
+
244
+ def change_order_direction(order)
245
+ order.split(",").collect do |fragment|
246
+ case fragment
247
+ when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
248
+ when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
249
+ else String.new(fragment).split(',').join(' DESC,') + ' DESC'
250
+ end
251
+ end.join(",")
252
+ end
253
+
254
+ def supports_ddl_transactions?
255
+ true
256
+ end
257
+
258
+ def recreate_database(name)
259
+ drop_database(name)
260
+ create_database(name)
261
+ end
262
+
263
+ def drop_database(name)
264
+ execute "USE master"
265
+ execute "DROP DATABASE #{name}"
266
+ end
267
+
268
+ def create_database(name)
269
+ execute "CREATE DATABASE #{name}"
270
+ execute "USE #{name}"
271
+ end
272
+
273
+ def rename_table(name, new_name)
274
+ clear_cached_table(name)
275
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
276
+ end
277
+
278
+ # Adds a new column to the named table.
279
+ # See TableDefinition#column for details of the options you can use.
280
+ def add_column(table_name, column_name, type, options = {})
281
+ clear_cached_table(table_name)
282
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
283
+ add_column_options!(add_column_sql, options)
284
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
285
+ # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
286
+ execute(add_column_sql)
287
+ end
288
+
289
+ def rename_column(table, column, new_column_name)
290
+ clear_cached_table(table)
291
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
292
+ end
293
+
294
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
295
+ clear_cached_table(table_name)
296
+ change_column_type(table_name, column_name, type, options)
297
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
298
+ end
299
+
300
+ def change_column_type(table_name, column_name, type, options = {}) #:nodoc:
301
+ clear_cached_table(table_name)
302
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
303
+ if options.has_key?(:null)
304
+ sql += (options[:null] ? " NULL" : " NOT NULL")
305
+ end
306
+ execute(sql)
307
+ end
308
+
309
+ def change_column_default(table_name, column_name, default) #:nodoc:
310
+ clear_cached_table(table_name)
311
+ remove_default_constraint(table_name, column_name)
312
+ unless default.nil?
313
+ execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
314
+ end
315
+ end
316
+
317
+ def remove_column(table_name, column_name)
318
+ clear_cached_table(table_name)
319
+ remove_check_constraints(table_name, column_name)
320
+ remove_default_constraint(table_name, column_name)
321
+ execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
322
+ end
323
+
324
+ def remove_default_constraint(table_name, column_name)
325
+ clear_cached_table(table_name)
326
+ defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
327
+ defaults.each {|constraint|
328
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
329
+ }
330
+ end
331
+
332
+ def remove_check_constraints(table_name, column_name)
333
+ clear_cached_table(table_name)
334
+ # TODO remove all constraints in single method
335
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
336
+ constraints.each do |constraint|
337
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
338
+ end
339
+ end
340
+
341
+ def remove_index(table_name, options = {})
342
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
343
+ end
344
+
345
+ def columns(table_name, name = nil)
346
+ # It's possible for table_name to be an empty string, or nil, if something attempts to issue SQL
347
+ # which doesn't involve a table. IE. "SELECT 1" or "SELECT * from someFunction()".
348
+ return [] if table_name.blank?
349
+ table_name = table_name.to_s if table_name.is_a?(Symbol)
350
+
351
+ # Remove []'s from around the table name, valid in a select statement, but not when matching metadata.
352
+ table_name = table_name.gsub(/[\[\]]/, '')
353
+
354
+ return [] if table_name =~ /^information_schema\./i
355
+ @table_columns ||= {}
356
+ unless @table_columns[table_name]
357
+ @table_columns[table_name] = super
358
+ @table_columns[table_name].each do |col|
359
+ col.identity = true if col.sql_type =~ /identity/i
360
+ col.is_special = true if col.sql_type =~ /text|ntext|image|xml/i
361
+ end
362
+ end
363
+ @table_columns[table_name]
364
+ end
365
+
366
+ def _execute(sql, name = nil)
367
+ # Match the start of the sql to determine appropriate behaviour. Be aware of
368
+ # multi-line sql which might begin with 'create stored_proc' and contain 'insert into ...' lines.
369
+ # Possible improvements include ignoring comment blocks prior to the first statement.
370
+ if sql.lstrip =~ /\Ainsert/i
371
+ if query_requires_identity_insert?(sql)
372
+ table_name = get_table_name(sql)
373
+ with_identity_insert_enabled(table_name) do
374
+ id = @connection.execute_insert(sql)
375
+ end
376
+ else
377
+ @connection.execute_insert(sql)
378
+ end
379
+ elsif sql.lstrip =~ /\A(create|exec)/i
380
+ @connection.execute_update(sql)
381
+ elsif sql.lstrip =~ /\A\(?\s*(select|show)/i
382
+ repair_special_columns(sql)
383
+ @connection.execute_query(sql)
384
+ else
385
+ @connection.execute_update(sql)
386
+ end
387
+ end
388
+
389
+ def select(sql, name = nil, binds = [])
390
+ sql = substitute_binds(sql, binds)
391
+ log(sql, name) do
392
+ @connection.execute_query(sql)
393
+ end
394
+ end
395
+
396
+ # Turns IDENTITY_INSERT ON for table during execution of the block
397
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
398
+ # block has been executed without regard to its previous state
399
+ def with_identity_insert_enabled(table_name, &block)
400
+ set_identity_insert(table_name, true)
401
+ yield
402
+ ensure
403
+ set_identity_insert(table_name, false)
404
+ end
405
+
406
+ def set_identity_insert(table_name, enable = true)
407
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
408
+ rescue Exception => e
409
+ raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
410
+ end
411
+
412
+ def identity_column(table_name)
413
+ columns(table_name).each do |col|
414
+ return col.name if col.identity
415
+ end
416
+ return nil
417
+ end
418
+
419
+ def query_requires_identity_insert?(sql)
420
+ table_name = get_table_name(sql)
421
+ id_column = identity_column(table_name)
422
+ if sql.strip =~ /insert into [^ ]+ ?\((.+?)\)/i
423
+ insert_columns = $1.split(/, */).map(&method(:unquote_column_name))
424
+ return table_name if insert_columns.include?(id_column)
425
+ end
426
+ end
427
+
428
+ def unquote_column_name(name)
429
+ if name =~ /^\[.*\]$/
430
+ name[1..-2]
431
+ else
432
+ name
433
+ end
434
+ end
435
+
436
+ def get_special_columns(table_name)
437
+ special = []
438
+ columns(table_name).each do |col|
439
+ special << col.name if col.is_special
440
+ end
441
+ special
442
+ end
443
+
444
+ def repair_special_columns(sql)
445
+ special_cols = get_special_columns(get_table_name(sql))
446
+ for col in special_cols.to_a
447
+ sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
448
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
449
+ end
450
+ sql
451
+ end
452
+
453
+ def determine_order_clause(sql)
454
+ return $1 if sql =~ /ORDER BY (.*)$/
455
+ table_name = get_table_name(sql)
456
+ "#{table_name}.#{determine_primary_key(table_name)}"
457
+ end
458
+
459
+ def determine_primary_key(table_name)
460
+ primary_key = columns(table_name).detect { |column| column.primary || column.identity }
461
+ return primary_key.name if primary_key
462
+ # Look for an id column. Return it, without changing case, to cover dbs with a case-sensitive collation.
463
+ columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
464
+ # Give up and provide something which is going to crash almost certainly
465
+ columns(table_name)[0].name
466
+ end
467
+
468
+ def clear_cached_table(name)
469
+ (@table_columns ||= {}).delete(name.to_s)
470
+ end
471
+
472
+ def reset_column_information
473
+ @table_columns = nil
474
+ end
475
+ end
476
+ end
477
+