activerecord 1.14.4 → 1.15.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 +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- data/lib/active_record/locking.rb +0 -79
@@ -3,16 +3,20 @@
|
|
3
3
|
require 'active_record/connection_adapters/abstract_adapter'
|
4
4
|
|
5
5
|
module FireRuby # :nodoc: all
|
6
|
+
NON_EXISTENT_DOMAIN_ERROR = "335544569"
|
6
7
|
class Database
|
7
|
-
def self.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
def self.db_string_for(config)
|
9
|
+
unless config.has_key?(:database)
|
10
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
11
|
+
end
|
12
|
+
host_string = config.values_at(:host, :service, :port).compact.first(2).join("/") if config[:host]
|
13
|
+
[host_string, config[:database]].join(":")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.new_from_config(config)
|
17
|
+
db = new db_string_for(config)
|
18
|
+
db.character_set = config[:charset]
|
19
|
+
return db
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -26,13 +30,9 @@ module ActiveRecord
|
|
26
30
|
'The Firebird adapter requires FireRuby version 0.4.0 or greater; you appear ' <<
|
27
31
|
'to be running an older version -- please update FireRuby (gem install fireruby).'
|
28
32
|
end
|
29
|
-
config
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
options = config[:charset] ? { CHARACTER_SET => config[:charset] } : {}
|
34
|
-
connection_params = [config[:username], config[:password], options]
|
35
|
-
db = FireRuby::Database.new_from_params(*config.values_at(:database, :host, :port, :service))
|
33
|
+
config.symbolize_keys!
|
34
|
+
db = FireRuby::Database.new_from_config(config)
|
35
|
+
connection_params = config.values_at(:username, :password)
|
36
36
|
connection = db.connect(*connection_params)
|
37
37
|
ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params)
|
38
38
|
end
|
@@ -45,10 +45,12 @@ module ActiveRecord
|
|
45
45
|
|
46
46
|
def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
|
47
47
|
@firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
|
48
|
+
|
48
49
|
super(name.downcase, nil, @firebird_type, !null_flag)
|
50
|
+
|
49
51
|
@default = parse_default(default_source) if default_source
|
50
|
-
@limit =
|
51
|
-
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale
|
52
|
+
@limit = decide_limit(length)
|
53
|
+
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale.abs
|
52
54
|
end
|
53
55
|
|
54
56
|
def type
|
@@ -61,27 +63,12 @@ module ActiveRecord
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
|
-
# Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
|
65
|
-
# This enables Firebird to provide an actual value when context variables are used as column
|
66
|
-
# defaults (such as CURRENT_TIMESTAMP).
|
67
66
|
def default
|
68
|
-
if @default
|
69
|
-
sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
|
70
|
-
connection = ActiveRecord::Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
|
71
|
-
if connection
|
72
|
-
type_cast connection.execute(sql).to_a.first['CAST']
|
73
|
-
else
|
74
|
-
raise ConnectionNotEstablished, "No Firebird connections established."
|
75
|
-
end
|
76
|
-
end
|
67
|
+
type_cast(decide_default) if @default
|
77
68
|
end
|
78
69
|
|
79
|
-
def
|
80
|
-
|
81
|
-
value == true or value == ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain[:true]
|
82
|
-
else
|
83
|
-
super
|
84
|
-
end
|
70
|
+
def self.value_to_boolean(value)
|
71
|
+
%W(#{FirebirdAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
|
85
72
|
end
|
86
73
|
|
87
74
|
private
|
@@ -90,6 +77,35 @@ module ActiveRecord
|
|
90
77
|
return $1 unless $1.upcase == "NULL"
|
91
78
|
end
|
92
79
|
|
80
|
+
def decide_default
|
81
|
+
if @default =~ /^'?(\d*\.?\d+)'?$/ or
|
82
|
+
@default =~ /^'(.*)'$/ && [:text, :string, :binary, :boolean].include?(type)
|
83
|
+
$1
|
84
|
+
else
|
85
|
+
firebird_cast_default
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
|
90
|
+
# This enables Firebird to provide an actual value when context variables are used as column
|
91
|
+
# defaults (such as CURRENT_TIMESTAMP).
|
92
|
+
def firebird_cast_default
|
93
|
+
sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
|
94
|
+
if connection = Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
|
95
|
+
connection.execute(sql).to_a.first['CAST']
|
96
|
+
else
|
97
|
+
raise ConnectionNotEstablished, "No Firebird connections established."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def decide_limit(length)
|
102
|
+
if text? or number?
|
103
|
+
length
|
104
|
+
elsif @firebird_type == 'BLOB'
|
105
|
+
BLOB_MAX_LENGTH
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
93
109
|
def column_def
|
94
110
|
case @firebird_type
|
95
111
|
when 'BLOB' then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
|
@@ -133,7 +149,7 @@ module ActiveRecord
|
|
133
149
|
# Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
|
134
150
|
# define a +BOOLEAN+ _domain_ for this purpose, e.g.:
|
135
151
|
#
|
136
|
-
# CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
|
152
|
+
# CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1) OR VALUE IS NULL);
|
137
153
|
#
|
138
154
|
# When the Firebird adapter encounters a column that is based on a domain
|
139
155
|
# that includes "BOOLEAN" in the domain name, it will attempt to treat
|
@@ -200,8 +216,23 @@ module ActiveRecord
|
|
200
216
|
# as column names as well.
|
201
217
|
#
|
202
218
|
# === Migrations
|
203
|
-
# The Firebird
|
204
|
-
#
|
219
|
+
# The Firebird Adapter now supports Migrations.
|
220
|
+
#
|
221
|
+
# ==== Create/Drop Table and Sequence Generators
|
222
|
+
# Creating or dropping a table will automatically create/drop a
|
223
|
+
# correpsonding sequence generator, using the default naming convension.
|
224
|
+
# You can specify a different name using the <tt>:sequence</tt> option; no
|
225
|
+
# generator is created if <tt>:sequence</tt> is set to +false+.
|
226
|
+
#
|
227
|
+
# ==== Rename Table
|
228
|
+
# The Firebird #rename_table Migration should be used with caution.
|
229
|
+
# Firebird 1.5 lacks built-in support for this feature, so it is
|
230
|
+
# implemented by making a copy of the original table (including column
|
231
|
+
# definitions, indexes and data records), and then dropping the original
|
232
|
+
# table. Constraints and Triggers are _not_ properly copied, so avoid
|
233
|
+
# this method if your original table includes constraints (other than
|
234
|
+
# the primary key) or triggers. (Consider manually copying your table
|
235
|
+
# or using a view instead.)
|
205
236
|
#
|
206
237
|
# == Connection Options
|
207
238
|
# The following options are supported by the Firebird adapter. None of the
|
@@ -238,10 +269,12 @@ module ActiveRecord
|
|
238
269
|
# Specifies the character set to be used by the connection. Refer to
|
239
270
|
# Firebird documentation for valid options.
|
240
271
|
class FirebirdAdapter < AbstractAdapter
|
241
|
-
|
272
|
+
TEMP_COLUMN_NAME = 'AR$TEMP_COLUMN'
|
273
|
+
|
274
|
+
@@boolean_domain = { :name => "d_boolean", :type => "smallint", :true => 1, :false => 0 }
|
242
275
|
cattr_accessor :boolean_domain
|
243
276
|
|
244
|
-
def initialize(connection, logger, connection_params=nil)
|
277
|
+
def initialize(connection, logger, connection_params = nil)
|
245
278
|
super(connection, logger)
|
246
279
|
@connection_params = connection_params
|
247
280
|
end
|
@@ -250,13 +283,35 @@ module ActiveRecord
|
|
250
283
|
'Firebird'
|
251
284
|
end
|
252
285
|
|
286
|
+
def supports_migrations? # :nodoc:
|
287
|
+
true
|
288
|
+
end
|
289
|
+
|
290
|
+
def native_database_types # :nodoc:
|
291
|
+
{
|
292
|
+
:primary_key => "BIGINT NOT NULL PRIMARY KEY",
|
293
|
+
:string => { :name => "varchar", :limit => 255 },
|
294
|
+
:text => { :name => "blob sub_type text" },
|
295
|
+
:integer => { :name => "bigint" },
|
296
|
+
:decimal => { :name => "decimal" },
|
297
|
+
:numeric => { :name => "numeric" },
|
298
|
+
:float => { :name => "float" },
|
299
|
+
:datetime => { :name => "timestamp" },
|
300
|
+
:timestamp => { :name => "timestamp" },
|
301
|
+
:time => { :name => "time" },
|
302
|
+
:date => { :name => "date" },
|
303
|
+
:binary => { :name => "blob sub_type 0" },
|
304
|
+
:boolean => boolean_domain
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
253
308
|
# Returns true for Firebird adapter (since Firebird requires primary key
|
254
309
|
# values to be pre-fetched before insert). See also #next_sequence_value.
|
255
310
|
def prefetch_primary_key?(table_name = nil)
|
256
311
|
true
|
257
312
|
end
|
258
313
|
|
259
|
-
def default_sequence_name(table_name, primary_key) # :nodoc:
|
314
|
+
def default_sequence_name(table_name, primary_key = nil) # :nodoc:
|
260
315
|
"#{table_name}_seq"
|
261
316
|
end
|
262
317
|
|
@@ -276,7 +331,7 @@ module ActiveRecord
|
|
276
331
|
end
|
277
332
|
|
278
333
|
def quote_column_name(column_name) # :nodoc:
|
279
|
-
%Q("#{ar_to_fb_case(column_name)}")
|
334
|
+
%Q("#{ar_to_fb_case(column_name.to_s)}")
|
280
335
|
end
|
281
336
|
|
282
337
|
def quoted_true # :nodoc:
|
@@ -290,12 +345,16 @@ module ActiveRecord
|
|
290
345
|
|
291
346
|
# CONNECTION MANAGEMENT ====================================
|
292
347
|
|
293
|
-
def active?
|
348
|
+
def active? # :nodoc:
|
294
349
|
not @connection.closed?
|
295
350
|
end
|
296
351
|
|
297
|
-
def
|
298
|
-
@connection.close
|
352
|
+
def disconnect! # :nodoc:
|
353
|
+
@connection.close rescue nil
|
354
|
+
end
|
355
|
+
|
356
|
+
def reconnect! # :nodoc:
|
357
|
+
disconnect!
|
299
358
|
@connection = @connection.database.connect(*@connection_params)
|
300
359
|
end
|
301
360
|
|
@@ -307,8 +366,7 @@ module ActiveRecord
|
|
307
366
|
end
|
308
367
|
|
309
368
|
def select_one(sql, name = nil) # :nodoc:
|
310
|
-
|
311
|
-
result.nil? ? nil : result.first
|
369
|
+
select(sql, name).first
|
312
370
|
end
|
313
371
|
|
314
372
|
def execute(sql, name = nil, &block) # :nodoc:
|
@@ -363,8 +421,37 @@ module ActiveRecord
|
|
363
421
|
|
364
422
|
# SCHEMA STATEMENTS ========================================
|
365
423
|
|
424
|
+
def current_database # :nodoc:
|
425
|
+
file = @connection.database.file.split(':').last
|
426
|
+
File.basename(file, '.*')
|
427
|
+
end
|
428
|
+
|
429
|
+
def recreate_database! # :nodoc:
|
430
|
+
sql = "SELECT rdb$character_set_name FROM rdb$database"
|
431
|
+
charset = execute(sql).to_a.first[0].rstrip
|
432
|
+
disconnect!
|
433
|
+
@connection.database.drop(*@connection_params)
|
434
|
+
FireRuby::Database.create(@connection.database.file,
|
435
|
+
@connection_params[0], @connection_params[1], 4096, charset)
|
436
|
+
end
|
437
|
+
|
438
|
+
def tables(name = nil) # :nodoc:
|
439
|
+
sql = "SELECT rdb$relation_name FROM rdb$relations WHERE rdb$system_flag = 0"
|
440
|
+
execute(sql, name).collect { |row| row[0].rstrip.downcase }
|
441
|
+
end
|
442
|
+
|
443
|
+
def indexes(table_name, name = nil) # :nodoc:
|
444
|
+
index_metadata(table_name, false, name).inject([]) do |indexes, row|
|
445
|
+
if indexes.empty? or indexes.last.name != row[0]
|
446
|
+
indexes << IndexDefinition.new(table_name, row[0].rstrip.downcase, row[1] == 1, [])
|
447
|
+
end
|
448
|
+
indexes.last.columns << row[2].rstrip.downcase
|
449
|
+
indexes
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
366
453
|
def columns(table_name, name = nil) # :nodoc:
|
367
|
-
sql = <<-
|
454
|
+
sql = <<-end_sql
|
368
455
|
SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
|
369
456
|
f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
|
370
457
|
COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
|
@@ -373,7 +460,7 @@ module ActiveRecord
|
|
373
460
|
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
374
461
|
WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
|
375
462
|
ORDER BY r.rdb$field_position
|
376
|
-
|
463
|
+
end_sql
|
377
464
|
execute(sql, name).collect do |field|
|
378
465
|
field_values = field.values.collect do |value|
|
379
466
|
case value
|
@@ -386,7 +473,120 @@ module ActiveRecord
|
|
386
473
|
end
|
387
474
|
end
|
388
475
|
|
476
|
+
def create_table(name, options = {}) # :nodoc:
|
477
|
+
begin
|
478
|
+
super
|
479
|
+
rescue StatementInvalid
|
480
|
+
raise unless non_existent_domain_error?
|
481
|
+
create_boolean_domain
|
482
|
+
super
|
483
|
+
end
|
484
|
+
unless options[:id] == false or options[:sequence] == false
|
485
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
486
|
+
create_sequence(sequence_name)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def drop_table(name, options = {}) # :nodoc:
|
491
|
+
super(name)
|
492
|
+
unless options[:sequence] == false
|
493
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
494
|
+
drop_sequence(sequence_name) if sequence_exists?(sequence_name)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def add_column(table_name, column_name, type, options = {}) # :nodoc:
|
499
|
+
super
|
500
|
+
rescue StatementInvalid
|
501
|
+
raise unless non_existent_domain_error?
|
502
|
+
create_boolean_domain
|
503
|
+
super
|
504
|
+
end
|
505
|
+
|
506
|
+
def change_column(table_name, column_name, type, options = {}) # :nodoc:
|
507
|
+
change_column_type(table_name, column_name, type, options)
|
508
|
+
change_column_position(table_name, column_name, options[:position]) if options.include?(:position)
|
509
|
+
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
510
|
+
end
|
511
|
+
|
512
|
+
def change_column_default(table_name, column_name, default) # :nodoc:
|
513
|
+
table_name = table_name.to_s.upcase
|
514
|
+
sql = <<-end_sql
|
515
|
+
UPDATE rdb$relation_fields f1
|
516
|
+
SET f1.rdb$default_source =
|
517
|
+
(SELECT f2.rdb$default_source FROM rdb$relation_fields f2
|
518
|
+
WHERE f2.rdb$relation_name = '#{table_name}'
|
519
|
+
AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}'),
|
520
|
+
f1.rdb$default_value =
|
521
|
+
(SELECT f2.rdb$default_value FROM rdb$relation_fields f2
|
522
|
+
WHERE f2.rdb$relation_name = '#{table_name}'
|
523
|
+
AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}')
|
524
|
+
WHERE f1.rdb$relation_name = '#{table_name}'
|
525
|
+
AND f1.rdb$field_name = '#{ar_to_fb_case(column_name.to_s)}'
|
526
|
+
end_sql
|
527
|
+
transaction do
|
528
|
+
add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default)
|
529
|
+
execute sql
|
530
|
+
remove_column(table_name, TEMP_COLUMN_NAME)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
535
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TO #{new_column_name}"
|
536
|
+
end
|
537
|
+
|
538
|
+
def remove_index(table_name, options) #:nodoc:
|
539
|
+
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
|
540
|
+
end
|
541
|
+
|
542
|
+
def rename_table(name, new_name) # :nodoc:
|
543
|
+
if table_has_constraints_or_dependencies?(name)
|
544
|
+
raise ActiveRecordError,
|
545
|
+
"Table #{name} includes constraints or dependencies that are not supported by " <<
|
546
|
+
"the Firebird rename_table migration. Try explicitly removing the constraints/" <<
|
547
|
+
"dependencies first, or manually renaming the table."
|
548
|
+
end
|
549
|
+
|
550
|
+
transaction do
|
551
|
+
copy_table(name, new_name)
|
552
|
+
copy_table_indexes(name, new_name)
|
553
|
+
end
|
554
|
+
begin
|
555
|
+
copy_table_data(name, new_name)
|
556
|
+
copy_sequence_value(name, new_name)
|
557
|
+
rescue
|
558
|
+
drop_table(new_name)
|
559
|
+
raise
|
560
|
+
end
|
561
|
+
drop_table(name)
|
562
|
+
end
|
563
|
+
|
564
|
+
def dump_schema_information # :nodoc:
|
565
|
+
super << ";\n"
|
566
|
+
end
|
567
|
+
|
568
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc:
|
569
|
+
case type
|
570
|
+
when :integer then integer_sql_type(limit)
|
571
|
+
when :float then float_sql_type(limit)
|
572
|
+
when :string then super(type, limit, precision, scale)
|
573
|
+
else super(type, limit, precision, scale)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
389
577
|
private
|
578
|
+
def integer_sql_type(limit)
|
579
|
+
case limit
|
580
|
+
when (1..2) then 'smallint'
|
581
|
+
when (3..4) then 'integer'
|
582
|
+
else 'bigint'
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def float_sql_type(limit)
|
587
|
+
limit.to_i <= 4 ? 'float' : 'double precision'
|
588
|
+
end
|
589
|
+
|
390
590
|
def select(sql, name = nil)
|
391
591
|
execute(sql, name).collect do |row|
|
392
592
|
hashed_row = {}
|
@@ -398,6 +598,120 @@ module ActiveRecord
|
|
398
598
|
end
|
399
599
|
end
|
400
600
|
|
601
|
+
def primary_key(table_name)
|
602
|
+
if pk_row = index_metadata(table_name, true).to_a.first
|
603
|
+
pk_row[2].rstrip.downcase
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
def index_metadata(table_name, pk, name = nil)
|
608
|
+
sql = <<-end_sql
|
609
|
+
SELECT i.rdb$index_name, i.rdb$unique_flag, s.rdb$field_name
|
610
|
+
FROM rdb$indices i
|
611
|
+
JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
|
612
|
+
LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
|
613
|
+
WHERE i.rdb$relation_name = '#{table_name.to_s.upcase}'
|
614
|
+
end_sql
|
615
|
+
if pk
|
616
|
+
sql << "AND c.rdb$constraint_type = 'PRIMARY KEY'\n"
|
617
|
+
else
|
618
|
+
sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n"
|
619
|
+
end
|
620
|
+
sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n"
|
621
|
+
execute sql, name
|
622
|
+
end
|
623
|
+
|
624
|
+
def change_column_type(table_name, column_name, type, options = {})
|
625
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
|
626
|
+
execute sql
|
627
|
+
rescue StatementInvalid
|
628
|
+
raise unless non_existent_domain_error?
|
629
|
+
create_boolean_domain
|
630
|
+
execute sql
|
631
|
+
end
|
632
|
+
|
633
|
+
def change_column_position(table_name, column_name, position)
|
634
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} POSITION #{position}"
|
635
|
+
end
|
636
|
+
|
637
|
+
def copy_table(from, to)
|
638
|
+
table_opts = {}
|
639
|
+
if pk = primary_key(from)
|
640
|
+
table_opts[:primary_key] = pk
|
641
|
+
else
|
642
|
+
table_opts[:id] = false
|
643
|
+
end
|
644
|
+
create_table(to, table_opts) do |table|
|
645
|
+
from_columns = columns(from).reject { |col| col.name == table_opts[:primary_key] }
|
646
|
+
from_columns.each do |column|
|
647
|
+
col_opts = [:limit, :default, :null].inject({}) { |opts, opt| opts.merge(opt => column.send(opt)) }
|
648
|
+
table.column column.name, column.type, col_opts
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
def copy_table_indexes(from, to)
|
654
|
+
indexes(from).each do |index|
|
655
|
+
unless index.name[from.to_s]
|
656
|
+
raise ActiveRecordError,
|
657
|
+
"Cannot rename index #{index.name}, because the index name does not include " <<
|
658
|
+
"the original table name (#{from}). Try explicitly removing the index on the " <<
|
659
|
+
"original table and re-adding it on the new (renamed) table."
|
660
|
+
end
|
661
|
+
options = {}
|
662
|
+
options[:name] = index.name.gsub(from.to_s, to.to_s)
|
663
|
+
options[:unique] = index.unique
|
664
|
+
add_index(to, index.columns, options)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def copy_table_data(from, to)
|
669
|
+
execute "INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}"
|
670
|
+
end
|
671
|
+
|
672
|
+
def copy_sequence_value(from, to)
|
673
|
+
sequence_value = FireRuby::Generator.new(default_sequence_name(from), @connection).last
|
674
|
+
execute "SET GENERATOR #{default_sequence_name(to)} TO #{sequence_value}"
|
675
|
+
end
|
676
|
+
|
677
|
+
def sequence_exists?(sequence_name)
|
678
|
+
FireRuby::Generator.exists?(sequence_name, @connection)
|
679
|
+
end
|
680
|
+
|
681
|
+
def create_sequence(sequence_name)
|
682
|
+
FireRuby::Generator.create(sequence_name.to_s, @connection)
|
683
|
+
end
|
684
|
+
|
685
|
+
def drop_sequence(sequence_name)
|
686
|
+
FireRuby::Generator.new(sequence_name.to_s, @connection).drop
|
687
|
+
end
|
688
|
+
|
689
|
+
def create_boolean_domain
|
690
|
+
sql = <<-end_sql
|
691
|
+
CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
|
692
|
+
CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
|
693
|
+
end_sql
|
694
|
+
execute sql rescue nil
|
695
|
+
end
|
696
|
+
|
697
|
+
def table_has_constraints_or_dependencies?(table_name)
|
698
|
+
table_name = table_name.to_s.upcase
|
699
|
+
sql = <<-end_sql
|
700
|
+
SELECT 1 FROM rdb$relation_constraints
|
701
|
+
WHERE rdb$relation_name = '#{table_name}'
|
702
|
+
AND rdb$constraint_type IN ('UNIQUE', 'FOREIGN KEY', 'CHECK')
|
703
|
+
UNION
|
704
|
+
SELECT 1 FROM rdb$dependencies
|
705
|
+
WHERE rdb$depended_on_name = '#{table_name}'
|
706
|
+
AND rdb$depended_on_type = 0
|
707
|
+
end_sql
|
708
|
+
!select(sql).empty?
|
709
|
+
end
|
710
|
+
|
711
|
+
def non_existent_domain_error?
|
712
|
+
$!.message.include? FireRuby::NON_EXISTENT_DOMAIN_ERROR
|
713
|
+
end
|
714
|
+
|
401
715
|
# Maps uppercase Firebird column names to lowercase for ActiveRecord;
|
402
716
|
# mixed-case columns retain their original case.
|
403
717
|
def fb_to_ar_case(column_name)
|