activerecord 1.10.1 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +187 -19
- data/RUNNING_UNIT_TESTS +11 -0
- data/lib/active_record.rb +3 -1
- data/lib/active_record/acts/list.rb +25 -14
- data/lib/active_record/acts/nested_set.rb +4 -4
- data/lib/active_record/acts/tree.rb +18 -1
- data/lib/active_record/associations.rb +90 -17
- data/lib/active_record/associations/association_collection.rb +44 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +17 -4
- data/lib/active_record/associations/has_many_association.rb +13 -3
- data/lib/active_record/associations/has_one_association.rb +19 -0
- data/lib/active_record/base.rb +292 -268
- data/lib/active_record/callbacks.rb +14 -14
- data/lib/active_record/connection_adapters/abstract_adapter.rb +137 -75
- data/lib/active_record/connection_adapters/db2_adapter.rb +10 -8
- data/lib/active_record/connection_adapters/mysql_adapter.rb +91 -64
- data/lib/active_record/connection_adapters/oci_adapter.rb +6 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +113 -60
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +15 -12
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +159 -132
- data/lib/active_record/fixtures.rb +59 -12
- data/lib/active_record/locking.rb +10 -9
- data/lib/active_record/migration.rb +112 -5
- data/lib/active_record/query_cache.rb +64 -0
- data/lib/active_record/timestamp.rb +10 -8
- data/lib/active_record/validations.rb +121 -26
- data/rakefile +16 -10
- data/test/aaa_create_tables_test.rb +26 -48
- data/test/abstract_unit.rb +3 -0
- data/test/aggregations_test.rb +19 -19
- data/test/association_callbacks_test.rb +110 -0
- data/test/associations_go_eager_test.rb +48 -14
- data/test/associations_test.rb +344 -142
- data/test/base_test.rb +150 -31
- data/test/binary_test.rb +7 -0
- data/test/callbacks_test.rb +24 -5
- data/test/column_alias_test.rb +2 -2
- data/test/connections/native_sqlserver_odbc/connection.rb +26 -0
- data/test/deprecated_associations_test.rb +27 -28
- data/test/deprecated_finder_test.rb +8 -9
- data/test/finder_test.rb +52 -17
- data/test/fixtures/author.rb +39 -0
- data/test/fixtures/categories.yml +7 -0
- data/test/fixtures/categories_posts.yml +8 -0
- data/test/fixtures/category.rb +2 -0
- data/test/fixtures/comment.rb +3 -1
- data/test/fixtures/comments.yml +43 -1
- data/test/fixtures/companies.yml +14 -0
- data/test/fixtures/company.rb +1 -1
- data/test/fixtures/computers.yml +2 -1
- data/test/fixtures/db_definitions/db2.sql +7 -2
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql.sql +11 -6
- data/test/fixtures/db_definitions/oci.sql +7 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +8 -5
- data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite.sql +9 -4
- data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.sql +12 -7
- data/test/fixtures/developer.rb +8 -1
- data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
- data/test/fixtures/post.rb +8 -2
- data/test/fixtures/posts.yml +21 -0
- data/test/fixtures/project.rb +14 -1
- data/test/fixtures/subscriber.rb +3 -0
- data/test/fixtures_test.rb +14 -0
- data/test/inheritance_test.rb +30 -22
- data/test/lifecycle_test.rb +3 -4
- data/test/locking_test.rb +2 -4
- data/test/migration_test.rb +186 -0
- data/test/mixin_nested_set_test.rb +19 -19
- data/test/mixin_test.rb +88 -88
- data/test/modules_test.rb +5 -10
- data/test/multiple_db_test.rb +2 -0
- data/test/pk_test.rb +8 -12
- data/test/reflection_test.rb +8 -4
- data/test/schema_test_postgresql.rb +63 -0
- data/test/thread_safety_test.rb +4 -1
- data/test/transactions_test.rb +9 -2
- data/test/unconnected_test.rb +1 -0
- data/test/validations_test.rb +151 -8
- metadata +11 -5
- data/test/migration_mysql.rb +0 -104
@@ -52,8 +52,8 @@ begin
|
|
52
52
|
def execute(sql, name = nil)
|
53
53
|
rows_affected = 0
|
54
54
|
|
55
|
-
log(sql, name
|
56
|
-
stmt = DB2::Statement.new(connection)
|
55
|
+
log(sql, name) do
|
56
|
+
stmt = DB2::Statement.new(@connection)
|
57
57
|
stmt.exec_direct(sql)
|
58
58
|
rows_affected = stmt.row_count
|
59
59
|
stmt.free
|
@@ -79,14 +79,16 @@ begin
|
|
79
79
|
@connection.set_auto_commit_on
|
80
80
|
end
|
81
81
|
|
82
|
-
def quote_column_name(
|
82
|
+
def quote_column_name(column_name)
|
83
|
+
column_name
|
84
|
+
end
|
83
85
|
|
84
86
|
def adapter_name()
|
85
87
|
'DB2'
|
86
88
|
end
|
87
89
|
|
88
|
-
def quote_string(
|
89
|
-
|
90
|
+
def quote_string(string)
|
91
|
+
string.gsub(/'/, "''") # ' (for ruby-mode)
|
90
92
|
end
|
91
93
|
|
92
94
|
def add_limit_with_offset!(sql, limit, offset)
|
@@ -124,9 +126,9 @@ begin
|
|
124
126
|
|
125
127
|
def select(sql, name = nil)
|
126
128
|
stmt = nil
|
127
|
-
log(sql, name
|
128
|
-
stmt = DB2::Statement.new(connection)
|
129
|
-
stmt.exec_direct(sql
|
129
|
+
log(sql, name) do
|
130
|
+
stmt = DB2::Statement.new(@connection)
|
131
|
+
stmt.exec_direct("#{sql} with ur")
|
130
132
|
end
|
131
133
|
|
132
134
|
rows = []
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
2
|
require 'parsedate'
|
3
|
-
|
3
|
+
|
4
4
|
module ActiveRecord
|
5
5
|
class Base
|
6
6
|
# Establishes a connection to the database that's used by all Active Record objects.
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
require_library_or_gem 'mysql'
|
12
12
|
rescue LoadError => cannot_require_mysql
|
13
13
|
# Only use the supplied backup Ruby/MySQL driver if no driver is already in place
|
14
|
-
begin
|
14
|
+
begin
|
15
15
|
require 'active_record/vendor/mysql'
|
16
16
|
require 'active_record/vendor/mysql411'
|
17
17
|
rescue LoadError
|
@@ -19,27 +19,27 @@ module ActiveRecord
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
symbolize_strings_in_hash(config)
|
24
|
-
|
24
|
+
|
25
25
|
host = config[:host]
|
26
26
|
port = config[:port]
|
27
27
|
socket = config[:socket]
|
28
28
|
username = config[:username] ? config[:username].to_s : 'root'
|
29
29
|
password = config[:password].to_s
|
30
|
-
|
30
|
+
|
31
31
|
if config.has_key?(:database)
|
32
32
|
database = config[:database]
|
33
33
|
else
|
34
34
|
raise ArgumentError, "No database specified. Missing argument: database."
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
mysql = Mysql.init
|
38
38
|
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
|
39
39
|
ConnectionAdapters::MysqlAdapter.new(mysql.real_connect(host, username, password, database, port, socket), logger, [host, username, password, database, port, socket])
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
module ConnectionAdapters
|
44
44
|
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
|
45
45
|
# the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
|
@@ -57,12 +57,16 @@ module ActiveRecord
|
|
57
57
|
# * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
|
58
58
|
# * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
|
59
59
|
class MysqlAdapter < AbstractAdapter
|
60
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
60
|
+
LOST_CONNECTION_ERROR_MESSAGES = [
|
61
61
|
"Server shutdown in progress",
|
62
|
-
"Broken pipe",
|
63
|
-
"Lost connection to MySQL server during query",
|
62
|
+
"Broken pipe",
|
63
|
+
"Lost connection to MySQL server during query",
|
64
64
|
"MySQL server has gone away"
|
65
65
|
]
|
66
|
+
|
67
|
+
def supports_migrations?
|
68
|
+
true
|
69
|
+
end
|
66
70
|
|
67
71
|
def native_database_types
|
68
72
|
{
|
@@ -84,86 +88,85 @@ module ActiveRecord
|
|
84
88
|
super(connection, logger)
|
85
89
|
@connection_options = connection_options
|
86
90
|
end
|
87
|
-
|
91
|
+
|
88
92
|
def adapter_name
|
89
93
|
'MySQL'
|
90
94
|
end
|
91
95
|
|
92
|
-
|
93
96
|
def select_all(sql, name = nil)
|
94
97
|
select(sql, name)
|
95
98
|
end
|
96
|
-
|
99
|
+
|
97
100
|
def select_one(sql, name = nil)
|
98
101
|
result = select(sql, name)
|
99
102
|
result.nil? ? nil : result.first
|
100
103
|
end
|
101
|
-
|
104
|
+
|
102
105
|
def columns(table_name, name = nil)
|
103
|
-
sql = "SHOW FIELDS FROM #{table_name}"
|
106
|
+
sql = "SHOW FIELDS FROM #{table_name}"
|
104
107
|
columns = []
|
105
108
|
execute(sql, name).each { |field| columns << Column.new(field[0], field[4], field[1]) }
|
106
109
|
columns
|
107
110
|
end
|
108
|
-
|
111
|
+
|
109
112
|
def insert(sql, name = nil, pk = nil, id_value = nil)
|
110
113
|
execute(sql, name = nil)
|
111
|
-
|
114
|
+
id_value || @connection.insert_id
|
112
115
|
end
|
113
|
-
|
114
|
-
def execute(sql, name = nil)
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
|
117
|
+
def execute(sql, name = nil, retries = 2)
|
118
|
+
unless @logger
|
119
|
+
@connection.query(sql)
|
120
|
+
else
|
121
|
+
log(sql, name) { @connection.query(sql) }
|
122
|
+
end
|
123
|
+
rescue ActiveRecord::StatementInvalid => exception
|
124
|
+
if LOST_CONNECTION_ERROR_MESSAGES.any? { |msg| exception.message.split(":").first =~ /^#{msg}/ }
|
125
|
+
@connection.real_connect(*@connection_options)
|
126
|
+
unless @logger
|
127
|
+
@connection.query(sql)
|
122
128
|
else
|
123
|
-
|
129
|
+
@logger.info "Retrying invalid statement with reopened connection"
|
130
|
+
log(sql, name) { @connection.query(sql) }
|
124
131
|
end
|
132
|
+
else
|
133
|
+
raise
|
125
134
|
end
|
126
135
|
end
|
127
|
-
|
136
|
+
|
128
137
|
def update(sql, name = nil)
|
129
138
|
execute(sql, name)
|
130
139
|
@connection.affected_rows
|
131
140
|
end
|
132
|
-
|
141
|
+
|
133
142
|
alias_method :delete, :update
|
134
|
-
|
135
|
-
|
143
|
+
|
144
|
+
|
136
145
|
def begin_db_transaction
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
# Transactions aren't supported
|
141
|
-
end
|
146
|
+
execute "BEGIN"
|
147
|
+
rescue Exception
|
148
|
+
# Transactions aren't supported
|
142
149
|
end
|
143
|
-
|
150
|
+
|
144
151
|
def commit_db_transaction
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
# Transactions aren't supported
|
149
|
-
end
|
152
|
+
execute "COMMIT"
|
153
|
+
rescue Exception
|
154
|
+
# Transactions aren't supported
|
150
155
|
end
|
151
|
-
|
156
|
+
|
152
157
|
def rollback_db_transaction
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
# Transactions aren't supported
|
157
|
-
end
|
158
|
+
execute "ROLLBACK"
|
159
|
+
rescue Exception
|
160
|
+
# Transactions aren't supported
|
158
161
|
end
|
159
162
|
|
160
|
-
|
163
|
+
|
161
164
|
def quote_column_name(name)
|
162
|
-
|
165
|
+
"`#{name}`"
|
163
166
|
end
|
164
|
-
|
165
|
-
def quote_string(
|
166
|
-
Mysql::quote(
|
167
|
+
|
168
|
+
def quote_string(string)
|
169
|
+
Mysql::quote(string)
|
167
170
|
end
|
168
171
|
|
169
172
|
|
@@ -172,39 +175,63 @@ module ActiveRecord
|
|
172
175
|
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
|
173
176
|
end
|
174
177
|
end
|
175
|
-
|
176
|
-
def
|
177
|
-
|
178
|
+
|
179
|
+
def add_limit_offset!(sql, options)
|
180
|
+
if options[:limit]
|
181
|
+
if options[:offset].blank?
|
182
|
+
sql << " LIMIT #{options[:limit]}"
|
183
|
+
else
|
184
|
+
sql << " LIMIT #{options[:offset]}, #{options[:limit]}"
|
185
|
+
end
|
186
|
+
end
|
178
187
|
end
|
179
|
-
|
188
|
+
|
180
189
|
def recreate_database(name)
|
181
190
|
drop_database(name)
|
182
191
|
create_database(name)
|
183
192
|
end
|
184
|
-
|
193
|
+
|
185
194
|
def drop_database(name)
|
186
195
|
execute "DROP DATABASE IF EXISTS #{name}"
|
187
196
|
end
|
188
|
-
|
197
|
+
|
189
198
|
def create_database(name)
|
190
199
|
execute "CREATE DATABASE #{name}"
|
191
200
|
end
|
192
|
-
|
193
201
|
|
194
|
-
def
|
195
|
-
|
202
|
+
def change_column_default(table_name, column_name, default)
|
203
|
+
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
204
|
+
|
205
|
+
change_column(table_name, column_name, current_type, { :default => default })
|
206
|
+
end
|
207
|
+
|
208
|
+
def change_column(table_name, column_name, type, options = {})
|
209
|
+
options[:default] ||= select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
|
210
|
+
|
211
|
+
change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
|
212
|
+
add_column_options!(change_column_sql, options)
|
213
|
+
execute(change_column_sql)
|
214
|
+
end
|
215
|
+
|
216
|
+
def rename_column(table_name, column_name, new_column_name)
|
217
|
+
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
218
|
+
execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def create_table(name, options = {})
|
222
|
+
super(name, {:options => "ENGINE=InnoDB"}.merge(options))
|
196
223
|
end
|
197
224
|
|
198
225
|
private
|
199
226
|
def select(sql, name = nil)
|
200
|
-
result = nil
|
201
227
|
@connection.query_with_result = true
|
202
228
|
result = execute(sql, name)
|
203
229
|
rows = []
|
204
230
|
all_fields_initialized = result.fetch_fields.inject({}) { |all_fields, f| all_fields[f.name] = nil; all_fields }
|
205
231
|
result.each_hash { |row| rows << all_fields_initialized.dup.update(row) }
|
232
|
+
result.free
|
206
233
|
rows
|
207
234
|
end
|
208
235
|
end
|
209
236
|
end
|
210
|
-
end
|
237
|
+
end
|
@@ -99,8 +99,8 @@ begin
|
|
99
99
|
# * <tt>:password</tt> -- Defaults to nothing
|
100
100
|
# * <tt>:host</tt> -- Defaults to localhost
|
101
101
|
class OCIAdapter < AbstractAdapter
|
102
|
-
def quote_string(
|
103
|
-
|
102
|
+
def quote_string(string)
|
103
|
+
string.gsub(/'/, "''")
|
104
104
|
end
|
105
105
|
|
106
106
|
def quote(value, column = nil)
|
@@ -125,7 +125,7 @@ begin
|
|
125
125
|
elsif offset > 0
|
126
126
|
sql = "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
127
127
|
end
|
128
|
-
cursor = log(sql, name
|
128
|
+
cursor = log(sql, name) { @connection.exec sql }
|
129
129
|
cols = cursor.get_col_names.map { |x| x.downcase }
|
130
130
|
rows = []
|
131
131
|
while row = cursor.fetch
|
@@ -167,16 +167,16 @@ begin
|
|
167
167
|
if pk.nil? # Who called us? What does the sql look like? No idea!
|
168
168
|
execute sql, name
|
169
169
|
elsif id_value # Pre-assigned id
|
170
|
-
log(sql, name
|
170
|
+
log(sql, name) { @connection.exec sql }
|
171
171
|
else # Assume the sql contains a bind-variable for the id
|
172
172
|
id_value = select_one("select rails_sequence.nextval id from dual")['id']
|
173
|
-
log(sql, name
|
173
|
+
log(sql, name) { @connection.exec sql, id_value }
|
174
174
|
end
|
175
175
|
id_value
|
176
176
|
end
|
177
177
|
|
178
178
|
def execute(sql, name = nil)
|
179
|
-
log(sql, name
|
179
|
+
log(sql, name) { @connection.exec sql }
|
180
180
|
end
|
181
181
|
|
182
182
|
alias :update :execute
|
@@ -24,7 +24,8 @@ module ActiveRecord
|
|
24
24
|
username = config[:username].to_s
|
25
25
|
password = config[:password].to_s
|
26
26
|
|
27
|
-
|
27
|
+
encoding = config[:encoding]
|
28
|
+
min_messages = config[:min_messages]
|
28
29
|
|
29
30
|
if config.has_key?(:database)
|
30
31
|
database = config[:database]
|
@@ -36,7 +37,9 @@ module ActiveRecord
|
|
36
37
|
PGconn.connect(host, port, "", "", database, username, password), logger
|
37
38
|
)
|
38
39
|
|
39
|
-
pga.
|
40
|
+
pga.schema_search_path = config[:schema_search_path] || config[:schema_order]
|
41
|
+
pga.execute("SET client_encoding TO '#{encoding}'") if encoding
|
42
|
+
pga.execute("SET client_min_messages TO '#{min_messages}'") if min_messages
|
40
43
|
|
41
44
|
pga
|
42
45
|
end
|
@@ -53,8 +56,30 @@ module ActiveRecord
|
|
53
56
|
# * <tt>:username</tt> -- Defaults to nothing
|
54
57
|
# * <tt>:password</tt> -- Defaults to nothing
|
55
58
|
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
56
|
-
# * <tt>:
|
59
|
+
# * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
|
60
|
+
# * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
|
61
|
+
# * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
|
57
62
|
class PostgreSQLAdapter < AbstractAdapter
|
63
|
+
def native_database_types
|
64
|
+
{
|
65
|
+
:primary_key => "serial primary key",
|
66
|
+
:string => { :name => "character varying", :limit => 255 },
|
67
|
+
:text => { :name => "text" },
|
68
|
+
:integer => { :name => "integer" },
|
69
|
+
:float => { :name => "float" },
|
70
|
+
:datetime => { :name => "timestamp" },
|
71
|
+
:timestamp => { :name => "timestamp" },
|
72
|
+
:time => { :name => "timestamp" },
|
73
|
+
:date => { :name => "date" },
|
74
|
+
:binary => { :name => "bytea" },
|
75
|
+
:boolean => { :name => "boolean"}
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def supports_migrations?
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
58
83
|
def select_all(sql, name = nil)
|
59
84
|
select(sql, name)
|
60
85
|
end
|
@@ -65,26 +90,27 @@ module ActiveRecord
|
|
65
90
|
end
|
66
91
|
|
67
92
|
def columns(table_name, name = nil)
|
68
|
-
|
69
|
-
|
70
|
-
columns
|
93
|
+
column_definitions(table_name).collect do |name, type, default|
|
94
|
+
Column.new(name, default_value(default), translate_field_type(type))
|
71
95
|
end
|
72
96
|
end
|
73
97
|
|
74
98
|
def insert(sql, name = nil, pk = nil, id_value = nil)
|
75
|
-
execute(sql, name
|
99
|
+
execute(sql, name)
|
76
100
|
table = sql.split(" ", 4)[2]
|
77
101
|
return id_value || last_insert_id(table, pk)
|
78
102
|
end
|
79
103
|
|
104
|
+
def query(sql, name = nil)
|
105
|
+
log(sql, name) { @connection.query(sql) }
|
106
|
+
end
|
107
|
+
|
80
108
|
def execute(sql, name = nil)
|
81
|
-
log(sql, name
|
109
|
+
log(sql, name) { @connection.exec(sql) }
|
82
110
|
end
|
83
111
|
|
84
112
|
def update(sql, name = nil)
|
85
|
-
|
86
|
-
log(sql, name, @connection) { |connection| result = connection.exec(sql) }
|
87
|
-
result.cmdtuples
|
113
|
+
execute(sql, name).cmdtuples
|
88
114
|
end
|
89
115
|
|
90
116
|
alias_method :delete, :update
|
@@ -102,23 +128,55 @@ module ActiveRecord
|
|
102
128
|
end
|
103
129
|
|
104
130
|
def quote_column_name(name)
|
105
|
-
|
131
|
+
%("#{name}")
|
106
132
|
end
|
107
133
|
|
108
|
-
def adapter_name
|
134
|
+
def adapter_name
|
109
135
|
'PostgreSQL'
|
110
136
|
end
|
111
137
|
|
138
|
+
|
139
|
+
# Set the schema search path to a string of comma-separated schema names.
|
140
|
+
# Names beginning with $ are quoted (e.g. $user => '$user')
|
141
|
+
# See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
|
142
|
+
def schema_search_path=(schema_csv)
|
143
|
+
if schema_csv
|
144
|
+
execute "SET search_path TO #{schema_csv}"
|
145
|
+
@schema_search_path = nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def schema_search_path
|
150
|
+
@schema_search_path ||= query('SHOW search_path')[0][0]
|
151
|
+
end
|
152
|
+
|
153
|
+
def change_column(table_name, column_name, type, options = {})
|
154
|
+
execute = "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type}"
|
155
|
+
change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
|
156
|
+
end
|
157
|
+
|
158
|
+
def change_column_default(table_name, column_name, default)
|
159
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
|
160
|
+
end
|
161
|
+
|
162
|
+
def rename_column(table_name, column_name, new_column_name)
|
163
|
+
execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
|
164
|
+
end
|
165
|
+
|
166
|
+
def remove_index(table_name, column_name)
|
167
|
+
execute "DROP INDEX #{table_name}_#{column_name}_index"
|
168
|
+
end
|
169
|
+
|
112
170
|
private
|
171
|
+
BYTEA_COLUMN_TYPE_OID = 17
|
172
|
+
|
113
173
|
def last_insert_id(table, column = "id")
|
114
174
|
sequence_name = "#{table}_#{column || 'id'}_seq"
|
115
175
|
@connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
|
116
176
|
end
|
117
177
|
|
118
178
|
def select(sql, name = nil)
|
119
|
-
res =
|
120
|
-
log(sql, name, @connection) { |connection| res = connection.exec(sql) }
|
121
|
-
|
179
|
+
res = execute(sql, name)
|
122
180
|
results = res.result
|
123
181
|
rows = []
|
124
182
|
if results.length > 0
|
@@ -127,7 +185,7 @@ module ActiveRecord
|
|
127
185
|
hashed_row = {}
|
128
186
|
row.each_index do |cel_index|
|
129
187
|
column = row[cel_index]
|
130
|
-
if res.type(cel_index) ==
|
188
|
+
if res.type(cel_index) == BYTEA_COLUMN_TYPE_OID
|
131
189
|
column = unescape_bytea(column)
|
132
190
|
end
|
133
191
|
hashed_row[fields[cel_index]] = column
|
@@ -150,54 +208,49 @@ module ActiveRecord
|
|
150
208
|
s.gsub(/\\([0-9][0-9][0-9])/) { $1.oct.chr }.gsub(/\\\\/) { '\\' } unless s.nil?
|
151
209
|
end
|
152
210
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
211
|
+
# Query a table's column names, default values, and types.
|
212
|
+
#
|
213
|
+
# The underlying query is roughly:
|
214
|
+
# SELECT column.name, column.type, default.value
|
215
|
+
# FROM column LEFT JOIN default
|
216
|
+
# ON column.table_id = default.table_id
|
217
|
+
# AND column.num = default.column_num
|
218
|
+
# WHERE column.table_id = get_table_id('table_name')
|
219
|
+
# AND column.num > 0
|
220
|
+
# AND NOT column.is_dropped
|
221
|
+
# ORDER BY column.num
|
222
|
+
#
|
223
|
+
# If the table name is not prefixed with a schema, the database will
|
224
|
+
# take the first match from the schema search path.
|
225
|
+
#
|
226
|
+
# Query implementation notes:
|
227
|
+
# - format_type includes the column size constraint, e.g. varchar(50)
|
228
|
+
# - ::regclass is a function that gives the id for a table name
|
229
|
+
def column_definitions(table_name)
|
230
|
+
query <<-end_sql
|
231
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc
|
232
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
233
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
234
|
+
WHERE a.attrelid = '#{table_name}'::regclass
|
235
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
236
|
+
ORDER BY a.attnum
|
237
|
+
end_sql
|
161
238
|
end
|
162
239
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
#
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
column_defaults = nil
|
175
|
-
log(sql, nil, @connection) { |connection| column_defaults = connection.query(sql) }
|
176
|
-
column_defaults.collect do |row|
|
177
|
-
field = row[0]
|
178
|
-
type = type_as_string(row[3], row[2])
|
179
|
-
default = default_value(row[1])
|
180
|
-
length = row[2]
|
181
|
-
|
182
|
-
[field, type, default, length]
|
240
|
+
# Translate PostgreSQL-specific types into simplified SQL types.
|
241
|
+
# These are special cases; standard types are handled by
|
242
|
+
# ConnectionAdapters::Column#simplified_type.
|
243
|
+
def translate_field_type(field_type)
|
244
|
+
# Match the beginning of field_type since it may have a size constraint on the end.
|
245
|
+
case field_type
|
246
|
+
when /^timestamp/i then 'datetime'
|
247
|
+
when /^real|^money/i then 'float'
|
248
|
+
when /^interval/i then 'string'
|
249
|
+
when /^bytea/i then 'binary'
|
250
|
+
else field_type # Pass through standard types.
|
183
251
|
end
|
184
252
|
end
|
185
253
|
|
186
|
-
def type_as_string(field_type, field_length)
|
187
|
-
type = case field_type
|
188
|
-
when 'numeric', 'real', 'money' then 'float'
|
189
|
-
when 'character varying', 'interval' then 'string'
|
190
|
-
when 'timestamp without time zone' then 'datetime'
|
191
|
-
when 'timestamp with time zone' then 'datetime'
|
192
|
-
when 'bytea' then 'binary'
|
193
|
-
else field_type
|
194
|
-
end
|
195
|
-
|
196
|
-
size = field_length.nil? ? "" : "(#{field_length})"
|
197
|
-
|
198
|
-
return type + size
|
199
|
-
end
|
200
|
-
|
201
254
|
def default_value(value)
|
202
255
|
# Boolean types
|
203
256
|
return "t" if value =~ /true/i
|