activerecord 1.11.1 → 1.12.1
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 +198 -0
- data/lib/active_record.rb +19 -14
- data/lib/active_record/acts/list.rb +8 -6
- data/lib/active_record/acts/tree.rb +33 -10
- data/lib/active_record/aggregations.rb +1 -7
- data/lib/active_record/associations.rb +151 -82
- data/lib/active_record/associations/association_collection.rb +25 -0
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +19 -5
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
- data/lib/active_record/associations/has_many_association.rb +6 -14
- data/lib/active_record/associations/has_one_association.rb +5 -3
- data/lib/active_record/base.rb +344 -130
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
- data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
- data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
- data/lib/active_record/fixtures.rb +47 -24
- data/lib/active_record/migration.rb +34 -5
- data/lib/active_record/observer.rb +32 -2
- data/lib/active_record/query_cache.rb +12 -11
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +84 -0
- data/lib/active_record/transactions.rb +1 -3
- data/lib/active_record/validations.rb +40 -26
- data/lib/active_record/vendor/mysql.rb +6 -0
- data/lib/active_record/version.rb +9 -0
- data/rakefile +5 -16
- data/test/abstract_unit.rb +6 -11
- data/test/adapter_test.rb +58 -0
- data/test/ar_schema_test.rb +33 -0
- data/test/association_callbacks_test.rb +14 -0
- data/test/associations_go_eager_test.rb +56 -14
- data/test/associations_test.rb +245 -25
- data/test/base_test.rb +205 -34
- data/test/binary_test.rb +25 -42
- data/test/callbacks_test.rb +75 -0
- data/test/conditions_scoping_test.rb +136 -0
- data/test/connections/native_mysql/connection.rb +0 -4
- data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
- data/test/copy_table_sqlite.rb +64 -0
- data/test/deprecated_associations_test.rb +7 -6
- data/test/deprecated_finder_test.rb +3 -3
- data/test/finder_test.rb +33 -3
- data/test/fixtures/accounts.yml +5 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/category.rb +11 -1
- data/test/fixtures/comment.rb +22 -2
- data/test/fixtures/comments.yml +6 -0
- data/test/fixtures/companies.yml +15 -0
- data/test/fixtures/company.rb +24 -1
- data/test/fixtures/db_definitions/db2.drop.sql +5 -1
- data/test/fixtures/db_definitions/db2.sql +15 -1
- data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql.sql +17 -2
- data/test/fixtures/db_definitions/oci.drop.sql +37 -5
- data/test/fixtures/db_definitions/oci.sql +47 -4
- data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
- data/test/fixtures/db_definitions/oci2.sql +2 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +33 -4
- data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite.sql +16 -2
- data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver.sql +16 -2
- data/test/fixtures/developer.rb +1 -1
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/mixins.yml +11 -1
- data/test/fixtures/order.rb +4 -0
- data/test/fixtures/post.rb +4 -0
- data/test/fixtures/posts.yml +7 -0
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +2 -4
- data/test/fixtures/topics.yml +2 -2
- data/test/fixtures_test.rb +79 -7
- data/test/inheritance_test.rb +2 -2
- data/test/lifecycle_test.rb +14 -6
- data/test/migration_test.rb +164 -6
- data/test/mixin_test.rb +78 -2
- data/test/pk_test.rb +25 -1
- data/test/readonly_test.rb +31 -0
- data/test/reflection_test.rb +4 -1
- data/test/schema_dumper_test.rb +19 -0
- data/test/schema_test_postgresql.rb +3 -2
- data/test/synonym_test_oci.rb +17 -0
- data/test/threaded_connections_test.rb +2 -1
- data/test/transactions_test.rb +109 -10
- data/test/validations_test.rb +70 -42
- metadata +25 -5
- data/test/fixtures/associations.png +0 -0
- data/test/thread_safety_test.rb +0 -36
@@ -0,0 +1,245 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module SchemaStatements
|
4
|
+
# Returns a Hash of mappings from the abstract data types to the native
|
5
|
+
# database types. See TableDefinition#column for details on the recognized
|
6
|
+
# abstract data types.
|
7
|
+
def native_database_types
|
8
|
+
{}
|
9
|
+
end
|
10
|
+
|
11
|
+
# def tables(name = nil) end
|
12
|
+
|
13
|
+
# Returns an array of indexes for the given table.
|
14
|
+
# def indexes(table_name, name = nil) end
|
15
|
+
|
16
|
+
# Returns an array of Column objects for the table specified by +table_name+.
|
17
|
+
# See the concrete implementation for details on the expected parameter values.
|
18
|
+
def columns(table_name, name = nil) end
|
19
|
+
|
20
|
+
# Creates a new table
|
21
|
+
# There are two ways to work with #create_table. You can use the block
|
22
|
+
# form or the regular form, like this:
|
23
|
+
#
|
24
|
+
# === Block form
|
25
|
+
# # create_table() yields a TableDefinition instance
|
26
|
+
# create_table(:suppliers) do |t|
|
27
|
+
# t.column :name, :string, :limit => 60
|
28
|
+
# # Other fields here
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# === Regular form
|
32
|
+
# create_table(:suppliers)
|
33
|
+
# add_column(:suppliers, :name, :string, {:limit => 60})
|
34
|
+
#
|
35
|
+
# The +options+ hash can include the following keys:
|
36
|
+
# [<tt>:id</tt>]
|
37
|
+
# Set to true or false to add/not add a primary key column
|
38
|
+
# automatically. Defaults to true.
|
39
|
+
# [<tt>:primary_key</tt>]
|
40
|
+
# The name of the primary key, if one is to be added automatically.
|
41
|
+
# Defaults to +id+.
|
42
|
+
# [<tt>:options</tt>]
|
43
|
+
# Any extra options you want appended to the table definition.
|
44
|
+
# [<tt>:temporary</tt>]
|
45
|
+
# Make a temporary table.
|
46
|
+
#
|
47
|
+
# ===== Examples
|
48
|
+
# ====== Add a backend specific option to the generated SQL (MySQL)
|
49
|
+
# create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
|
50
|
+
# generates:
|
51
|
+
# CREATE TABLE suppliers (
|
52
|
+
# id int(11) DEFAULT NULL auto_increment PRIMARY KEY
|
53
|
+
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
54
|
+
#
|
55
|
+
# ====== Rename the primary key column
|
56
|
+
# create_table(:objects, :primary_key => 'guid') do |t|
|
57
|
+
# t.column :name, :string, :limit => 80
|
58
|
+
# end
|
59
|
+
# generates:
|
60
|
+
# CREATE TABLE objects (
|
61
|
+
# guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
|
62
|
+
# name varchar(80)
|
63
|
+
# )
|
64
|
+
#
|
65
|
+
# ====== Do not add a primary key column
|
66
|
+
# create_table(:categories_suppliers, :id => false) do |t|
|
67
|
+
# t.column :category_id, :integer
|
68
|
+
# t.column :supplier_id, :integer
|
69
|
+
# end
|
70
|
+
# generates:
|
71
|
+
# CREATE TABLE categories_suppliers_join (
|
72
|
+
# category_id int,
|
73
|
+
# supplier_id int
|
74
|
+
# )
|
75
|
+
#
|
76
|
+
# See also TableDefinition#column for details on how to create columns.
|
77
|
+
def create_table(name, options = {})
|
78
|
+
table_definition = TableDefinition.new(self)
|
79
|
+
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
80
|
+
|
81
|
+
yield table_definition
|
82
|
+
|
83
|
+
if options[:force]
|
84
|
+
drop_table(name) rescue nil
|
85
|
+
end
|
86
|
+
|
87
|
+
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
88
|
+
create_sql << "#{name} ("
|
89
|
+
create_sql << table_definition.to_sql
|
90
|
+
create_sql << ") #{options[:options]}"
|
91
|
+
execute create_sql
|
92
|
+
end
|
93
|
+
|
94
|
+
# Renames a table.
|
95
|
+
# ===== Example
|
96
|
+
# rename_table('octopuses', 'octopi')
|
97
|
+
def rename_table(name, new_name)
|
98
|
+
raise NotImplementedError, "rename_table is not implemented"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Drops a table from the database.
|
102
|
+
def drop_table(name)
|
103
|
+
execute "DROP TABLE #{name}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Adds a new column to the named table.
|
107
|
+
# See TableDefinition#column for details of the options you can use.
|
108
|
+
def add_column(table_name, column_name, type, options = {})
|
109
|
+
add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
|
110
|
+
add_column_options!(add_column_sql, options)
|
111
|
+
execute(add_column_sql)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Removes the column from the table definition.
|
115
|
+
# ===== Examples
|
116
|
+
# remove_column(:suppliers, :qualification)
|
117
|
+
def remove_column(table_name, column_name)
|
118
|
+
execute "ALTER TABLE #{table_name} DROP #{column_name}"
|
119
|
+
end
|
120
|
+
|
121
|
+
# Changes the column's definition according to the new options.
|
122
|
+
# See TableDefinition#column for details of the options you can use.
|
123
|
+
# ===== Examples
|
124
|
+
# change_column(:suppliers, :name, :string, :limit => 80)
|
125
|
+
# change_column(:accounts, :description, :text)
|
126
|
+
def change_column(table_name, column_name, type, options = {})
|
127
|
+
raise NotImplementedError, "change_column is not implemented"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Sets a new default value for a column. If you want to set the default
|
131
|
+
# value to +NULL+, you are out of luck. You need to
|
132
|
+
# DatabaseStatements#execute the apppropriate SQL statement yourself.
|
133
|
+
# ===== Examples
|
134
|
+
# change_column_default(:suppliers, :qualification, 'new')
|
135
|
+
# change_column_default(:accounts, :authorized, 1)
|
136
|
+
def change_column_default(table_name, column_name, default)
|
137
|
+
raise NotImplementedError, "change_column_default is not implemented"
|
138
|
+
end
|
139
|
+
|
140
|
+
# Renames a column.
|
141
|
+
# ===== Example
|
142
|
+
# rename_column(:suppliers, :description, :name)
|
143
|
+
def rename_column(table_name, column_name, new_column_name)
|
144
|
+
raise NotImplementedError, "rename_column is not implemented"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Adds a new index to the table. +column_name+ can be a single Symbol, or
|
148
|
+
# an Array of Symbols.
|
149
|
+
#
|
150
|
+
# The index will be named after the table and the first column names,
|
151
|
+
# unless you pass +:name+ as an option.
|
152
|
+
#
|
153
|
+
# ===== Examples
|
154
|
+
# ====== Creating a simple index
|
155
|
+
# add_index(:suppliers, :name)
|
156
|
+
# generates
|
157
|
+
# CREATE INDEX suppliers_name_index ON suppliers(name)
|
158
|
+
# ====== Creating a unique index
|
159
|
+
# add_index(:accounts, [:branch_id, :party_id], :unique => true)
|
160
|
+
# generates
|
161
|
+
# CREATE UNIQUE INDEX accounts_branch_id_index ON accounts(branch_id, party_id)
|
162
|
+
# ====== Creating a named index
|
163
|
+
# add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
|
164
|
+
# generates
|
165
|
+
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
|
166
|
+
def add_index(table_name, column_name, options = {})
|
167
|
+
index_name = "#{table_name}_#{Array(column_name).first}_index"
|
168
|
+
|
169
|
+
if Hash === options # legacy support, since this param was a string
|
170
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
171
|
+
index_name = options[:name] || index_name
|
172
|
+
else
|
173
|
+
index_type = options
|
174
|
+
end
|
175
|
+
|
176
|
+
execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
|
177
|
+
end
|
178
|
+
|
179
|
+
# Remove the given index from the table.
|
180
|
+
#
|
181
|
+
# Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
|
182
|
+
# remove_index :suppliers, :name
|
183
|
+
# Remove the index named accounts_branch_id in the accounts table.
|
184
|
+
# remove_index :accounts, :column => :branch_id
|
185
|
+
# Remove the index named by_branch_party in the accounts table.
|
186
|
+
# remove_index :accounts, :name => :by_branch_party
|
187
|
+
def remove_index(table_name, options = {})
|
188
|
+
if Hash === options # legacy support
|
189
|
+
if options[:column]
|
190
|
+
index_name = "#{table_name}_#{options[:column]}_index"
|
191
|
+
elsif options[:name]
|
192
|
+
index_name = options[:name]
|
193
|
+
else
|
194
|
+
raise ArgumentError, "You must specify the index name"
|
195
|
+
end
|
196
|
+
else
|
197
|
+
index_name = "#{table_name}_#{options}_index"
|
198
|
+
end
|
199
|
+
|
200
|
+
execute "DROP INDEX #{index_name} ON #{table_name}"
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
|
205
|
+
# entire structure of the database.
|
206
|
+
def structure_dump
|
207
|
+
end
|
208
|
+
|
209
|
+
# Should not be called normally, but this operation is non-destructive.
|
210
|
+
# The migrations module handles this automatically.
|
211
|
+
def initialize_schema_information
|
212
|
+
begin
|
213
|
+
execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
|
214
|
+
execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
|
215
|
+
rescue ActiveRecord::StatementInvalid
|
216
|
+
# Schema has been intialized
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def dump_schema_information #:nodoc:
|
221
|
+
begin
|
222
|
+
if (current_schema = ActiveRecord::Migrator.current_version) > 0
|
223
|
+
return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema});"
|
224
|
+
end
|
225
|
+
rescue ActiveRecord::StatementInvalid
|
226
|
+
# No Schema Info
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def type_to_sql(type, limit = nil) #:nodoc:
|
232
|
+
native = native_database_types[type]
|
233
|
+
limit ||= native[:limit]
|
234
|
+
column_type_sql = native[:name]
|
235
|
+
column_type_sql << "(#{limit})" if limit
|
236
|
+
column_type_sql
|
237
|
+
end
|
238
|
+
|
239
|
+
def add_column_options!(sql, options) #:nodoc:
|
240
|
+
sql << " NOT NULL" if options[:null] == false
|
241
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -1,444 +1,49 @@
|
|
1
1
|
require 'benchmark'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
begin
|
10
|
-
require library_name
|
11
|
-
rescue LoadError => cannot_require
|
12
|
-
# 1. Requiring the module is unsuccessful, maybe it's a gem and nobody required rubygems yet. Try.
|
13
|
-
begin
|
14
|
-
require 'rubygems'
|
15
|
-
rescue LoadError => rubygems_not_installed
|
16
|
-
raise cannot_require
|
17
|
-
end
|
18
|
-
# 2. Rubygems is installed and loaded. Try to load the library again
|
19
|
-
begin
|
20
|
-
require library_name
|
21
|
-
rescue LoadError => gem_not_installed
|
22
|
-
raise cannot_require
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
4
|
+
require 'active_record/connection_adapters/abstract/schema_definitions'
|
5
|
+
require 'active_record/connection_adapters/abstract/schema_statements'
|
6
|
+
require 'active_record/connection_adapters/abstract/database_statements'
|
7
|
+
require 'active_record/connection_adapters/abstract/quoting'
|
8
|
+
require 'active_record/connection_adapters/abstract/connection_specification'
|
26
9
|
|
27
10
|
module ActiveRecord
|
28
|
-
class Base
|
29
|
-
class ConnectionSpecification #:nodoc:
|
30
|
-
attr_reader :config, :adapter_method
|
31
|
-
def initialize (config, adapter_method)
|
32
|
-
@config, @adapter_method = config, adapter_method
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# The class -> [adapter_method, config] map
|
37
|
-
@@defined_connections = {}
|
38
|
-
|
39
|
-
# Establishes the connection to the database. Accepts a hash as input where
|
40
|
-
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
41
|
-
# example for regular databases (MySQL, Postgresql, etc):
|
42
|
-
#
|
43
|
-
# ActiveRecord::Base.establish_connection(
|
44
|
-
# :adapter => "mysql",
|
45
|
-
# :host => "localhost",
|
46
|
-
# :username => "myuser",
|
47
|
-
# :password => "mypass",
|
48
|
-
# :database => "somedatabase"
|
49
|
-
# )
|
50
|
-
#
|
51
|
-
# Example for SQLite database:
|
52
|
-
#
|
53
|
-
# ActiveRecord::Base.establish_connection(
|
54
|
-
# :adapter => "sqlite",
|
55
|
-
# :dbfile => "path/to/dbfile"
|
56
|
-
# )
|
57
|
-
#
|
58
|
-
# Also accepts keys as strings (for parsing from yaml for example):
|
59
|
-
# ActiveRecord::Base.establish_connection(
|
60
|
-
# "adapter" => "sqlite",
|
61
|
-
# "dbfile" => "path/to/dbfile"
|
62
|
-
# )
|
63
|
-
#
|
64
|
-
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
65
|
-
# may be returned on an error.
|
66
|
-
def self.establish_connection(spec = nil)
|
67
|
-
case spec
|
68
|
-
when nil
|
69
|
-
raise AdapterNotSpecified unless defined? RAILS_ENV
|
70
|
-
establish_connection(RAILS_ENV)
|
71
|
-
when ConnectionSpecification
|
72
|
-
@@defined_connections[self] = spec
|
73
|
-
when Symbol, String
|
74
|
-
if configuration = configurations[spec.to_s]
|
75
|
-
establish_connection(configuration)
|
76
|
-
else
|
77
|
-
raise AdapterNotSpecified, "#{spec} database is not configured"
|
78
|
-
end
|
79
|
-
else
|
80
|
-
spec = spec.symbolize_keys
|
81
|
-
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
82
|
-
adapter_method = "#{spec[:adapter]}_connection"
|
83
|
-
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
|
84
|
-
remove_connection
|
85
|
-
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def self.active_connections #:nodoc:
|
90
|
-
if threaded_connections
|
91
|
-
Thread.current['active_connections'] ||= {}
|
92
|
-
else
|
93
|
-
@@active_connections ||= {}
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# Locate the connection of the nearest super class. This can be an
|
98
|
-
# active or defined connections: if it is the latter, it will be
|
99
|
-
# opened and set as the active connection for the class it was defined
|
100
|
-
# for (not necessarily the current class).
|
101
|
-
def self.retrieve_connection #:nodoc:
|
102
|
-
klass = self
|
103
|
-
ar_super = ActiveRecord::Base.superclass
|
104
|
-
until klass == ar_super
|
105
|
-
if conn = active_connections[klass]
|
106
|
-
return conn
|
107
|
-
elsif conn = @@defined_connections[klass]
|
108
|
-
klass.connection = conn
|
109
|
-
return self.connection
|
110
|
-
end
|
111
|
-
klass = klass.superclass
|
112
|
-
end
|
113
|
-
raise ConnectionNotEstablished
|
114
|
-
end
|
115
|
-
|
116
|
-
# Returns true if a connection that's accessible to this class have already been opened.
|
117
|
-
def self.connected?
|
118
|
-
klass = self
|
119
|
-
until klass == ActiveRecord::Base.superclass
|
120
|
-
if active_connections[klass]
|
121
|
-
return true
|
122
|
-
else
|
123
|
-
klass = klass.superclass
|
124
|
-
end
|
125
|
-
end
|
126
|
-
return false
|
127
|
-
end
|
128
|
-
|
129
|
-
# Remove the connection for this class. This will close the active
|
130
|
-
# connection and the defined connection (if they exist). The result
|
131
|
-
# can be used as argument for establish_connection, for easy
|
132
|
-
# re-establishing of the connection.
|
133
|
-
def self.remove_connection(klass=self)
|
134
|
-
conn = @@defined_connections[klass]
|
135
|
-
@@defined_connections.delete(klass)
|
136
|
-
active_connections[klass] = nil
|
137
|
-
conn.config if conn
|
138
|
-
end
|
139
|
-
|
140
|
-
# Set the connection for the class.
|
141
|
-
def self.connection=(spec)
|
142
|
-
raise ConnectionNotEstablished unless spec
|
143
|
-
conn = self.send(spec.adapter_method, spec.config)
|
144
|
-
active_connections[self] = conn
|
145
|
-
end
|
146
|
-
|
147
|
-
# Converts all strings in a hash to symbols.
|
148
|
-
def self.symbolize_strings_in_hash(hash) #:nodoc:
|
149
|
-
hash.symbolize_keys
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
11
|
module ConnectionAdapters # :nodoc:
|
154
|
-
class Column # :nodoc:
|
155
|
-
attr_reader :name, :default, :type, :limit
|
156
|
-
# The name should contain the name of the column, such as "name" in "name varchar(250)"
|
157
|
-
# The default should contain the type-casted default of the column, such as 1 in "count int(11) DEFAULT 1"
|
158
|
-
# The type parameter should either contain :integer, :float, :datetime, :date, :text, or :string
|
159
|
-
# The sql_type is just used for extracting the limit, such as 10 in "varchar(10)"
|
160
|
-
def initialize(name, default, sql_type = nil)
|
161
|
-
@name, @default, @type = name, type_cast(default), simplified_type(sql_type)
|
162
|
-
@limit = extract_limit(sql_type) unless sql_type.nil?
|
163
|
-
end
|
164
|
-
|
165
|
-
def klass
|
166
|
-
case type
|
167
|
-
when :integer then Fixnum
|
168
|
-
when :float then Float
|
169
|
-
when :datetime then Time
|
170
|
-
when :date then Date
|
171
|
-
when :timestamp then Time
|
172
|
-
when :time then Time
|
173
|
-
when :text, :string then String
|
174
|
-
when :binary then String
|
175
|
-
when :boolean then Object
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def type_cast(value)
|
180
|
-
if value.nil? then return nil end
|
181
|
-
case type
|
182
|
-
when :string then value
|
183
|
-
when :text then value
|
184
|
-
when :integer then value.to_i rescue value ? 1 : 0
|
185
|
-
when :float then value.to_f
|
186
|
-
when :datetime then string_to_time(value)
|
187
|
-
when :timestamp then string_to_time(value)
|
188
|
-
when :time then string_to_dummy_time(value)
|
189
|
-
when :date then string_to_date(value)
|
190
|
-
when :binary then binary_to_string(value)
|
191
|
-
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
192
|
-
else value
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
def human_name
|
197
|
-
Base.human_attribute_name(@name)
|
198
|
-
end
|
199
|
-
|
200
|
-
def string_to_binary(value)
|
201
|
-
value
|
202
|
-
end
|
203
|
-
|
204
|
-
def binary_to_string(value)
|
205
|
-
value
|
206
|
-
end
|
207
|
-
|
208
|
-
private
|
209
|
-
def string_to_date(string)
|
210
|
-
return string unless string.is_a?(String)
|
211
|
-
date_array = ParseDate.parsedate(string.to_s)
|
212
|
-
# treat 0000-00-00 as nil
|
213
|
-
Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
|
214
|
-
end
|
215
|
-
|
216
|
-
def string_to_time(string)
|
217
|
-
return string unless string.is_a?(String)
|
218
|
-
time_array = ParseDate.parsedate(string.to_s).compact
|
219
|
-
# treat 0000-00-00 00:00:00 as nil
|
220
|
-
Time.send(Base.default_timezone, *time_array) rescue nil
|
221
|
-
end
|
222
|
-
|
223
|
-
def string_to_dummy_time(string)
|
224
|
-
return string unless string.is_a?(String)
|
225
|
-
time_array = ParseDate.parsedate(string.to_s)
|
226
|
-
# pad the resulting array with dummy date information
|
227
|
-
time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
|
228
|
-
Time.send(Base.default_timezone, *time_array) rescue nil
|
229
|
-
end
|
230
|
-
|
231
|
-
def extract_limit(sql_type)
|
232
|
-
$1.to_i if sql_type =~ /\((.*)\)/
|
233
|
-
end
|
234
|
-
|
235
|
-
def simplified_type(field_type)
|
236
|
-
case field_type
|
237
|
-
when /int/i
|
238
|
-
:integer
|
239
|
-
when /float|double|decimal|numeric/i
|
240
|
-
:float
|
241
|
-
when /datetime/i
|
242
|
-
:datetime
|
243
|
-
when /timestamp/i
|
244
|
-
:timestamp
|
245
|
-
when /time/i
|
246
|
-
:time
|
247
|
-
when /date/i
|
248
|
-
:date
|
249
|
-
when /clob/i, /text/i
|
250
|
-
:text
|
251
|
-
when /blob/i, /binary/i
|
252
|
-
:binary
|
253
|
-
when /char/i, /string/i
|
254
|
-
:string
|
255
|
-
when /boolean/i
|
256
|
-
:boolean
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
12
|
# All the concrete database adapters follow the interface laid down in this class.
|
262
13
|
# You can use this interface directly by borrowing the database connection from the Base with
|
263
14
|
# Base.connection.
|
15
|
+
#
|
16
|
+
# Most of the methods in the adapter are useful during migrations. Most
|
17
|
+
# notably, SchemaStatements#create_table, SchemaStatements#drop_table,
|
18
|
+
# SchemaStatements#add_index, SchemaStatements#remove_index,
|
19
|
+
# SchemaStatements#add_column, SchemaStatements#change_column and
|
20
|
+
# SchemaStatements#remove_column are very useful.
|
264
21
|
class AbstractAdapter
|
22
|
+
include Quoting, DatabaseStatements, SchemaStatements
|
265
23
|
@@row_even = true
|
266
24
|
|
267
|
-
def initialize(connection, logger = nil)
|
25
|
+
def initialize(connection, logger = nil) #:nodoc:
|
268
26
|
@connection, @logger = connection, logger
|
269
27
|
@runtime = 0
|
270
28
|
end
|
271
29
|
|
272
|
-
# Returns
|
273
|
-
|
274
|
-
|
275
|
-
# Returns a record hash with the column names as a keys and fields as values.
|
276
|
-
def select_one(sql, name = nil) end
|
277
|
-
|
278
|
-
# Returns an array of column objects for the table specified by +table_name+.
|
279
|
-
def columns(table_name, name = nil) end
|
280
|
-
|
281
|
-
# Returns the last auto-generated ID from the affected table.
|
282
|
-
def insert(sql, name = nil, pk = nil, id_value = nil) end
|
283
|
-
|
284
|
-
# Executes the update statement and returns the number of rows affected.
|
285
|
-
def update(sql, name = nil) end
|
286
|
-
|
287
|
-
# Executes the delete statement and returns the number of rows affected.
|
288
|
-
def delete(sql, name = nil) end
|
289
|
-
|
290
|
-
def reset_runtime # :nodoc:
|
291
|
-
rt = @runtime
|
292
|
-
@runtime = 0
|
293
|
-
return rt
|
294
|
-
end
|
295
|
-
|
296
|
-
# Wrap a block in a transaction. Returns result of block.
|
297
|
-
def transaction(start_db_transaction = true)
|
298
|
-
begin
|
299
|
-
if block_given?
|
300
|
-
begin_db_transaction if start_db_transaction
|
301
|
-
result = yield
|
302
|
-
commit_db_transaction if start_db_transaction
|
303
|
-
result
|
304
|
-
end
|
305
|
-
rescue Exception => database_transaction_rollback
|
306
|
-
rollback_db_transaction if start_db_transaction
|
307
|
-
raise
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
# Begins the transaction (and turns off auto-committing).
|
312
|
-
def begin_db_transaction() end
|
313
|
-
|
314
|
-
# Commits the transaction (and turns on auto-committing).
|
315
|
-
def commit_db_transaction() end
|
316
|
-
|
317
|
-
# Rolls back the transaction (and turns on auto-committing). Must be done if the transaction block
|
318
|
-
# raises an exception or returns false.
|
319
|
-
def rollback_db_transaction() end
|
320
|
-
|
321
|
-
def quote(value, column = nil)
|
322
|
-
case value
|
323
|
-
when String
|
324
|
-
if column && column.type == :binary
|
325
|
-
"'#{quote_string(column.string_to_binary(value))}'" # ' (for ruby-mode)
|
326
|
-
else
|
327
|
-
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
328
|
-
end
|
329
|
-
when NilClass then "NULL"
|
330
|
-
when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
|
331
|
-
when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
|
332
|
-
when Float, Fixnum, Bignum then value.to_s
|
333
|
-
when Date then "'#{value.to_s}'"
|
334
|
-
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
335
|
-
else "'#{quote_string(value.to_yaml)}'"
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
def quote_string(s)
|
340
|
-
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
341
|
-
end
|
342
|
-
|
343
|
-
def quote_column_name(name)
|
344
|
-
name
|
345
|
-
end
|
346
|
-
|
347
|
-
# Returns the human-readable name of the adapter. Use mixed case - one can always use downcase if needed.
|
348
|
-
def adapter_name()
|
30
|
+
# Returns the human-readable name of the adapter. Use mixed case - one
|
31
|
+
# can always use downcase if needed.
|
32
|
+
def adapter_name
|
349
33
|
'Abstract'
|
350
34
|
end
|
351
|
-
|
352
|
-
# Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
|
353
|
-
def structure_dump() end
|
354
|
-
|
355
|
-
def add_limit!(sql, options)
|
356
|
-
return unless options
|
357
|
-
add_limit_offset!(sql, options)
|
358
|
-
end
|
359
|
-
|
360
|
-
def add_limit_offset!(sql, options)
|
361
|
-
return if options[:limit].nil?
|
362
|
-
sql << " LIMIT #{options[:limit]}"
|
363
|
-
sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil?
|
364
|
-
end
|
365
|
-
|
366
|
-
|
367
|
-
def initialize_schema_information
|
368
|
-
begin
|
369
|
-
execute "CREATE TABLE schema_info (version #{type_to_sql(:integer)})"
|
370
|
-
execute "INSERT INTO schema_info (version) VALUES(0)"
|
371
|
-
rescue ActiveRecord::StatementInvalid
|
372
|
-
# Schema has been intialized
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def create_table(name, options = {})
|
377
|
-
table_definition = TableDefinition.new(self)
|
378
|
-
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
379
|
-
|
380
|
-
yield table_definition
|
381
|
-
create_sql = "CREATE TABLE #{name} ("
|
382
|
-
create_sql << table_definition.to_sql
|
383
|
-
create_sql << ") #{options[:options]}"
|
384
|
-
|
385
|
-
execute create_sql
|
386
|
-
end
|
387
|
-
|
388
|
-
def drop_table(name)
|
389
|
-
execute "DROP TABLE #{name}"
|
390
|
-
end
|
391
|
-
|
392
|
-
def add_column(table_name, column_name, type, options = {})
|
393
|
-
native_type = native_database_types[type]
|
394
|
-
add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
|
395
|
-
add_column_options!(add_column_sql, options)
|
396
|
-
execute(add_column_sql)
|
397
|
-
end
|
398
|
-
|
399
|
-
def remove_column(table_name, column_name)
|
400
|
-
execute "ALTER TABLE #{table_name} DROP #{column_name}"
|
401
|
-
end
|
402
|
-
|
403
|
-
def change_column(table_name, column_name, type, options = {})
|
404
|
-
raise NotImplementedError, "change_column is not implemented"
|
405
|
-
end
|
406
|
-
|
407
|
-
def change_column_default(table_name, column_name, default)
|
408
|
-
raise NotImplementedError, "change_column_default is not implemented"
|
409
|
-
end
|
410
35
|
|
36
|
+
# Does this adapter support migrations ? Backend specific, as the
|
37
|
+
# abstract adapter always returns +false+.
|
411
38
|
def supports_migrations?
|
412
39
|
false
|
413
|
-
end
|
414
|
-
|
415
|
-
def rename_column(table_name, column_name, new_column_name)
|
416
|
-
raise NotImplementedError, "rename_column is not implemented"
|
417
|
-
end
|
418
|
-
|
419
|
-
def add_index(table_name, column_name, index_type = '')
|
420
|
-
execute "CREATE #{index_type} INDEX #{table_name}_#{column_name.to_a.first}_index ON #{table_name} (#{column_name.to_a.join(", ")})"
|
421
40
|
end
|
422
41
|
|
423
|
-
def
|
424
|
-
|
42
|
+
def reset_runtime #:nodoc:
|
43
|
+
rt = @runtime
|
44
|
+
@runtime = 0
|
45
|
+
return rt
|
425
46
|
end
|
426
|
-
|
427
|
-
def supports_migrations?
|
428
|
-
false
|
429
|
-
end
|
430
|
-
|
431
|
-
def native_database_types
|
432
|
-
{}
|
433
|
-
end
|
434
|
-
|
435
|
-
def type_to_sql(type, limit = nil)
|
436
|
-
native = native_database_types[type]
|
437
|
-
limit ||= native[:limit]
|
438
|
-
column_type_sql = native[:name]
|
439
|
-
column_type_sql << "(#{limit})" if limit
|
440
|
-
column_type_sql
|
441
|
-
end
|
442
47
|
|
443
48
|
protected
|
444
49
|
def log(sql, name)
|
@@ -476,61 +81,21 @@ module ActiveRecord
|
|
476
81
|
|
477
82
|
def format_log_entry(message, dump = nil)
|
478
83
|
if ActiveRecord::Base.colorize_logging
|
479
|
-
if @@row_even
|
480
|
-
@@row_even = false
|
84
|
+
if @@row_even
|
85
|
+
@@row_even = false
|
86
|
+
message_color, dump_color = "4;36;1", "0;1"
|
481
87
|
else
|
482
|
-
@@row_even = true
|
88
|
+
@@row_even = true
|
89
|
+
message_color, dump_color = "4;35;1", "0"
|
483
90
|
end
|
484
91
|
|
485
|
-
log_entry = " \e[#{message_color}m#{message}\e[
|
486
|
-
log_entry << "
|
487
|
-
log_entry << " \e[#{dump_color}m%p\e[m" % dump if !dump.kind_of?(String) && !dump.nil?
|
92
|
+
log_entry = " \e[#{message_color}m#{message}\e[0m "
|
93
|
+
log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
|
488
94
|
log_entry
|
489
95
|
else
|
490
96
|
"%s %s" % [message, dump]
|
491
97
|
end
|
492
98
|
end
|
493
|
-
|
494
|
-
def add_column_options!(sql, options)
|
495
|
-
sql << " DEFAULT '#{options[:default]}'" unless options[:default].nil?
|
496
|
-
end
|
497
|
-
end
|
498
|
-
|
499
|
-
class TableDefinition
|
500
|
-
attr_accessor :columns
|
501
|
-
|
502
|
-
def initialize(base)
|
503
|
-
@columns = []
|
504
|
-
@base = base
|
505
|
-
end
|
506
|
-
|
507
|
-
def primary_key(name)
|
508
|
-
@columns << "#{name} #{native[:primary_key]}"
|
509
|
-
self
|
510
|
-
end
|
511
|
-
|
512
|
-
def column(name, type, options = {})
|
513
|
-
limit = options[:limit] || native[type.to_sym][:limit]
|
514
|
-
|
515
|
-
column_sql = "#{name} #{type_to_sql(type.to_sym, options[:limit])}"
|
516
|
-
column_sql << " DEFAULT '#{options[:default]}'" if options[:default]
|
517
|
-
@columns << column_sql
|
518
|
-
self
|
519
|
-
end
|
520
|
-
|
521
|
-
def to_sql
|
522
|
-
@columns.join(", ")
|
523
|
-
end
|
524
|
-
|
525
|
-
private
|
526
|
-
|
527
|
-
def type_to_sql(name, limit)
|
528
|
-
@base.type_to_sql(name, limit)
|
529
|
-
end
|
530
|
-
|
531
|
-
def native
|
532
|
-
@base.native_database_types
|
533
|
-
end
|
534
99
|
end
|
535
100
|
end
|
536
101
|
end
|