cmoran92-activerecord-jdbc-adapter 1.2.1

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 (202) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +8 -0
  3. data/Gemfile +14 -0
  4. data/Gemfile.lock +42 -0
  5. data/History.txt +469 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.rdoc +199 -0
  8. data/Rakefile +60 -0
  9. data/activerecord-jdbc-adapter.gemspec +23 -0
  10. data/bench/bench_attributes.rb +13 -0
  11. data/bench/bench_attributes_new.rb +14 -0
  12. data/bench/bench_create.rb +12 -0
  13. data/bench/bench_find_all.rb +12 -0
  14. data/bench/bench_find_all_mt.rb +25 -0
  15. data/bench/bench_model.rb +85 -0
  16. data/bench/bench_new.rb +12 -0
  17. data/bench/bench_new_valid.rb +12 -0
  18. data/bench/bench_valid.rb +13 -0
  19. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  20. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  21. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  22. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  23. data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
  24. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  25. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  30. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  31. data/lib/activerecord-jdbc-adapter.rb +8 -0
  32. data/lib/arel/engines/sql/compilers/db2_compiler.rb +9 -0
  33. data/lib/arel/engines/sql/compilers/derby_compiler.rb +6 -0
  34. data/lib/arel/engines/sql/compilers/h2_compiler.rb +6 -0
  35. data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +15 -0
  36. data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +6 -0
  37. data/lib/arel/engines/sql/compilers/mssql_compiler.rb +46 -0
  38. data/lib/arel/visitors/compat.rb +13 -0
  39. data/lib/arel/visitors/db2.rb +17 -0
  40. data/lib/arel/visitors/derby.rb +25 -0
  41. data/lib/arel/visitors/firebird.rb +24 -0
  42. data/lib/arel/visitors/hsqldb.rb +26 -0
  43. data/lib/arel/visitors/sql_server.rb +44 -0
  44. data/lib/arjdbc.rb +24 -0
  45. data/lib/arjdbc/db2.rb +2 -0
  46. data/lib/arjdbc/db2/adapter.rb +510 -0
  47. data/lib/arjdbc/derby.rb +7 -0
  48. data/lib/arjdbc/derby/adapter.rb +351 -0
  49. data/lib/arjdbc/derby/connection_methods.rb +19 -0
  50. data/lib/arjdbc/discover.rb +92 -0
  51. data/lib/arjdbc/firebird.rb +2 -0
  52. data/lib/arjdbc/firebird/adapter.rb +136 -0
  53. data/lib/arjdbc/h2.rb +4 -0
  54. data/lib/arjdbc/h2/adapter.rb +54 -0
  55. data/lib/arjdbc/h2/connection_methods.rb +13 -0
  56. data/lib/arjdbc/hsqldb.rb +4 -0
  57. data/lib/arjdbc/hsqldb/adapter.rb +184 -0
  58. data/lib/arjdbc/hsqldb/connection_methods.rb +15 -0
  59. data/lib/arjdbc/informix.rb +3 -0
  60. data/lib/arjdbc/informix/adapter.rb +138 -0
  61. data/lib/arjdbc/informix/connection_methods.rb +11 -0
  62. data/lib/arjdbc/jdbc.rb +2 -0
  63. data/lib/arjdbc/jdbc/adapter.rb +354 -0
  64. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  65. data/lib/arjdbc/jdbc/callbacks.rb +44 -0
  66. data/lib/arjdbc/jdbc/column.rb +47 -0
  67. data/lib/arjdbc/jdbc/compatibility.rb +51 -0
  68. data/lib/arjdbc/jdbc/connection.rb +134 -0
  69. data/lib/arjdbc/jdbc/connection_methods.rb +16 -0
  70. data/lib/arjdbc/jdbc/core_ext.rb +24 -0
  71. data/lib/arjdbc/jdbc/discover.rb +18 -0
  72. data/lib/arjdbc/jdbc/driver.rb +35 -0
  73. data/lib/arjdbc/jdbc/extension.rb +47 -0
  74. data/lib/arjdbc/jdbc/java.rb +14 -0
  75. data/lib/arjdbc/jdbc/jdbc.rake +131 -0
  76. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +87 -0
  77. data/lib/arjdbc/jdbc/quoted_primary_key.rb +28 -0
  78. data/lib/arjdbc/jdbc/railtie.rb +9 -0
  79. data/lib/arjdbc/jdbc/rake_tasks.rb +10 -0
  80. data/lib/arjdbc/jdbc/require_driver.rb +16 -0
  81. data/lib/arjdbc/jdbc/type_converter.rb +126 -0
  82. data/lib/arjdbc/mimer.rb +2 -0
  83. data/lib/arjdbc/mimer/adapter.rb +142 -0
  84. data/lib/arjdbc/mssql.rb +4 -0
  85. data/lib/arjdbc/mssql/adapter.rb +468 -0
  86. data/lib/arjdbc/mssql/connection_methods.rb +31 -0
  87. data/lib/arjdbc/mssql/limit_helpers.rb +108 -0
  88. data/lib/arjdbc/mssql/tsql_helper.rb +61 -0
  89. data/lib/arjdbc/mysql.rb +4 -0
  90. data/lib/arjdbc/mysql/adapter.rb +505 -0
  91. data/lib/arjdbc/mysql/connection_methods.rb +28 -0
  92. data/lib/arjdbc/oracle.rb +3 -0
  93. data/lib/arjdbc/oracle/adapter.rb +428 -0
  94. data/lib/arjdbc/oracle/connection_methods.rb +12 -0
  95. data/lib/arjdbc/postgresql.rb +4 -0
  96. data/lib/arjdbc/postgresql/adapter.rb +825 -0
  97. data/lib/arjdbc/postgresql/connection_methods.rb +23 -0
  98. data/lib/arjdbc/sqlite3.rb +4 -0
  99. data/lib/arjdbc/sqlite3/adapter.rb +387 -0
  100. data/lib/arjdbc/sqlite3/connection_methods.rb +35 -0
  101. data/lib/arjdbc/sybase.rb +2 -0
  102. data/lib/arjdbc/sybase/adapter.rb +46 -0
  103. data/lib/arjdbc/version.rb +8 -0
  104. data/lib/generators/jdbc/USAGE +10 -0
  105. data/lib/generators/jdbc/jdbc_generator.rb +9 -0
  106. data/lib/jdbc_adapter.rb +2 -0
  107. data/lib/jdbc_adapter/rake_tasks.rb +3 -0
  108. data/lib/jdbc_adapter/version.rb +3 -0
  109. data/lib/pg.rb +26 -0
  110. data/pom.xml +57 -0
  111. data/rails_generators/jdbc_generator.rb +15 -0
  112. data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
  113. data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
  114. data/rakelib/bundler_ext.rb +11 -0
  115. data/rakelib/compile.rake +23 -0
  116. data/rakelib/db.rake +39 -0
  117. data/rakelib/rails.rake +41 -0
  118. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +62 -0
  119. data/src/java/arjdbc/derby/DerbyModule.java +324 -0
  120. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +70 -0
  121. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +74 -0
  122. data/src/java/arjdbc/jdbc/AdapterJavaService.java +68 -0
  123. data/src/java/arjdbc/jdbc/JdbcConnectionFactory.java +36 -0
  124. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1331 -0
  125. data/src/java/arjdbc/jdbc/SQLBlock.java +48 -0
  126. data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +127 -0
  127. data/src/java/arjdbc/mysql/MySQLModule.java +134 -0
  128. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +161 -0
  129. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +85 -0
  130. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +82 -0
  131. data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +126 -0
  132. data/test/abstract_db_create.rb +135 -0
  133. data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
  134. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
  135. data/test/activerecord/jall.sh +7 -0
  136. data/test/activerecord/jtest.sh +3 -0
  137. data/test/db/db2.rb +11 -0
  138. data/test/db/derby.rb +12 -0
  139. data/test/db/h2.rb +11 -0
  140. data/test/db/hsqldb.rb +13 -0
  141. data/test/db/informix.rb +11 -0
  142. data/test/db/jdbc.rb +12 -0
  143. data/test/db/jndi_config.rb +40 -0
  144. data/test/db/logger.rb +3 -0
  145. data/test/db/mssql.rb +9 -0
  146. data/test/db/mysql.rb +10 -0
  147. data/test/db/oracle.rb +34 -0
  148. data/test/db/postgres.rb +9 -0
  149. data/test/db/sqlite3.rb +11 -0
  150. data/test/db2_simple_test.rb +66 -0
  151. data/test/derby_migration_test.rb +68 -0
  152. data/test/derby_multibyte_test.rb +12 -0
  153. data/test/derby_simple_test.rb +99 -0
  154. data/test/generic_jdbc_connection_test.rb +29 -0
  155. data/test/h2_change_column_test.rb +68 -0
  156. data/test/h2_simple_test.rb +41 -0
  157. data/test/has_many_through.rb +79 -0
  158. data/test/helper.rb +85 -0
  159. data/test/hsqldb_simple_test.rb +6 -0
  160. data/test/informix_simple_test.rb +48 -0
  161. data/test/jdbc_common.rb +27 -0
  162. data/test/jndi_callbacks_test.rb +40 -0
  163. data/test/jndi_test.rb +25 -0
  164. data/test/manualTestDatabase.rb +191 -0
  165. data/test/models/add_not_null_column_to_table.rb +9 -0
  166. data/test/models/auto_id.rb +15 -0
  167. data/test/models/data_types.rb +30 -0
  168. data/test/models/entry.rb +40 -0
  169. data/test/models/mixed_case.rb +22 -0
  170. data/test/models/reserved_word.rb +15 -0
  171. data/test/models/string_id.rb +17 -0
  172. data/test/models/thing.rb +16 -0
  173. data/test/models/validates_uniqueness_of_string.rb +19 -0
  174. data/test/mssql_db_create_test.rb +26 -0
  175. data/test/mssql_identity_insert_test.rb +19 -0
  176. data/test/mssql_legacy_types_test.rb +58 -0
  177. data/test/mssql_limit_offset_test.rb +136 -0
  178. data/test/mssql_multibyte_test.rb +18 -0
  179. data/test/mssql_simple_test.rb +55 -0
  180. data/test/mysql_db_create_test.rb +27 -0
  181. data/test/mysql_index_length_test.rb +58 -0
  182. data/test/mysql_info_test.rb +123 -0
  183. data/test/mysql_multibyte_test.rb +10 -0
  184. data/test/mysql_nonstandard_primary_key_test.rb +42 -0
  185. data/test/mysql_simple_test.rb +125 -0
  186. data/test/oracle_simple_test.rb +18 -0
  187. data/test/oracle_specific_test.rb +83 -0
  188. data/test/postgres_db_create_test.rb +32 -0
  189. data/test/postgres_drop_db_test.rb +16 -0
  190. data/test/postgres_information_schema_leak_test.rb +29 -0
  191. data/test/postgres_mixed_case_test.rb +29 -0
  192. data/test/postgres_native_type_mapping_test.rb +89 -0
  193. data/test/postgres_nonseq_pkey_test.rb +38 -0
  194. data/test/postgres_reserved_test.rb +22 -0
  195. data/test/postgres_schema_search_path_test.rb +48 -0
  196. data/test/postgres_simple_test.rb +167 -0
  197. data/test/postgres_table_alias_length_test.rb +15 -0
  198. data/test/postgres_type_conversion_test.rb +34 -0
  199. data/test/simple.rb +658 -0
  200. data/test/sqlite3_simple_test.rb +284 -0
  201. data/test/sybase_jtds_simple_test.rb +28 -0
  202. metadata +261 -0
@@ -0,0 +1,87 @@
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
+ :null => column.null)
37
+ end
38
+ @definition.primary_key(primary_key(from)) if primary_key(from)
39
+ yield @definition if block_given?
40
+ end
41
+
42
+ copy_table_indexes(from, to, options[:rename] || {})
43
+ copy_table_contents(from, to,
44
+ @definition.columns.map {|column| column.name},
45
+ options[:rename] || {})
46
+ end
47
+
48
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
49
+ indexes(from).each do |index|
50
+ name = index.name.downcase
51
+ if to == "altered_#{from}"
52
+ name = "temp_#{name}"
53
+ elsif from == "altered_#{to}"
54
+ name = name[5..-1]
55
+ end
56
+
57
+ to_column_names = columns(to).map(&:name)
58
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
59
+ to_column_names.include?(column)
60
+ end
61
+
62
+ unless columns.empty?
63
+ # index name can't be the same
64
+ opts = { :name => name.gsub(/(_?)(#{from})_/, "\\1#{to}_") }
65
+ opts[:unique] = true if index.unique
66
+ add_index(to, columns, opts)
67
+ end
68
+ end
69
+ end
70
+
71
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
72
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
73
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
74
+ from_columns = columns(from).collect {|col| col.name}
75
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
76
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
77
+
78
+ quoted_to = quote_table_name(to)
79
+ execute("SELECT * FROM #{quote_table_name(from)}").each do |row|
80
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
81
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
82
+ sql << ')'
83
+ execute sql
84
+ end
85
+ end
86
+ end
87
+ 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,468 @@
1
+ require 'arjdbc/mssql/tsql_helper'
2
+ require 'arjdbc/mssql/limit_helpers'
3
+
4
+ module ::ArJdbc
5
+ module MsSQL
6
+ include TSqlMethods
7
+ include LimitHelpers
8
+
9
+ def self.extended(mod)
10
+ unless defined?(@lob_callback_added)
11
+ ActiveRecord::Base.class_eval do
12
+ def after_save_with_mssql_lob
13
+ self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
14
+ value = self[c.name]
15
+ value = value.to_yaml if unserializable_attribute?(c.name, c)
16
+ next if value.nil? || (value == '')
17
+
18
+ connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
19
+ end
20
+ end
21
+ end
22
+
23
+ ActiveRecord::Base.after_save :after_save_with_mssql_lob
24
+ @lob_callback_added = true
25
+ end
26
+ mod.add_version_specific_add_limit_offset
27
+ end
28
+
29
+ def self.column_selector
30
+ [/sqlserver|tds|Microsoft SQL/i, lambda {|cfg,col| col.extend(::ArJdbc::MsSQL::Column)}]
31
+ end
32
+
33
+ def self.jdbc_connection_class
34
+ ::ActiveRecord::ConnectionAdapters::MssqlJdbcConnection
35
+ end
36
+
37
+ def self.arel2_visitors(config)
38
+ require 'arel/visitors/sql_server'
39
+ visitor_class = config[:sqlserver_version] == "2000" ? ::Arel::Visitors::SQLServer2000 : ::Arel::Visitors::SQLServer
40
+ {}.tap {|v| %w(mssql sqlserver jdbcmssql).each {|x| v[x] = visitor_class } }
41
+ end
42
+
43
+ def sqlserver_version
44
+ @sqlserver_version ||= select_value("select @@version")[/Microsoft SQL Server\s+(\d{4})/, 1]
45
+ end
46
+
47
+ def add_version_specific_add_limit_offset
48
+ config[:sqlserver_version] = version = sqlserver_version
49
+ if version == "2000"
50
+ extend LimitHelpers::SqlServer2000AddLimitOffset
51
+ else
52
+ extend LimitHelpers::SqlServerAddLimitOffset
53
+ end
54
+ end
55
+
56
+ def modify_types(tp) #:nodoc:
57
+ super(tp)
58
+ tp[:string] = {:name => "NVARCHAR", :limit => 255}
59
+ if sqlserver_version == "2000"
60
+ tp[:text] = {:name => "NTEXT"}
61
+ else
62
+ tp[:text] = {:name => "NVARCHAR(MAX)"}
63
+ end
64
+ tp
65
+ end
66
+
67
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
68
+ # MSSQL's NVARCHAR(n | max) column supports either a number between 1 and
69
+ # 4000, or the word "MAX", which corresponds to 2**30-1 UCS-2 characters.
70
+ #
71
+ # It does not accept NVARCHAR(1073741823) here, so we have to change it
72
+ # to NVARCHAR(MAX), even though they are logically equivalent.
73
+ #
74
+ # MSSQL Server 2000 is skipped here because I don't know how it will behave.
75
+ #
76
+ # See: http://msdn.microsoft.com/en-us/library/ms186939.aspx
77
+ if type.to_s == 'string' and limit == 1073741823 and sqlserver_version != "2000"
78
+ 'NVARCHAR(MAX)'
79
+ elsif %w( boolean date datetime ).include?(type.to_s)
80
+ super(type) # cannot specify limit/precision/scale with these types
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ module Column
87
+ attr_accessor :identity, :is_special
88
+
89
+ def simplified_type(field_type)
90
+ case field_type
91
+ when /int|bigint|smallint|tinyint/i then :integer
92
+ when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
93
+ when /float|double|money|real|smallmoney/i then :decimal
94
+ when /datetime|smalldatetime/i then :datetime
95
+ when /timestamp/i then :timestamp
96
+ when /time/i then :time
97
+ when /date/i then :date
98
+ when /text|ntext|xml/i then :text
99
+ when /binary|image|varbinary/i then :binary
100
+ when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
101
+ when /bit/i then :boolean
102
+ when /uniqueidentifier/i then :string
103
+ else
104
+ super
105
+ end
106
+ end
107
+
108
+ def default_value(value)
109
+ return $1 if value =~ /^\(N?'(.*)'\)$/
110
+ value
111
+ end
112
+
113
+ def type_cast(value)
114
+ return nil if value.nil? || value == "(null)" || value == "(NULL)"
115
+ case type
116
+ when :integer then value.delete('()').to_i rescue unquote(value).to_i rescue value ? 1 : 0
117
+ when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
118
+ when :decimal then self.class.value_to_decimal(unquote(value))
119
+ when :datetime then cast_to_datetime(value)
120
+ when :timestamp then cast_to_time(value)
121
+ when :time then cast_to_time(value)
122
+ when :date then cast_to_date(value)
123
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or unquote(value)=="1"
124
+ when :binary then unquote value
125
+ else value
126
+ end
127
+ end
128
+
129
+ def extract_limit(sql_type)
130
+ case sql_type
131
+ when /text|ntext|xml|binary|image|varbinary|bit/
132
+ nil
133
+ else
134
+ super
135
+ end
136
+ end
137
+
138
+ def is_utf8?
139
+ sql_type =~ /nvarchar|ntext|nchar/i
140
+ end
141
+
142
+ def unquote(value)
143
+ value.to_s.sub(/\A\([\(\']?/, "").sub(/[\'\)]?\)\Z/, "")
144
+ end
145
+
146
+ def cast_to_time(value)
147
+ return value if value.is_a?(Time)
148
+ DateTime.parse(value).to_time rescue nil
149
+ end
150
+
151
+ def cast_to_date(value)
152
+ return value if value.is_a?(Date)
153
+ return Date.parse(value) rescue nil
154
+ end
155
+
156
+ def cast_to_datetime(value)
157
+ if value.is_a?(Time)
158
+ if value.year != 0 and value.month != 0 and value.day != 0
159
+ return value
160
+ else
161
+ return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
162
+ end
163
+ end
164
+ if value.is_a?(DateTime)
165
+ begin
166
+ # Attempt to convert back to a Time, but it could fail for dates significantly in the past/future.
167
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
168
+ rescue ArgumentError
169
+ return value
170
+ end
171
+ end
172
+
173
+ return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
174
+
175
+ return value.is_a?(Date) ? value : nil
176
+ end
177
+
178
+ # These methods will only allow the adapter to insert binary data with a length of 7K or less
179
+ # because of a SQL Server statement length policy.
180
+ def self.string_to_binary(value)
181
+ ''
182
+ end
183
+
184
+ end
185
+
186
+ def quote(value, column = nil)
187
+ return value.quoted_id if value.respond_to?(:quoted_id)
188
+
189
+ case value
190
+ # SQL Server 2000 doesn't let you insert an integer into a NVARCHAR
191
+ # column, so we include Integer here.
192
+ when String, ActiveSupport::Multibyte::Chars, Integer
193
+ value = value.to_s
194
+ if column && column.type == :binary
195
+ "'#{quote_string(ArJdbc::MsSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
196
+ elsif column && [:integer, :float].include?(column.type)
197
+ value = column.type == :integer ? value.to_i : value.to_f
198
+ value.to_s
199
+ elsif !column.respond_to?(:is_utf8?) || column.is_utf8?
200
+ "N'#{quote_string(value)}'" # ' (for ruby-mode)
201
+ else
202
+ super
203
+ end
204
+ when TrueClass then '1'
205
+ when FalseClass then '0'
206
+ else super
207
+ end
208
+ end
209
+
210
+ def quote_string(string)
211
+ string.gsub(/\'/, "''")
212
+ end
213
+
214
+ def quote_table_name(name)
215
+ quote_column_name(name)
216
+ end
217
+
218
+ def quote_column_name(name)
219
+ "[#{name}]"
220
+ end
221
+
222
+ def quoted_true
223
+ quote true
224
+ end
225
+
226
+ def quoted_false
227
+ quote false
228
+ end
229
+
230
+ def adapter_name #:nodoc:
231
+ 'MsSQL'
232
+ end
233
+
234
+ def change_order_direction(order)
235
+ order.split(",").collect do |fragment|
236
+ case fragment
237
+ when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
238
+ when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
239
+ else String.new(fragment).split(',').join(' DESC,') + ' DESC'
240
+ end
241
+ end.join(",")
242
+ end
243
+
244
+ def supports_ddl_transactions?
245
+ true
246
+ end
247
+
248
+ def recreate_database(name)
249
+ drop_database(name)
250
+ create_database(name)
251
+ end
252
+
253
+ def drop_database(name)
254
+ execute "USE master"
255
+ execute "DROP DATABASE #{name}"
256
+ end
257
+
258
+ def create_database(name)
259
+ execute "CREATE DATABASE #{name}"
260
+ execute "USE #{name}"
261
+ end
262
+
263
+ def rename_table(name, new_name)
264
+ clear_cached_table(name)
265
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
266
+ end
267
+
268
+ # Adds a new column to the named table.
269
+ # See TableDefinition#column for details of the options you can use.
270
+ def add_column(table_name, column_name, type, options = {})
271
+ clear_cached_table(table_name)
272
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
273
+ add_column_options!(add_column_sql, options)
274
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
275
+ # 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
276
+ execute(add_column_sql)
277
+ end
278
+
279
+ def rename_column(table, column, new_column_name)
280
+ clear_cached_table(table)
281
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
282
+ end
283
+
284
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
285
+ clear_cached_table(table_name)
286
+ change_column_type(table_name, column_name, type, options)
287
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
288
+ end
289
+
290
+ def change_column_type(table_name, column_name, type, options = {}) #:nodoc:
291
+ clear_cached_table(table_name)
292
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
293
+ if options.has_key?(:null)
294
+ sql += (options[:null] ? " NULL" : " NOT NULL")
295
+ end
296
+ execute(sql)
297
+ end
298
+
299
+ def change_column_default(table_name, column_name, default) #:nodoc:
300
+ clear_cached_table(table_name)
301
+ remove_default_constraint(table_name, column_name)
302
+ unless default.nil?
303
+ execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
304
+ end
305
+ end
306
+
307
+ def remove_column(table_name, column_name)
308
+ clear_cached_table(table_name)
309
+ remove_check_constraints(table_name, column_name)
310
+ remove_default_constraint(table_name, column_name)
311
+ execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
312
+ end
313
+
314
+ def remove_default_constraint(table_name, column_name)
315
+ clear_cached_table(table_name)
316
+ 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"
317
+ defaults.each {|constraint|
318
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
319
+ }
320
+ end
321
+
322
+ def remove_check_constraints(table_name, column_name)
323
+ clear_cached_table(table_name)
324
+ # TODO remove all constraints in single method
325
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
326
+ constraints.each do |constraint|
327
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
328
+ end
329
+ end
330
+
331
+ def remove_index(table_name, options = {})
332
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
333
+ end
334
+
335
+ def columns(table_name, name = nil)
336
+ # It's possible for table_name to be an empty string, or nil, if something attempts to issue SQL
337
+ # which doesn't involve a table. IE. "SELECT 1" or "SELECT * from someFunction()".
338
+ return [] if table_name.blank?
339
+ table_name = table_name.to_s if table_name.is_a?(Symbol)
340
+
341
+ # Remove []'s from around the table name, valid in a select statement, but not when matching metadata.
342
+ table_name = table_name.gsub(/[\[\]]/, '')
343
+
344
+ return [] if table_name =~ /^information_schema\./i
345
+ @table_columns ||= {}
346
+ unless @table_columns[table_name]
347
+ @table_columns[table_name] = super
348
+ @table_columns[table_name].each do |col|
349
+ col.identity = true if col.sql_type =~ /identity/i
350
+ col.is_special = true if col.sql_type =~ /text|ntext|image|xml/i
351
+ end
352
+ end
353
+ @table_columns[table_name]
354
+ end
355
+
356
+ def _execute(sql, name = nil)
357
+ # Match the start of the sql to determine appropriate behaviour. Be aware of
358
+ # multi-line sql which might begin with 'create stored_proc' and contain 'insert into ...' lines.
359
+ # Possible improvements include ignoring comment blocks prior to the first statement.
360
+ if sql.lstrip =~ /\Ainsert/i
361
+ if query_requires_identity_insert?(sql)
362
+ table_name = get_table_name(sql)
363
+ with_identity_insert_enabled(table_name) do
364
+ id = @connection.execute_insert(sql)
365
+ end
366
+ else
367
+ @connection.execute_insert(sql)
368
+ end
369
+ elsif sql.lstrip =~ /\A(create|exec)/i
370
+ @connection.execute_update(sql)
371
+ elsif sql.lstrip =~ /\A\(?\s*(select|show)/i
372
+ repair_special_columns(sql)
373
+ @connection.execute_query(sql)
374
+ else
375
+ @connection.execute_update(sql)
376
+ end
377
+ end
378
+
379
+ def select(sql, name = nil, binds = [])
380
+ sql = substitute_binds(sql, binds)
381
+ log(sql, name) do
382
+ @connection.execute_query(sql)
383
+ end
384
+ end
385
+
386
+ #SELECT .. FOR UPDATE is not supported on Microsoft SQL Server
387
+ def add_lock!(sql, options)
388
+ sql
389
+ end
390
+
391
+ # Turns IDENTITY_INSERT ON for table during execution of the block
392
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
393
+ # block has been executed without regard to its previous state
394
+ def with_identity_insert_enabled(table_name, &block)
395
+ set_identity_insert(table_name, true)
396
+ yield
397
+ ensure
398
+ set_identity_insert(table_name, false)
399
+ end
400
+
401
+ def set_identity_insert(table_name, enable = true)
402
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
403
+ rescue Exception => e
404
+ raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
405
+ end
406
+
407
+ def identity_column(table_name)
408
+ columns(table_name).each do |col|
409
+ return col.name if col.identity
410
+ end
411
+ return nil
412
+ end
413
+
414
+ def query_requires_identity_insert?(sql)
415
+ table_name = get_table_name(sql)
416
+ id_column = identity_column(table_name)
417
+ if sql.strip =~ /insert into [^ ]+ ?\((.+?)\)/i
418
+ insert_columns = $1.split(/, */).map(&method(:unquote_column_name))
419
+ return table_name if insert_columns.include?(id_column)
420
+ end
421
+ end
422
+
423
+ def unquote_column_name(name)
424
+ if name =~ /^\[.*\]$/
425
+ name[1..-2]
426
+ else
427
+ name
428
+ end
429
+ end
430
+
431
+ def get_special_columns(table_name)
432
+ special = []
433
+ columns(table_name).each do |col|
434
+ special << col.name if col.is_special
435
+ end
436
+ special
437
+ end
438
+
439
+ def repair_special_columns(sql)
440
+ special_cols = get_special_columns(get_table_name(sql))
441
+ for col in special_cols.to_a
442
+ sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
443
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
444
+ end
445
+ sql
446
+ end
447
+
448
+ def determine_order_clause(sql)
449
+ return $1 if sql =~ /ORDER BY (.*)$/
450
+ table_name = get_table_name(sql)
451
+ "#{table_name}.#{determine_primary_key(table_name)}"
452
+ end
453
+
454
+ def determine_primary_key(table_name)
455
+ primary_key = columns(table_name).detect { |column| column.primary || column.identity }
456
+ return primary_key.name if primary_key
457
+ # Look for an id column. Return it, without changing case, to cover dbs with a case-sensitive collation.
458
+ columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
459
+ # Give up and provide something which is going to crash almost certainly
460
+ columns(table_name)[0].name
461
+ end
462
+
463
+ def clear_cached_table(name)
464
+ (@table_columns ||= {}).delete(name.to_s)
465
+ end
466
+ end
467
+ end
468
+