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.
- data/History.txt +61 -0
- data/LICENSE +21 -0
- data/Manifest.txt +64 -0
- data/README.txt +116 -0
- data/Rakefile +146 -0
- data/lib/active_record/connection_adapters/derby_adapter.rb +13 -0
- data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/hsqldb_adapter.rb +13 -0
- data/lib/active_record/connection_adapters/jdbc_adapter.rb +575 -0
- data/lib/active_record/connection_adapters/jdbc_adapter_spec.rb +10 -0
- data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +13 -0
- data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +13 -0
- data/lib/jdbc_adapter.rb +32 -0
- data/lib/jdbc_adapter/jdbc_db2.rb +104 -0
- data/lib/jdbc_adapter/jdbc_derby.rb +362 -0
- data/lib/jdbc_adapter/jdbc_firebird.rb +109 -0
- data/lib/jdbc_adapter/jdbc_hsqldb.rb +168 -0
- data/lib/jdbc_adapter/jdbc_mimer.rb +134 -0
- data/lib/jdbc_adapter/jdbc_mssql.rb +356 -0
- data/lib/jdbc_adapter/jdbc_mysql.rb +168 -0
- data/lib/jdbc_adapter/jdbc_oracle.rb +340 -0
- data/lib/jdbc_adapter/jdbc_postgre.rb +347 -0
- data/lib/jdbc_adapter/missing_functionality_helper.rb +72 -0
- data/lib/jdbc_adapter/version.rb +5 -0
- data/lib/jdbc_adapter_internal.jar +0 -0
- data/lib/tasks/jdbc_databases.rake +72 -0
- data/src/java/JDBCDerbySpec.java +323 -0
- data/src/java/JDBCMySQLSpec.java +89 -0
- data/src/java/JdbcAdapterInternalService.java +953 -0
- data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
- data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
- data/test/db/derby.rb +18 -0
- data/test/db/h2.rb +11 -0
- data/test/db/hsqldb.rb +15 -0
- data/test/db/jdbc.rb +11 -0
- data/test/db/jndi_config.rb +30 -0
- data/test/db/logger.rb +3 -0
- data/test/db/mysql.rb +9 -0
- data/test/db/postgres.rb +9 -0
- data/test/derby_multibyte_test.rb +12 -0
- data/test/derby_simple_test.rb +12 -0
- data/test/generic_jdbc_connection_test.rb +9 -0
- data/test/h2_simple_test.rb +7 -0
- data/test/hsqldb_simple_test.rb +6 -0
- data/test/jdbc_adapter/jdbc_db2_test.rb +21 -0
- data/test/jdbc_common.rb +6 -0
- data/test/jndi_test.rb +37 -0
- data/test/manualTestDatabase.rb +195 -0
- data/test/minirunit.rb +109 -0
- data/test/minirunit/testConnect.rb +14 -0
- data/test/minirunit/testH2.rb +73 -0
- data/test/minirunit/testHsqldb.rb +73 -0
- data/test/minirunit/testLoadActiveRecord.rb +3 -0
- data/test/minirunit/testMysql.rb +83 -0
- data/test/minirunit/testRawSelect.rb +24 -0
- data/test/models/auto_id.rb +18 -0
- data/test/models/data_types.rb +18 -0
- data/test/models/entry.rb +20 -0
- data/test/mysql_multibyte_test.rb +6 -0
- data/test/mysql_simple_test.rb +13 -0
- data/test/postgres_simple_test.rb +12 -0
- data/test/simple.rb +157 -0
- 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
|
+
|