activerecord-jdbc-adapter 0.6

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 (65) hide show
  1. data/History.txt +61 -0
  2. data/LICENSE +21 -0
  3. data/Manifest.txt +64 -0
  4. data/README.txt +116 -0
  5. data/Rakefile +146 -0
  6. data/lib/active_record/connection_adapters/derby_adapter.rb +13 -0
  7. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  8. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +13 -0
  9. data/lib/active_record/connection_adapters/jdbc_adapter.rb +575 -0
  10. data/lib/active_record/connection_adapters/jdbc_adapter_spec.rb +10 -0
  11. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  12. data/lib/active_record/connection_adapters/mysql_adapter.rb +13 -0
  13. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  14. data/lib/active_record/connection_adapters/postgresql_adapter.rb +13 -0
  15. data/lib/jdbc_adapter.rb +32 -0
  16. data/lib/jdbc_adapter/jdbc_db2.rb +104 -0
  17. data/lib/jdbc_adapter/jdbc_derby.rb +362 -0
  18. data/lib/jdbc_adapter/jdbc_firebird.rb +109 -0
  19. data/lib/jdbc_adapter/jdbc_hsqldb.rb +168 -0
  20. data/lib/jdbc_adapter/jdbc_mimer.rb +134 -0
  21. data/lib/jdbc_adapter/jdbc_mssql.rb +356 -0
  22. data/lib/jdbc_adapter/jdbc_mysql.rb +168 -0
  23. data/lib/jdbc_adapter/jdbc_oracle.rb +340 -0
  24. data/lib/jdbc_adapter/jdbc_postgre.rb +347 -0
  25. data/lib/jdbc_adapter/missing_functionality_helper.rb +72 -0
  26. data/lib/jdbc_adapter/version.rb +5 -0
  27. data/lib/jdbc_adapter_internal.jar +0 -0
  28. data/lib/tasks/jdbc_databases.rake +72 -0
  29. data/src/java/JDBCDerbySpec.java +323 -0
  30. data/src/java/JDBCMySQLSpec.java +89 -0
  31. data/src/java/JdbcAdapterInternalService.java +953 -0
  32. data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
  33. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
  34. data/test/db/derby.rb +18 -0
  35. data/test/db/h2.rb +11 -0
  36. data/test/db/hsqldb.rb +15 -0
  37. data/test/db/jdbc.rb +11 -0
  38. data/test/db/jndi_config.rb +30 -0
  39. data/test/db/logger.rb +3 -0
  40. data/test/db/mysql.rb +9 -0
  41. data/test/db/postgres.rb +9 -0
  42. data/test/derby_multibyte_test.rb +12 -0
  43. data/test/derby_simple_test.rb +12 -0
  44. data/test/generic_jdbc_connection_test.rb +9 -0
  45. data/test/h2_simple_test.rb +7 -0
  46. data/test/hsqldb_simple_test.rb +6 -0
  47. data/test/jdbc_adapter/jdbc_db2_test.rb +21 -0
  48. data/test/jdbc_common.rb +6 -0
  49. data/test/jndi_test.rb +37 -0
  50. data/test/manualTestDatabase.rb +195 -0
  51. data/test/minirunit.rb +109 -0
  52. data/test/minirunit/testConnect.rb +14 -0
  53. data/test/minirunit/testH2.rb +73 -0
  54. data/test/minirunit/testHsqldb.rb +73 -0
  55. data/test/minirunit/testLoadActiveRecord.rb +3 -0
  56. data/test/minirunit/testMysql.rb +83 -0
  57. data/test/minirunit/testRawSelect.rb +24 -0
  58. data/test/models/auto_id.rb +18 -0
  59. data/test/models/data_types.rb +18 -0
  60. data/test/models/entry.rb +20 -0
  61. data/test/mysql_multibyte_test.rb +6 -0
  62. data/test/mysql_simple_test.rb +13 -0
  63. data/test/postgres_simple_test.rb +12 -0
  64. data/test/simple.rb +157 -0
  65. metadata +112 -0
@@ -0,0 +1,109 @@
1
+ module ::JdbcSpec
2
+ module FireBird
3
+ def self.adapter_selector
4
+ [/firebird/i, lambda{|cfg,adapt| adapt.extend(::JdbcSpec::FireBird)}]
5
+ end
6
+
7
+ def modify_types(tp)
8
+ tp[:primary_key] = 'INTEGER NOT NULL PRIMARY KEY'
9
+ tp[:string][:limit] = 252
10
+ tp[:integer][:limit] = nil
11
+ tp
12
+ end
13
+
14
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
15
+ execute(sql, name)
16
+ id_value
17
+ end
18
+
19
+ def add_limit_offset!(sql, options) # :nodoc:
20
+ if options[:limit]
21
+ limit_string = "FIRST #{options[:limit]}"
22
+ limit_string << " SKIP #{options[:offset]}" if options[:offset]
23
+ sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
24
+ end
25
+ end
26
+
27
+ def prefetch_primary_key?(table_name = nil)
28
+ true
29
+ end
30
+
31
+ def default_sequence_name(table_name, primary_key) # :nodoc:
32
+ "#{table_name}_seq"
33
+ end
34
+
35
+ def next_sequence_value(sequence_name)
36
+ select_one("SELECT GEN_ID(#{sequence_name}, 1 ) FROM RDB$DATABASE;")["gen_id"]
37
+ end
38
+
39
+ def create_table(name, options = {}) #:nodoc:
40
+ super(name, options)
41
+ execute "CREATE GENERATOR #{name}_seq"
42
+ end
43
+
44
+ def rename_table(name, new_name) #:nodoc:
45
+ execute "RENAME #{name} TO #{new_name}"
46
+ execute "UPDATE RDB$GENERATORS SET RDB$GENERATOR_NAME='#{new_name}_seq' WHERE RDB$GENERATOR_NAME='#{name}_seq'" rescue nil
47
+ end
48
+
49
+ def drop_table(name, options = {}) #:nodoc:
50
+ super(name)
51
+ execute "DROP GENERATOR #{name}_seq" rescue nil
52
+ end
53
+
54
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
55
+ execute "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
56
+ end
57
+
58
+ def rename_column(table_name, column_name, new_column_name)
59
+ execute "ALTER TABLE #{table_name} ALTER #{column_name} TO #{new_column_name}"
60
+ end
61
+
62
+ def remove_index(table_name, options) #:nodoc:
63
+ execute "DROP INDEX #{index_name(table_name, options)}"
64
+ end
65
+
66
+ def quote(value, column = nil) # :nodoc:
67
+ return value.quoted_id if value.respond_to?(:quoted_id)
68
+
69
+ if [Time, DateTime].include?(value.class)
70
+ "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
71
+ else
72
+ if column && column.type == :primary_key
73
+ return value.to_s
74
+ end
75
+ super
76
+ end
77
+ end
78
+
79
+ def quote_string(string) # :nodoc:
80
+ string.gsub(/'/, "''")
81
+ end
82
+
83
+ def quote_column_name(column_name) # :nodoc:
84
+ %Q("#{ar_to_fb_case(column_name)}")
85
+ end
86
+
87
+ def quoted_true # :nodoc:
88
+ quote(1)
89
+ end
90
+
91
+ def quoted_false # :nodoc:
92
+ quote(0)
93
+ end
94
+
95
+ private
96
+
97
+ # Maps uppercase Firebird column names to lowercase for ActiveRecord;
98
+ # mixed-case columns retain their original case.
99
+ def fb_to_ar_case(column_name)
100
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.to_s.downcase
101
+ end
102
+
103
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
104
+ # mixed-case columns retain their original case.
105
+ def ar_to_fb_case(column_name)
106
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.to_s.upcase
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,168 @@
1
+ module ::JdbcSpec
2
+ module ActiveRecordExtensions
3
+ def hsqldb_connection(config)
4
+ config[:url] ||= "jdbc:hsqldb:#{config[:database]}"
5
+ config[:driver] ||= "org.hsqldb.jdbcDriver"
6
+ embedded_driver(config)
7
+ end
8
+
9
+ def h2_connection(config)
10
+ config[:url] ||= "jdbc:h2:#{config[:database]}"
11
+ config[:driver] ||= "org.h2.Driver"
12
+ embedded_driver(config)
13
+ end
14
+ end
15
+
16
+ module HSQLDB
17
+ def self.column_selector
18
+ [/hsqldb|\.h2\./i, lambda {|cfg,col| col.extend(::JdbcSpec::HSQLDB::Column)}]
19
+ end
20
+
21
+ def self.adapter_selector
22
+ [/hsqldb|\.h2\./i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::HSQLDB)}]
23
+ end
24
+
25
+ module Column
26
+ def type_cast(value)
27
+ return nil if value.nil? || value =~ /^\s*null\s*$/i
28
+ case type
29
+ when :string then value
30
+ when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
31
+ when :primary_key then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
32
+ when :float then value.to_f
33
+ when :datetime then cast_to_date_or_time(value)
34
+ when :timestamp then cast_to_time(value)
35
+ when :binary then value.scan(/[0-9A-Fa-f]{2}/).collect {|v| v.to_i(16)}.pack("C*")
36
+ when :time then cast_to_time(value)
37
+ else value
38
+ end
39
+ end
40
+ def cast_to_date_or_time(value)
41
+ return value if value.is_a? Date
42
+ return nil if value.blank?
43
+ guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
44
+ end
45
+
46
+ def cast_to_time(value)
47
+ return value if value.is_a? Time
48
+ time_array = ParseDate.parsedate value
49
+ time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
50
+ Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
51
+ end
52
+
53
+ def guess_date_or_time(value)
54
+ (value.hour == 0 and value.min == 0 and value.sec == 0) ?
55
+ Date.new(value.year, value.month, value.day) : value
56
+ end
57
+
58
+
59
+ private
60
+ def simplified_type(field_type)
61
+ case field_type
62
+ when /longvarchar/i
63
+ :text
64
+ else
65
+ super(field_type)
66
+ end
67
+ end
68
+
69
+ # Override of ActiveRecord::ConnectionAdapters::Column
70
+ def extract_limit(sql_type)
71
+ # HSQLDB appears to return "LONGVARCHAR(0)" for :text columns, which
72
+ # for AR purposes should be interpreted as "no limit"
73
+ return nil if sql_type =~ /\(0\)/
74
+ super
75
+ end
76
+ end
77
+
78
+ def modify_types(tp)
79
+ tp[:primary_key] = "INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY"
80
+ tp[:integer][:limit] = nil
81
+ tp[:boolean][:limit] = nil
82
+ # set text and float limits so we don't see odd scales tacked on
83
+ # in migrations
84
+ tp[:text][:limit] = nil
85
+ tp[:float][:limit] = 17
86
+ tp[:string][:limit] = 255
87
+ tp[:datetime] = { :name => "DATETIME" }
88
+ tp[:timestamp] = { :name => "DATETIME" }
89
+ tp[:time] = { :name => "DATETIME" }
90
+ tp[:date] = { :name => "DATETIME" }
91
+ tp
92
+ end
93
+
94
+ def quote(value, column = nil) # :nodoc:
95
+ return value.quoted_id if value.respond_to?(:quoted_id)
96
+
97
+ case value
98
+ when String
99
+ if column && column.type == :binary
100
+ "'#{quote_string(value).unpack("C*").collect {|v| v.to_s(16)}.join}'"
101
+ else
102
+ "'#{quote_string(value)}'"
103
+ end
104
+ else super
105
+ end
106
+ end
107
+
108
+ def quote_string(str)
109
+ str.gsub(/'/, "''")
110
+ end
111
+
112
+ def quoted_true
113
+ '1'
114
+ end
115
+
116
+ def quoted_false
117
+ '0'
118
+ end
119
+
120
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
121
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"
122
+ end
123
+
124
+ def change_column_default(table_name, column_name, default) #:nodoc:
125
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{quote(default)}"
126
+ end
127
+
128
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
129
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} RENAME TO #{new_column_name}"
130
+ end
131
+
132
+ def rename_table(name, new_name)
133
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
134
+ end
135
+
136
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
137
+ log_no_bench(sql,name) do
138
+ @connection.execute_update(sql)
139
+ end
140
+ table = sql.split(" ", 4)[2]
141
+ id_value || last_insert_id(table, nil)
142
+ end
143
+
144
+ def last_insert_id(table, sequence_name)
145
+ Integer(select_value("SELECT IDENTITY() FROM #{table}"))
146
+ end
147
+
148
+ def add_limit_offset!(sql, options) #:nodoc:
149
+ offset = options[:offset] || 0
150
+ bef = sql[7..-1]
151
+ if limit = options[:limit]
152
+ sql.replace "select limit #{offset} #{limit} #{bef}"
153
+ elsif offset > 0
154
+ sql.replace "select limit #{offset} 0 #{bef}"
155
+ end
156
+ end
157
+
158
+ # override to filter out system tables that otherwise end
159
+ # up in db/schema.rb during migrations. JdbcConnection#tables
160
+ # now takes an optional block filter so we can screen out
161
+ # rows corresponding to system tables. HSQLDB names its
162
+ # system tables SYSTEM.*, but H2 seems to name them without
163
+ # any kind of convention
164
+ def tables
165
+ @connection.tables.select {|row| row.to_s !~ /^system_/i }
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,134 @@
1
+ module JdbcSpec
2
+ module Mimer
3
+ def self.adapter_selector
4
+ [/mimer/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Mimer)}]
5
+ end
6
+
7
+ def modify_types(tp)
8
+ tp[:primary_key] = "INTEGER NOT NULL PRIMARY KEY"
9
+ tp[:boolean][:limit] = nil
10
+ tp[:string][:limit] = 255
11
+ tp[:binary] = {:name => "BINARY VARYING", :limit => 4096}
12
+ tp[:text] = {:name => "VARCHAR", :limit => 4096}
13
+ tp[:datetime] = { :name => "TIMESTAMP" }
14
+ tp[:timestamp] = { :name => "TIMESTAMP" }
15
+ tp[:time] = { :name => "TIMESTAMP" }
16
+ tp[:date] = { :name => "TIMESTAMP" }
17
+ tp
18
+ end
19
+
20
+ def default_sequence_name(table, column) #:nodoc:
21
+ "#{table}_seq"
22
+ end
23
+
24
+ def create_table(name, options = {}) #:nodoc:
25
+ super(name, options)
26
+ execute "CREATE SEQUENCE #{name}_seq" unless options[:id] == false
27
+ end
28
+
29
+ def drop_table(name, options = {}) #:nodoc:
30
+ super(name) rescue nil
31
+ execute "DROP SEQUENCE #{name}_seq" rescue nil
32
+ end
33
+
34
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
35
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"
36
+ end
37
+
38
+ def change_column_default(table_name, column_name, default) #:nodoc:
39
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{quote(default)}"
40
+ end
41
+
42
+ def remove_index(table_name, options = {}) #:nodoc:
43
+ execute "DROP INDEX #{index_name(table_name, options)}"
44
+ end
45
+
46
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
47
+ if pk.nil? # Who called us? What does the sql look like? No idea!
48
+ execute sql, name
49
+ elsif id_value # Pre-assigned id
50
+ log(sql, name) { @connection.execute_insert sql,pk }
51
+ else # Assume the sql contains a bind-variable for the id
52
+ id_value = select_one("SELECT NEXT_VALUE OF #{sequence_name} AS val FROM MIMER.ONEROW")['val']
53
+ log(sql, name) {
54
+ execute_prepared_insert(sql,id_value)
55
+ }
56
+ end
57
+ id_value
58
+ end
59
+
60
+ def execute_prepared_insert(sql, id)
61
+ @stmts ||= {}
62
+ @stmts[sql] ||= @connection.ps(sql)
63
+ stmt = @stmts[sql]
64
+ stmt.setLong(1,id)
65
+ stmt.executeUpdate
66
+ id
67
+ end
68
+
69
+ def quote(value, column = nil) #:nodoc:
70
+ return value.quoted_id if value.respond_to?(:quoted_id)
71
+
72
+ if String === value && column && column.type == :binary
73
+ return "X'#{quote_string(value.unpack("C*").collect {|v| v.to_s(16)}.join)}'"
74
+ end
75
+ case value
76
+ when String : %Q{'#{quote_string(value)}'}
77
+ when NilClass : 'NULL'
78
+ when TrueClass : '1'
79
+ when FalseClass : '0'
80
+ when Numeric : value.to_s
81
+ when Date, Time : %Q{TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
82
+ else %Q{'#{quote_string(value.to_yaml)}'}
83
+ end
84
+ end
85
+
86
+ def quoted_true
87
+ '1'
88
+ end
89
+
90
+ def quoted_false
91
+ '0'
92
+ end
93
+
94
+ def add_limit_offset!(sql, options) # :nodoc:
95
+ @limit = options[:limit]
96
+ @offset = options[:offset]
97
+ end
98
+
99
+ def select_all(sql, name = nil)
100
+ @offset ||= 0
101
+ if !@limit || @limit == -1
102
+ range = @offset..-1
103
+ else
104
+ range = @offset...(@offset+@limit)
105
+ end
106
+ select(sql, name)[range]
107
+ ensure
108
+ @limit = @offset = nil
109
+ end
110
+
111
+ def select_one(sql, name = nil)
112
+ @offset ||= 0
113
+ select(sql, name)[@offset]
114
+ ensure
115
+ @limit = @offset = nil
116
+ end
117
+
118
+ def _execute(sql, name = nil)
119
+ if sql =~ /^select/i
120
+ @offset ||= 0
121
+ if !@limit || @limit == -1
122
+ range = @offset..-1
123
+ else
124
+ range = @offset...(@offset+@limit)
125
+ end
126
+ @connection.execute_query(sql)[range]
127
+ else
128
+ @connection.execute_update(sql)
129
+ end
130
+ ensure
131
+ @limit = @offset = nil
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,356 @@
1
+ module ::ActiveRecord
2
+ class Base
3
+ # After setting large objects to empty, write data back with a helper method
4
+ after_save :write_lobs
5
+ def write_lobs() #:nodoc:
6
+ if connection.is_a?(JdbcSpec::MsSQL)
7
+ self.class.columns.select { |c| c.sql_type =~ /image/i }.each { |c|
8
+ value = self[c.name]
9
+ value = value.to_yaml if unserializable_attribute?(c.name, c)
10
+ next if value.nil? || (value == '')
11
+
12
+ connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
13
+ }
14
+ end
15
+ end
16
+ private :write_lobs
17
+ end
18
+ end
19
+
20
+ module JdbcSpec
21
+ module MsSQL
22
+ def self.column_selector
23
+ [/sqlserver|tds/i, lambda {|cfg,col| col.extend(::JdbcSpec::MsSQL::Column)}]
24
+ end
25
+
26
+ def self.adapter_selector
27
+ [/sqlserver|tds/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::MsSQL)}]
28
+ end
29
+
30
+ module Column
31
+ attr_accessor :identity, :is_special
32
+
33
+ def simplified_type(field_type)
34
+ case field_type
35
+ when /int|bigint|smallint|tinyint/i then :integer
36
+ when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :decimal
37
+ when /datetime|smalldatetime/i then :datetime
38
+ when /timestamp/i then :timestamp
39
+ when /time/i then :time
40
+ when /text|ntext/i then :text
41
+ when /binary|image|varbinary/i then :binary
42
+ when /char|nchar|nvarchar|string|varchar/i then :string
43
+ when /bit/i then :boolean
44
+ when /uniqueidentifier/i then :string
45
+ end
46
+ end
47
+
48
+ def type_cast(value)
49
+ return nil if value.nil? || value == "(NULL)"
50
+ case type
51
+ when :string then unquote value
52
+ when :integer then unquote(value).to_i rescue value ? 1 : 0
53
+ when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
54
+ when :decimal then self.class.value_to_decimal(unquote(value))
55
+ when :datetime then cast_to_datetime(value)
56
+ when :timestamp then cast_to_time(value)
57
+ when :time then cast_to_time(value)
58
+ when :date then cast_to_datetime(value)
59
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or unquote(value)=="1"
60
+ when :binary then unquote value
61
+ else value
62
+ end
63
+ end
64
+
65
+ def unquote(value)
66
+ value.to_s.sub(/\A\([\(\']?/, "").sub(/[\'\)]?\)\Z/, "")
67
+ end
68
+
69
+ def cast_to_time(value)
70
+ return value if value.is_a?(Time)
71
+ time_array = ParseDate.parsedate(value)
72
+ time_array[0] ||= 2000
73
+ time_array[1] ||= 1
74
+ time_array[2] ||= 1
75
+ Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
76
+ end
77
+
78
+ def cast_to_datetime(value)
79
+ if value.is_a?(Time)
80
+ if value.year != 0 and value.month != 0 and value.day != 0
81
+ return value
82
+ else
83
+ return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
84
+ end
85
+ end
86
+ return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
87
+ value
88
+ end
89
+
90
+ # These methods will only allow the adapter to insert binary data with a length of 7K or less
91
+ # because of a SQL Server statement length policy.
92
+ def self.string_to_binary(value)
93
+ ''
94
+ end
95
+ end
96
+
97
+ def modify_types(tp)
98
+ tp[:primary_key] = "int NOT NULL IDENTITY(1, 1) PRIMARY KEY"
99
+ tp[:integer][:limit] = nil
100
+ tp[:boolean] = {:name => "bit"}
101
+ tp[:binary] = { :name => "image"}
102
+ tp
103
+ end
104
+
105
+ def quote(value, column = nil)
106
+ return value.quoted_id if value.respond_to?(:quoted_id)
107
+
108
+ case value
109
+ when String, ActiveSupport::Multibyte::Chars
110
+ value = value.to_s
111
+ if column && column.type == :binary
112
+ "'#{quote_string(JdbcSpec::MsSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
113
+ elsif column && [:integer, :float].include?(column.type)
114
+ value = column.type == :integer ? value.to_i : value.to_f
115
+ value.to_s
116
+ else
117
+ "'#{quote_string(value)}'" # ' (for ruby-mode)
118
+ end
119
+ when TrueClass then '1'
120
+ when FalseClass then '0'
121
+ when Time, DateTime then "'#{value.strftime("%Y%m%d %H:%M:%S")}'"
122
+ when Date then "'#{value.strftime("%Y%m%d")}'"
123
+ else super
124
+ end
125
+ end
126
+
127
+ def quote_string(string)
128
+ string.gsub(/\'/, "''")
129
+ end
130
+
131
+ def quote_column_name(name)
132
+ "[#{name}]"
133
+ end
134
+
135
+ def add_limit_offset!(sql, options)
136
+ if options[:limit] and options[:offset]
137
+ total_rows = select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT\\1 TOP 1000000000")}) tally")[0]["TotalRows"].to_i
138
+ if (options[:limit] + options[:offset]) >= total_rows
139
+ options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
140
+ end
141
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT\\1 TOP #{options[:limit] + options[:offset]} ")
142
+ sql << ") AS tmp1"
143
+ if options[:order]
144
+ options[:order] = options[:order].split(',').map do |field|
145
+ parts = field.split(" ")
146
+ tc = parts[0]
147
+ if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
148
+ tc.gsub!(/\./, '\\.\\[')
149
+ tc << '\\]'
150
+ end
151
+ if sql =~ /#{tc} AS (t\d_r\d\d?)/
152
+ parts[0] = $1
153
+ elsif parts[0] =~ /\w+\.(\w+)/
154
+ parts[0] = $1
155
+ end
156
+ parts.join(' ')
157
+ end.join(', ')
158
+ sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
159
+ else
160
+ sql << " ) AS tmp2"
161
+ end
162
+ elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
163
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
164
+ "SELECT#{$1} TOP #{options[:limit]}"
165
+ end unless options[:limit].nil?
166
+ end
167
+ end
168
+
169
+
170
+ def change_order_direction(order)
171
+ order.split(",").collect {|fragment|
172
+ case fragment
173
+ when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
174
+ when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
175
+ else String.new(fragment).split(',').join(' DESC,') + ' DESC'
176
+ end
177
+ }.join(",")
178
+ end
179
+
180
+ def recreate_database(name)
181
+ drop_database(name)
182
+ create_database(name)
183
+ end
184
+
185
+ def drop_database(name)
186
+ execute "DROP DATABASE #{name}"
187
+ end
188
+
189
+ def create_database(name)
190
+ execute "CREATE DATABASE #{name}"
191
+ end
192
+
193
+ def rename_table(name, new_name)
194
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
195
+ end
196
+
197
+ # Adds a new column to the named table.
198
+ # See TableDefinition#column for details of the options you can use.
199
+ def add_column(table_name, column_name, type, options = {})
200
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
201
+ add_column_options!(add_column_sql, options)
202
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
203
+ # 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
204
+ execute(add_column_sql)
205
+ end
206
+
207
+ def rename_column(table, column, new_column_name)
208
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
209
+ end
210
+
211
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
212
+ return super unless type.to_s == 'integer'
213
+
214
+ if limit.nil? || limit == 4
215
+ 'int'
216
+ elsif limit == 2
217
+ 'smallint'
218
+ elsif limit ==1
219
+ 'tinyint'
220
+ else
221
+ 'bigint'
222
+ end
223
+ end
224
+
225
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
226
+ sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
227
+ if options_include_default?(options)
228
+ remove_default_constraint(table_name, column_name)
229
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
230
+ end
231
+ sql_commands.each {|c|
232
+ execute(c)
233
+ }
234
+ end
235
+ def change_column_default(table_name, column_name, default) #:nodoc:
236
+ execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default, column_name)} FOR #{column_name}"
237
+ end
238
+ def remove_column(table_name, column_name)
239
+ remove_check_constraints(table_name, column_name)
240
+ remove_default_constraint(table_name, column_name)
241
+ execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
242
+ end
243
+
244
+ def remove_default_constraint(table_name, column_name)
245
+ 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"
246
+ defaults.each {|constraint|
247
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
248
+ }
249
+ end
250
+
251
+ def remove_check_constraints(table_name, column_name)
252
+ # TODO remove all constraints in single method
253
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
254
+ constraints.each do |constraint|
255
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
256
+ end
257
+ end
258
+
259
+ def remove_index(table_name, options = {})
260
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
261
+ end
262
+
263
+
264
+ def columns(table_name, name = nil)
265
+ cc = super
266
+ cc.each do |col|
267
+ col.identity = true if col.sql_type =~ /identity/i
268
+ col.is_special = true if col.sql_type =~ /text|ntext|image/i
269
+ end
270
+ cc
271
+ end
272
+
273
+ def _execute(sql, name = nil)
274
+ if sql.lstrip =~ /^insert/i
275
+ if query_requires_identity_insert?(sql)
276
+ table_name = get_table_name(sql)
277
+ with_identity_insert_enabled(table_name) do
278
+ id = @connection.execute_insert(sql)
279
+ end
280
+ else
281
+ @connection.execute_insert(sql)
282
+ end
283
+ elsif sql.lstrip =~ /^\(?\s*(select|show)/i
284
+ repair_special_columns(sql)
285
+ @connection.execute_query(sql)
286
+ else
287
+ @connection.execute_update(sql)
288
+ end
289
+ end
290
+
291
+
292
+ private
293
+ # Turns IDENTITY_INSERT ON for table during execution of the block
294
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
295
+ # block has been executed without regard to its previous state
296
+
297
+ def with_identity_insert_enabled(table_name, &block)
298
+ set_identity_insert(table_name, true)
299
+ yield
300
+ ensure
301
+ set_identity_insert(table_name, false)
302
+ end
303
+
304
+ def set_identity_insert(table_name, enable = true)
305
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
306
+ rescue Exception => e
307
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
308
+ end
309
+
310
+ def get_table_name(sql)
311
+ if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
312
+ $1
313
+ elsif sql =~ /from\s+([^\(\s]+)\s*/i
314
+ $1
315
+ else
316
+ nil
317
+ end
318
+ end
319
+
320
+ def identity_column(table_name)
321
+ @table_columns = {} unless @table_columns
322
+ @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
323
+ @table_columns[table_name].each do |col|
324
+ return col.name if col.identity
325
+ end
326
+
327
+ return nil
328
+ end
329
+
330
+ def query_requires_identity_insert?(sql)
331
+ table_name = get_table_name(sql)
332
+ id_column = identity_column(table_name)
333
+ sql =~ /\[#{id_column}\]/ ? table_name : nil
334
+ end
335
+
336
+ def get_special_columns(table_name)
337
+ special = []
338
+ @table_columns ||= {}
339
+ @table_columns[table_name] ||= columns(table_name)
340
+ @table_columns[table_name].each do |col|
341
+ special << col.name if col.is_special
342
+ end
343
+ special
344
+ end
345
+
346
+ def repair_special_columns(sql)
347
+ special_cols = get_special_columns(get_table_name(sql))
348
+ for col in special_cols.to_a
349
+ sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
350
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
351
+ end
352
+ sql
353
+ end
354
+ end
355
+ end
356
+