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,168 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract/schema_definitions'
|
2
|
+
|
3
|
+
module ::JdbcSpec
|
4
|
+
module ActiveRecordExtensions
|
5
|
+
def mysql_connection(config)
|
6
|
+
if config[:socket]
|
7
|
+
warn "AR-JDBC MySQL on JRuby does not support sockets"
|
8
|
+
end
|
9
|
+
config[:port] ||= 3306
|
10
|
+
config[:url] ||= "jdbc:mysql://#{config[:host]}:#{config[:port]}/#{config[:database]}?zeroDateTimeBehavior=convertToNull&jdbcCompliantTruncation=false&useUnicode=true&characterEncoding=utf8"
|
11
|
+
config[:driver] = "com.mysql.jdbc.Driver"
|
12
|
+
jdbc_connection(config)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module MySQL
|
17
|
+
def self.column_selector
|
18
|
+
[/mysql/i, lambda {|cfg,col| col.extend(::JdbcSpec::MySQL::Column)}]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.adapter_selector
|
22
|
+
[/mysql/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::MySQL)}]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.extended(adapter)
|
26
|
+
adapter.execute("SET SQL_AUTO_IS_NULL=0")
|
27
|
+
end
|
28
|
+
|
29
|
+
module Column
|
30
|
+
TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
|
31
|
+
|
32
|
+
def simplified_type(field_type)
|
33
|
+
return :boolean if field_type =~ /tinyint\(1\)|bit/i
|
34
|
+
return :string if field_type =~ /enum/i
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def init_column(name, default, *args)
|
39
|
+
@original_default = default
|
40
|
+
@default = nil if missing_default_forged_as_empty_string?
|
41
|
+
end
|
42
|
+
|
43
|
+
# MySQL misreports NOT NULL column default when none is given.
|
44
|
+
# We can't detect this for columns which may have a legitimate ''
|
45
|
+
# default (string, text, binary) but we can for others (integer,
|
46
|
+
# datetime, boolean, and the rest).
|
47
|
+
#
|
48
|
+
# Test whether the column has default '', is not null, and is not
|
49
|
+
# a type allowing default ''.
|
50
|
+
def missing_default_forged_as_empty_string?
|
51
|
+
!null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def modify_types(tp)
|
56
|
+
tp[:primary_key] = "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
|
57
|
+
tp[:decimal] = { :name => "decimal" }
|
58
|
+
tp[:timestamp] = { :name => "datetime" }
|
59
|
+
tp[:datetime][:limit] = nil
|
60
|
+
tp
|
61
|
+
end
|
62
|
+
|
63
|
+
# QUOTING ==================================================
|
64
|
+
|
65
|
+
def quote(value, column = nil)
|
66
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
67
|
+
|
68
|
+
if column && column.type == :primary_key
|
69
|
+
value.to_s
|
70
|
+
elsif column && String === value && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
71
|
+
s = column.class.string_to_binary(value).unpack("H*")[0]
|
72
|
+
"x'#{s}'"
|
73
|
+
elsif BigDecimal === value
|
74
|
+
"'#{value.to_s("F")}'"
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def quote_column_name(name) #:nodoc:
|
81
|
+
"`#{name}`"
|
82
|
+
end
|
83
|
+
|
84
|
+
def quoted_true
|
85
|
+
"1"
|
86
|
+
end
|
87
|
+
|
88
|
+
def quoted_false
|
89
|
+
"0"
|
90
|
+
end
|
91
|
+
|
92
|
+
# SCHEMA STATEMENTS ========================================
|
93
|
+
|
94
|
+
def structure_dump #:nodoc:
|
95
|
+
if supports_views?
|
96
|
+
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
97
|
+
else
|
98
|
+
sql = "SHOW TABLES"
|
99
|
+
end
|
100
|
+
|
101
|
+
select_all(sql).inject("") do |structure, table|
|
102
|
+
table.delete('Table_type')
|
103
|
+
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def recreate_database(name) #:nodoc:
|
108
|
+
drop_database(name)
|
109
|
+
create_database(name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_database(name) #:nodoc:
|
113
|
+
execute "CREATE DATABASE `#{name}`"
|
114
|
+
end
|
115
|
+
|
116
|
+
def drop_database(name) #:nodoc:
|
117
|
+
execute "DROP DATABASE IF EXISTS `#{name}`"
|
118
|
+
end
|
119
|
+
|
120
|
+
def current_database
|
121
|
+
select_one("SELECT DATABASE() as db")["db"]
|
122
|
+
end
|
123
|
+
|
124
|
+
def create_table(name, options = {}) #:nodoc:
|
125
|
+
super(name, {:options => "ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin"}.merge(options))
|
126
|
+
end
|
127
|
+
|
128
|
+
def rename_table(name, new_name)
|
129
|
+
execute "RENAME TABLE #{name} TO #{new_name}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
133
|
+
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
134
|
+
|
135
|
+
execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
|
136
|
+
end
|
137
|
+
|
138
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
139
|
+
unless options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
140
|
+
options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
|
141
|
+
end
|
142
|
+
|
143
|
+
change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
144
|
+
add_column_options!(change_column_sql, options)
|
145
|
+
execute(change_column_sql)
|
146
|
+
end
|
147
|
+
|
148
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
149
|
+
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
150
|
+
execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
154
|
+
if limit = options[:limit]
|
155
|
+
unless offset = options[:offset]
|
156
|
+
sql << " LIMIT #{limit}"
|
157
|
+
else
|
158
|
+
sql << " LIMIT #{offset}, #{limit}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
def supports_views?
|
165
|
+
false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
module ::ActiveRecord
|
2
|
+
class Base
|
3
|
+
# After setting large objects to empty, write data back with a helper method
|
4
|
+
alias after_save_without_oracle_lob after_save
|
5
|
+
|
6
|
+
def after_save_with_oracle_lob() #:nodoc:
|
7
|
+
if connection.is_a?(JdbcSpec::Oracle)
|
8
|
+
self.class.columns.select { |c| c.sql_type =~ /LOB\(|LOB$/i }.each { |c|
|
9
|
+
value = self[c.name]
|
10
|
+
value = value.to_yaml if unserializable_attribute?(c.name, c)
|
11
|
+
next if value.nil? || (value == '')
|
12
|
+
|
13
|
+
connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
after_save_without_oracle_lob
|
17
|
+
end
|
18
|
+
|
19
|
+
alias after_save after_save_with_oracle_lob
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module ::JdbcSpec
|
24
|
+
module ActiveRecordExtensions
|
25
|
+
def oracle_connection(config)
|
26
|
+
config[:port] ||= 1521
|
27
|
+
config[:url] ||= "jdbc:oracle:thin:@#{config[:host]}:#{config[:port]}:#{config[:database]}"
|
28
|
+
config[:driver] ||= "oracle.jdbc.driver.OracleDriver"
|
29
|
+
jdbc_connection(config)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Oracle
|
34
|
+
def self.column_selector
|
35
|
+
[/oracle/i, lambda {|cfg,col| col.extend(::JdbcSpec::Oracle::Column)}]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.adapter_selector
|
39
|
+
[/oracle/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Oracle)
|
40
|
+
=begin
|
41
|
+
(adapt.methods - %w(send __send__ id class methods is_a? kind_of? verify! active?)).each do |name|
|
42
|
+
new_name = "__#{name}"
|
43
|
+
(class << adapt; self; end).send :alias_method, new_name, name
|
44
|
+
(class << adapt; self; end).send :define_method, name do |*args|
|
45
|
+
puts "#{name}(#{args.inspect})"
|
46
|
+
adapt.send new_name, *args
|
47
|
+
end
|
48
|
+
end
|
49
|
+
=end
|
50
|
+
}]
|
51
|
+
end
|
52
|
+
|
53
|
+
module Column
|
54
|
+
def type_cast(value)
|
55
|
+
return nil if value.nil?
|
56
|
+
case type
|
57
|
+
when :string then value
|
58
|
+
when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
|
59
|
+
when :primary_key then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
|
60
|
+
when :float then value.to_f
|
61
|
+
when :datetime then JdbcSpec::Oracle::Column.cast_to_date_or_time(value)
|
62
|
+
when :time then JdbcSpec::Oracle::Column.cast_to_time(value)
|
63
|
+
when :decimal then self.class.value_to_decimal(value)
|
64
|
+
when :boolean then self.class.value_to_boolean(value)
|
65
|
+
else value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def type_cast_code(var_name)
|
70
|
+
return "JdbcSpec::Oracle::Column.cast_to_date_or_time(#{var_name})" if type == :datetime
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def simplified_type(field_type)
|
76
|
+
case field_type
|
77
|
+
when /^number\(1\)$/i : :boolean
|
78
|
+
when /char/i : :string
|
79
|
+
when /float|double/i : :float
|
80
|
+
when /int/i : :integer
|
81
|
+
when /num|dec|real/i : @scale == 0 ? :integer : :decimal
|
82
|
+
when /date|time/i : :datetime
|
83
|
+
when /clob/i : :text
|
84
|
+
when /blob/i : :binary
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.cast_to_date_or_time(value)
|
89
|
+
return value if value.is_a? Date
|
90
|
+
return nil if value.blank?
|
91
|
+
guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.cast_to_time(value)
|
95
|
+
return value if value.is_a? Time
|
96
|
+
time_array = ParseDate.parsedate value
|
97
|
+
time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
|
98
|
+
Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.guess_date_or_time(value)
|
102
|
+
(value.hour == 0 and value.min == 0 and value.sec == 0) ?
|
103
|
+
Date.new(value.year, value.month, value.day) : value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def table_alias_length
|
108
|
+
30
|
109
|
+
end
|
110
|
+
|
111
|
+
def default_sequence_name(table, column) #:nodoc:
|
112
|
+
"#{table}_seq"
|
113
|
+
end
|
114
|
+
|
115
|
+
def create_table(name, options = {}) #:nodoc:
|
116
|
+
super(name, options)
|
117
|
+
seq_name = options[:sequence_name] || "#{name}_seq"
|
118
|
+
raise ActiveRecord::StatementInvalid.new("name #{seq_name} too long") if seq_name.length > table_alias_length
|
119
|
+
execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
|
120
|
+
end
|
121
|
+
|
122
|
+
def rename_table(name, new_name) #:nodoc:
|
123
|
+
execute "RENAME #{name} TO #{new_name}"
|
124
|
+
execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def drop_table(name, options = {}) #:nodoc:
|
128
|
+
super(name)
|
129
|
+
seq_name = options[:sequence_name] || "#{name}_seq"
|
130
|
+
execute "DROP SEQUENCE #{seq_name}" rescue nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def recreate_database(name)
|
134
|
+
tables.each{ |table| drop_table(table) }
|
135
|
+
end
|
136
|
+
|
137
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
138
|
+
if pk.nil? # Who called us? What does the sql look like? No idea!
|
139
|
+
execute sql, name
|
140
|
+
elsif id_value # Pre-assigned id
|
141
|
+
execute sql, name
|
142
|
+
else # Assume the sql contains a bind-variable for the id
|
143
|
+
id_value = select_one("select #{sequence_name}.nextval id from dual")['id'].to_i
|
144
|
+
log(sql, name) {
|
145
|
+
@connection.execute_id_insert(sql,id_value)
|
146
|
+
}
|
147
|
+
end
|
148
|
+
id_value
|
149
|
+
end
|
150
|
+
|
151
|
+
def indexes(table, name = nil)
|
152
|
+
@connection.indexes(table, name, @connection.connection.meta_data.user_name)
|
153
|
+
end
|
154
|
+
|
155
|
+
def _execute(sql, name = nil)
|
156
|
+
case sql.strip
|
157
|
+
when /\A\(?\s*(select|show)/i:
|
158
|
+
@connection.execute_query(sql)
|
159
|
+
else
|
160
|
+
@connection.execute_update(sql)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def modify_types(tp)
|
165
|
+
tp[:primary_key] = "NUMBER(38) NOT NULL PRIMARY KEY"
|
166
|
+
tp[:integer] = { :name => "NUMBER", :limit => 38 }
|
167
|
+
tp[:datetime] = { :name => "DATE" }
|
168
|
+
tp[:timestamp] = { :name => "DATE" }
|
169
|
+
tp[:time] = { :name => "DATE" }
|
170
|
+
tp[:date] = { :name => "DATE" }
|
171
|
+
tp
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
175
|
+
offset = options[:offset] || 0
|
176
|
+
|
177
|
+
if limit = options[:limit]
|
178
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
179
|
+
elsif offset > 0
|
180
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def current_database #:nodoc:
|
185
|
+
select_one("select sys_context('userenv','db_name') db from dual")["db"]
|
186
|
+
end
|
187
|
+
|
188
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
189
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
190
|
+
end
|
191
|
+
|
192
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
193
|
+
execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
|
194
|
+
end
|
195
|
+
|
196
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
197
|
+
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
|
198
|
+
add_column_options!(change_column_sql, options)
|
199
|
+
execute(change_column_sql)
|
200
|
+
end
|
201
|
+
|
202
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
203
|
+
execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
|
204
|
+
end
|
205
|
+
|
206
|
+
def remove_column(table_name, column_name) #:nodoc:
|
207
|
+
execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
|
208
|
+
end
|
209
|
+
|
210
|
+
def structure_dump #:nodoc:
|
211
|
+
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
|
212
|
+
structure << "create sequence #{seq.to_a.first.last};\n\n"
|
213
|
+
end
|
214
|
+
|
215
|
+
select_all("select table_name from user_tables").inject(s) do |structure, table|
|
216
|
+
ddl = "create table #{table.to_a.first.last} (\n "
|
217
|
+
cols = select_all(%Q{
|
218
|
+
select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
|
219
|
+
from user_tab_columns
|
220
|
+
where table_name = '#{table.to_a.first.last}'
|
221
|
+
order by column_id
|
222
|
+
}).map do |row|
|
223
|
+
row = row.inject({}) do |h,args|
|
224
|
+
h[args[0].downcase] = args[1]
|
225
|
+
h
|
226
|
+
end
|
227
|
+
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
|
228
|
+
if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
|
229
|
+
col << "(#{row['data_precision'].to_i}"
|
230
|
+
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
|
231
|
+
col << ')'
|
232
|
+
elsif row['data_type'].include?('CHAR')
|
233
|
+
col << "(#{row['data_length'].to_i})"
|
234
|
+
end
|
235
|
+
col << " default #{row['data_default']}" if !row['data_default'].nil?
|
236
|
+
col << ' not null' if row['nullable'] == 'N'
|
237
|
+
col
|
238
|
+
end
|
239
|
+
ddl << cols.join(",\n ")
|
240
|
+
ddl << ");\n\n"
|
241
|
+
structure << ddl
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def structure_drop #:nodoc:
|
246
|
+
s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
|
247
|
+
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
248
|
+
end
|
249
|
+
|
250
|
+
select_all("select table_name from user_tables").inject(s) do |drop, table|
|
251
|
+
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
256
|
+
#
|
257
|
+
# Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
|
258
|
+
# queries. However, with those columns included in the SELECT DISTINCT list, you
|
259
|
+
# won't actually get a distinct list of the column you want (presuming the column
|
260
|
+
# has duplicates with multiple values for the ordered-by columns. So we use the
|
261
|
+
# FIRST_VALUE function to get a single (first) value for each column, effectively
|
262
|
+
# making every row the same.
|
263
|
+
#
|
264
|
+
# distinct("posts.id", "posts.created_at desc")
|
265
|
+
def distinct(columns, order_by)
|
266
|
+
return "DISTINCT #{columns}" if order_by.blank?
|
267
|
+
|
268
|
+
# construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
|
269
|
+
# FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
|
270
|
+
order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
|
271
|
+
order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
|
272
|
+
"FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
|
273
|
+
end
|
274
|
+
sql = "DISTINCT #{columns}, "
|
275
|
+
sql << order_columns * ", "
|
276
|
+
end
|
277
|
+
|
278
|
+
# ORDER BY clause for the passed order option.
|
279
|
+
#
|
280
|
+
# Uses column aliases as defined by #distinct.
|
281
|
+
def add_order_by_for_association_limiting!(sql, options)
|
282
|
+
return sql if options[:order].blank?
|
283
|
+
|
284
|
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
285
|
+
order.map! {|s| $1 if s =~ / (.*)/}
|
286
|
+
order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
|
287
|
+
|
288
|
+
sql << "ORDER BY #{order}"
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
# QUOTING ==================================================
|
293
|
+
#
|
294
|
+
# see: abstract/quoting.rb
|
295
|
+
|
296
|
+
# camelCase column names need to be quoted; not that anyone using Oracle
|
297
|
+
# would really do this, but handling this case means we pass the test...
|
298
|
+
def quote_column_name(name) #:nodoc:
|
299
|
+
name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : name.to_s
|
300
|
+
end
|
301
|
+
|
302
|
+
def quote_string(string) #:nodoc:
|
303
|
+
string.gsub(/'/, "''")
|
304
|
+
end
|
305
|
+
|
306
|
+
def quote(value, column = nil) #:nodoc:
|
307
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
308
|
+
|
309
|
+
if column && [:text, :binary].include?(column.type)
|
310
|
+
if /(.*?)\([0-9]+\)/ =~ column.sql_type
|
311
|
+
%Q{empty_#{ $1.downcase }()}
|
312
|
+
else
|
313
|
+
%Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
|
314
|
+
end
|
315
|
+
else
|
316
|
+
if column && column.type == :primary_key
|
317
|
+
return value.to_s
|
318
|
+
end
|
319
|
+
case value
|
320
|
+
when String, ActiveSupport::Multibyte::Chars : %Q{'#{quote_string(value)}'}
|
321
|
+
when NilClass : 'null'
|
322
|
+
when TrueClass : '1'
|
323
|
+
when FalseClass : '0'
|
324
|
+
when Numeric : value.to_s
|
325
|
+
when Date, Time : %Q{TIMESTAMP'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
|
326
|
+
else %Q{'#{quote_string(value.to_yaml)}'}
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
private
|
332
|
+
def select(sql, name=nil)
|
333
|
+
records = execute(sql,name)
|
334
|
+
records.each do |col|
|
335
|
+
col.delete('raw_rnum_')
|
336
|
+
end
|
337
|
+
records
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|