activerecord 1.13.0 → 1.13.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.

Files changed (68) hide show
  1. data/CHANGELOG +91 -0
  2. data/lib/active_record.rb +2 -2
  3. data/lib/active_record/acts/list.rb +16 -12
  4. data/lib/active_record/acts/tree.rb +2 -2
  5. data/lib/active_record/aggregations.rb +6 -0
  6. data/lib/active_record/associations.rb +38 -16
  7. data/lib/active_record/associations/has_many_association.rb +2 -1
  8. data/lib/active_record/associations/has_one_association.rb +1 -1
  9. data/lib/active_record/base.rb +46 -33
  10. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +33 -9
  11. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +11 -2
  12. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
  13. data/lib/active_record/connection_adapters/abstract_adapter.rb +41 -21
  14. data/lib/active_record/connection_adapters/firebird_adapter.rb +414 -0
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -29
  16. data/lib/active_record/connection_adapters/oci_adapter.rb +141 -21
  17. data/lib/active_record/connection_adapters/postgresql_adapter.rb +82 -21
  18. data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -3
  19. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +39 -6
  20. data/lib/active_record/fixtures.rb +1 -0
  21. data/lib/active_record/migration.rb +30 -13
  22. data/lib/active_record/validations.rb +18 -7
  23. data/lib/active_record/vendor/mysql.rb +89 -12
  24. data/lib/active_record/version.rb +2 -2
  25. data/rakefile +38 -3
  26. data/test/abstract_unit.rb +5 -0
  27. data/test/aggregations_test.rb +19 -0
  28. data/test/associations_go_eager_test.rb +26 -2
  29. data/test/associations_test.rb +29 -10
  30. data/test/base_test.rb +57 -6
  31. data/test/binary_test.rb +3 -3
  32. data/test/connections/native_db2/connection.rb +1 -1
  33. data/test/connections/native_firebird/connection.rb +24 -0
  34. data/test/connections/native_mysql/connection.rb +1 -1
  35. data/test/connections/native_oci/connection.rb +1 -1
  36. data/test/connections/native_postgresql/connection.rb +6 -6
  37. data/test/connections/native_sqlite/connection.rb +1 -1
  38. data/test/connections/native_sqlite3/connection.rb +1 -1
  39. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  40. data/test/connections/native_sqlserver/connection.rb +1 -1
  41. data/test/connections/native_sqlserver_odbc/connection.rb +1 -1
  42. data/test/default_test_firebird.rb +16 -0
  43. data/test/deprecated_associations_test.rb +1 -1
  44. data/test/finder_test.rb +11 -1
  45. data/test/fixtures/author.rb +30 -30
  46. data/test/fixtures/comment.rb +1 -1
  47. data/test/fixtures/company.rb +3 -1
  48. data/test/fixtures/customer.rb +4 -0
  49. data/test/fixtures/db_definitions/firebird.drop.sql +54 -0
  50. data/test/fixtures/db_definitions/firebird.sql +259 -0
  51. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  52. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  53. data/test/fixtures/db_definitions/oci.sql +8 -0
  54. data/test/fixtures/db_definitions/postgresql.sql +3 -2
  55. data/test/fixtures/developer.rb +10 -0
  56. data/test/fixtures/fixture_database.sqlite +0 -0
  57. data/test/fixtures/fixture_database_2.sqlite +0 -0
  58. data/test/fixtures/mixin.rb +11 -1
  59. data/test/fixtures/mixins.yml +20 -1
  60. data/test/fixtures_test.rb +65 -45
  61. data/test/inheritance_test.rb +1 -1
  62. data/test/migration_test.rb +7 -1
  63. data/test/mixin_test.rb +267 -98
  64. data/test/multiple_db_test.rb +13 -1
  65. data/test/pk_test.rb +1 -0
  66. metadata +11 -5
  67. data/lib/active_record/vendor/mysql411.rb +0 -311
  68. data/test/debug.log +0 -2857
@@ -1,5 +1,4 @@
1
1
  module ActiveRecord
2
- # The root class of all active record objects.
3
2
  class Base
4
3
  class ConnectionSpecification #:nodoc:
5
4
  attr_reader :config, :adapter_method
@@ -11,6 +10,28 @@ module ActiveRecord
11
10
  # The class -> [adapter_method, config] map
12
11
  @@defined_connections = {}
13
12
 
13
+ # The class -> thread id -> adapter cache.
14
+ @@connection_cache = Hash.new { |h, k| h[k] = Hash.new }
15
+
16
+ # Returns the connection currently associated with the class. This can
17
+ # also be used to "borrow" the connection to do database work unrelated
18
+ # to any of the specific Active Records.
19
+ def self.connection
20
+ @@connection_cache[Thread.current.object_id][name] ||= retrieve_connection
21
+ end
22
+
23
+ # Clears the cache which maps classes to connections.
24
+ def self.clear_connection_cache!
25
+ @@connection_cache.clear
26
+ end
27
+
28
+ # Returns the connection currently associated with the class. This can
29
+ # also be used to "borrow" the connection to do database work that isn't
30
+ # easily done without going straight to SQL.
31
+ def connection
32
+ self.class.connection
33
+ end
34
+
14
35
  # Establishes the connection to the database. Accepts a hash as input where
15
36
  # the :adapter key must be specified with the name of a database adapter (in lower-case)
16
37
  # example for regular databases (MySQL, Postgresql, etc):
@@ -44,7 +65,7 @@ module ActiveRecord
44
65
  raise AdapterNotSpecified unless defined? RAILS_ENV
45
66
  establish_connection(RAILS_ENV)
46
67
  when ConnectionSpecification
47
- @@defined_connections[self] = spec
68
+ @@defined_connections[name] = spec
48
69
  when Symbol, String
49
70
  if configuration = configurations[spec.to_s]
50
71
  establish_connection(configuration)
@@ -77,9 +98,11 @@ module ActiveRecord
77
98
  klass = self
78
99
  ar_super = ActiveRecord::Base.superclass
79
100
  until klass == ar_super
80
- if conn = active_connections[klass]
101
+ if conn = active_connections[klass.name]
102
+ # Reconnect if the connection is inactive.
103
+ conn.reconnect! unless conn.active?
81
104
  return conn
82
- elsif conn = @@defined_connections[klass]
105
+ elsif conn = @@defined_connections[klass.name]
83
106
  klass.connection = conn
84
107
  return self.connection
85
108
  end
@@ -92,7 +115,7 @@ module ActiveRecord
92
115
  def self.connected?
93
116
  klass = self
94
117
  until klass == ActiveRecord::Base.superclass
95
- if active_connections[klass]
118
+ if active_connections[klass.name]
96
119
  return true
97
120
  else
98
121
  klass = klass.superclass
@@ -106,9 +129,10 @@ module ActiveRecord
106
129
  # can be used as argument for establish_connection, for easy
107
130
  # re-establishing of the connection.
108
131
  def self.remove_connection(klass=self)
109
- conn = @@defined_connections[klass]
110
- @@defined_connections.delete(klass)
111
- active_connections[klass] = nil
132
+ conn = @@defined_connections[klass.name]
133
+ @@defined_connections.delete(klass.name)
134
+ @@connection_cache[Thread.current.object_id].delete(klass.name)
135
+ active_connections.delete(klass.name)
112
136
  @connection = nil
113
137
  conn.config if conn
114
138
  end
@@ -116,7 +140,7 @@ module ActiveRecord
116
140
  # Set the connection for the class.
117
141
  def self.connection=(spec)
118
142
  if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
119
- active_connections[self] = spec
143
+ active_connections[name] = spec
120
144
  elsif spec.kind_of?(ConnectionSpecification)
121
145
  self.connection = self.send(spec.adapter_method, spec.config)
122
146
  elsif spec.nil?
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  when :time then self.class.string_to_dummy_time(value)
60
60
  when :date then self.class.string_to_date(value)
61
61
  when :binary then self.class.binary_to_string(value)
62
- when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
62
+ when :boolean then self.class.value_to_boolean(value)
63
63
  else value
64
64
  end
65
65
  end
@@ -75,7 +75,7 @@ module ActiveRecord
75
75
  when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
76
76
  when :date then "#{self.class.name}.string_to_date(#{var_name})"
77
77
  when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
78
- when :boolean then "(#{var_name} == true or (#{var_name} =~ /^t(?:true)?$/i) == 0 or #{var_name}.to_s == '1')"
78
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
79
79
  else nil
80
80
  end
81
81
  end
@@ -120,6 +120,15 @@ module ActiveRecord
120
120
  Time.send(Base.default_timezone, *time_array) rescue nil
121
121
  end
122
122
 
123
+ # convert something to a boolean
124
+ def self.value_to_boolean(value)
125
+ return value if value==true || value==false
126
+ case value.to_s.downcase
127
+ when "true", "t", "1" then true
128
+ else false
129
+ end
130
+ end
131
+
123
132
  private
124
133
  def extract_limit(sql_type)
125
134
  $1.to_i if sql_type =~ /\((.*)\)/
@@ -43,6 +43,9 @@ module ActiveRecord
43
43
  # Any extra options you want appended to the table definition.
44
44
  # [<tt>:temporary</tt>]
45
45
  # Make a temporary table.
46
+ # [<tt>:force</tt>]
47
+ # Set to true or false to drop the table before creating it.
48
+ # Defaults to false.
46
49
  #
47
50
  # ===== Examples
48
51
  # ====== Add a backend specific option to the generated SQL (MySQL)
@@ -32,40 +32,60 @@ module ActiveRecord
32
32
  def adapter_name
33
33
  'Abstract'
34
34
  end
35
-
35
+
36
36
  # Does this adapter support migrations? Backend specific, as the
37
37
  # abstract adapter always returns +false+.
38
38
  def supports_migrations?
39
39
  false
40
40
  end
41
41
 
42
+ # Should primary key values be selected from their corresponding
43
+ # sequence before the insert statement? If true, next_sequence_value
44
+ # is called before each insert to set the record's primary key.
45
+ # This is false for all adapters but Firebird.
46
+ def prefetch_primary_key?(table_name = nil)
47
+ false
48
+ end
49
+
42
50
  def reset_runtime #:nodoc:
43
- rt = @runtime
44
- @runtime = 0
45
- return rt
51
+ rt, @runtime = @runtime, 0
52
+ rt
46
53
  end
47
54
 
48
- protected
55
+
56
+ # CONNECTION MANAGEMENT ====================================
57
+
58
+ # Is this connection active and ready to perform queries?
59
+ def active?
60
+ true
61
+ end
62
+
63
+ # Close this connection and open a new one in its place.
64
+ def reconnect!
65
+ end
66
+
67
+
68
+ protected
49
69
  def log(sql, name)
50
- begin
51
- if block_given?
52
- if @logger and @logger.level <= Logger::INFO
53
- result = nil
54
- seconds = Benchmark.realtime { result = yield }
55
- @runtime += seconds
56
- log_info(sql, name, seconds)
57
- result
58
- else
59
- yield
60
- end
70
+ if block_given?
71
+ if @logger and @logger.level <= Logger::INFO
72
+ result = nil
73
+ seconds = Benchmark.realtime { result = yield }
74
+ @runtime += seconds
75
+ log_info(sql, name, seconds)
76
+ result
61
77
  else
62
- log_info(sql, name, 0)
63
- nil
78
+ yield
64
79
  end
65
- rescue Exception => e
66
- log_info("#{e.message}: #{sql}", name, 0)
67
- raise ActiveRecord::StatementInvalid, "#{e.message}: #{sql}"
80
+ else
81
+ log_info(sql, name, 0)
82
+ nil
68
83
  end
84
+ rescue Exception => e
85
+ # Log message and raise exception.
86
+ message = "#{e.class.name}: #{e.message}: #{sql}"
87
+ log_info(message, name, 0)
88
+ raise ActiveRecord::StatementInvalid, message
69
89
  end
70
90
 
71
91
  def log_info(sql, name, runtime)
@@ -0,0 +1,414 @@
1
+ # Author: Ken Kunz <kennethkunz@gmail.com>
2
+
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+
5
+ module FireRuby # :nodoc: all
6
+ class Database
7
+ def self.new_from_params(database, host, port, service)
8
+ db_string = ""
9
+ if host
10
+ db_string << host
11
+ db_string << "/#{service || port}" if service || port
12
+ db_string << ":"
13
+ end
14
+ db_string << database
15
+ new(db_string)
16
+ end
17
+ end
18
+ end
19
+
20
+ module ActiveRecord
21
+ class << Base
22
+ def firebird_connection(config) # :nodoc:
23
+ require_library_or_gem 'fireruby'
24
+ unless defined? FireRuby::SQLType
25
+ raise AdapterNotFound,
26
+ 'The Firebird adapter requires FireRuby version 0.4.0 or greater; you appear ' <<
27
+ 'to be running an older version -- please update FireRuby (gem install fireruby).'
28
+ end
29
+ config = config.symbolize_keys
30
+ unless config.has_key?(:database)
31
+ raise ArgumentError, "No database specified. Missing argument: database."
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))
36
+ connection = db.connect(*connection_params)
37
+ ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params)
38
+ end
39
+ end
40
+
41
+ module ConnectionAdapters
42
+ class FirebirdColumn < Column # :nodoc:
43
+ VARCHAR_MAX_LENGTH = 32_765
44
+ BLOB_MAX_LENGTH = 32_767
45
+
46
+ def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
47
+ @firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
48
+ super(name.downcase, nil, @firebird_type, !null_flag)
49
+ @default = parse_default(default_source) if default_source
50
+ @limit = type == 'BLOB' ? BLOB_MAX_LENGTH : length
51
+ @domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale
52
+ end
53
+
54
+ def type
55
+ if @domain =~ /BOOLEAN/
56
+ :boolean
57
+ elsif @type == :binary and @sub_type == 1
58
+ :text
59
+ else
60
+ @type
61
+ end
62
+ end
63
+
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
+ 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
77
+ end
78
+
79
+ def type_cast(value)
80
+ if type == :boolean
81
+ value == true or value == ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain[:true]
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ private
88
+ def parse_default(default_source)
89
+ default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
90
+ return $1 unless $1.upcase == "NULL"
91
+ end
92
+
93
+ def column_def
94
+ case @firebird_type
95
+ when 'BLOB' then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
96
+ when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
97
+ when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
98
+ when 'DOUBLE' then "DOUBLE PRECISION"
99
+ else @firebird_type
100
+ end
101
+ end
102
+
103
+ def simplified_type(field_type)
104
+ if field_type == 'TIMESTAMP'
105
+ :datetime
106
+ else
107
+ super
108
+ end
109
+ end
110
+ end
111
+
112
+ # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
113
+ # extension, version 0.4.0 or later (available as a gem or from
114
+ # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with
115
+ # Firebird 1.5.x on Linux, OS X and Win32 platforms.
116
+ #
117
+ # == Usage Notes
118
+ #
119
+ # === Sequence (Generator) Names
120
+ # The Firebird adapter supports the same approach adopted for the Oracle
121
+ # adapter. See ActiveRecord::Base#set_sequence_name for more details.
122
+ #
123
+ # Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
124
+ # trigger corresponding to a Firebird sequence generator when using
125
+ # ActiveRecord. In other words, you don't have to try to make Firebird
126
+ # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
127
+ # new record, ActiveRecord pre-fetches the next sequence value for the table
128
+ # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
129
+ # next primary key value is the only reliable method for the Firebird
130
+ # adapter to report back the +id+ after a successful insert.)
131
+ #
132
+ # === BOOLEAN Domain
133
+ # Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
134
+ # define a +BOOLEAN+ _domain_ for this purpose, e.g.:
135
+ #
136
+ # CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
137
+ #
138
+ # When the Firebird adapter encounters a column that is based on a domain
139
+ # that includes "BOOLEAN" in the domain name, it will attempt to treat
140
+ # the column as a +BOOLEAN+.
141
+ #
142
+ # By default, the Firebird adapter will assume that the BOOLEAN domain is
143
+ # defined as above. This can be modified if needed. For example, if you
144
+ # have a legacy schema with the following +BOOLEAN+ domain defined:
145
+ #
146
+ # CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
147
+ #
148
+ # ...you can add the following line to your <tt>environment.rb</tt> file:
149
+ #
150
+ # ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain = { :true => 'T', :false => 'F' }
151
+ #
152
+ # === BLOB Elements
153
+ # The Firebird adapter currently provides only limited support for +BLOB+
154
+ # columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream.
155
+ # When selecting a +BLOB+, the entire element is converted into a String.
156
+ # When inserting or updating a +BLOB+, the entire value is included in-line
157
+ # in the SQL statement, limiting you to values <= 32KB in size.
158
+ #
159
+ # === Column Name Case Semantics
160
+ # Firebird and ActiveRecord have somewhat conflicting case semantics for
161
+ # column names.
162
+ #
163
+ # [*Firebird*]
164
+ # The standard practice is to use unquoted column names, which can be
165
+ # thought of as case-insensitive. (In fact, Firebird converts them to
166
+ # uppercase.) Quoted column names (not typically used) are case-sensitive.
167
+ # [*ActiveRecord*]
168
+ # Attribute accessors corresponding to column names are case-sensitive.
169
+ # The defaults for primary key and inheritance columns are lowercase, and
170
+ # in general, people use lowercase attribute names.
171
+ #
172
+ # In order to map between the differing semantics in a way that conforms
173
+ # to common usage for both Firebird and ActiveRecord, uppercase column names
174
+ # in Firebird are converted to lowercase attribute names in ActiveRecord,
175
+ # and vice-versa. Mixed-case column names retain their case in both
176
+ # directions. Lowercase (quoted) Firebird column names are not supported.
177
+ # This is similar to the solutions adopted by other adapters.
178
+ #
179
+ # In general, the best approach is to use unqouted (case-insensitive) column
180
+ # names in your Firebird DDL (or if you must quote, use uppercase column
181
+ # names). These will correspond to lowercase attributes in ActiveRecord.
182
+ #
183
+ # For example, a Firebird table based on the following DDL:
184
+ #
185
+ # CREATE TABLE products (
186
+ # id BIGINT NOT NULL PRIMARY KEY,
187
+ # "TYPE" VARCHAR(50),
188
+ # name VARCHAR(255) );
189
+ #
190
+ # ...will correspond to an ActiveRecord model class called +Product+ with
191
+ # the following attributes: +id+, +type+, +name+.
192
+ #
193
+ # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
194
+ # In ActiveRecord, the default inheritance column name is +type+. The word
195
+ # _type_ is a Firebird reserved word, so it must be quoted in any Firebird
196
+ # SQL statements. Because of the case mapping described above, you should
197
+ # always reference this column using quoted-uppercase syntax
198
+ # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
199
+ # example above). This holds true for any other Firebird reserved words used
200
+ # as column names as well.
201
+ #
202
+ # === Migrations
203
+ # The Firebird adapter does not currently support Migrations. I hope to
204
+ # add this feature in the near future.
205
+ #
206
+ # == Connection Options
207
+ # The following options are supported by the Firebird adapter. None of the
208
+ # options have default values.
209
+ #
210
+ # <tt>:database</tt>::
211
+ # <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
212
+ # (ii) the full path of a database file; _or_ (iii) a full Firebird
213
+ # connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
214
+ # or <tt>:port</tt> as separate options when using a full connection
215
+ # string.</i>
216
+ # <tt>:host</tt>::
217
+ # Set to <tt>"remote.host.name"</tt> for remote database connections.
218
+ # May be omitted for local connections if a full database path is
219
+ # specified for <tt>:database</tt>. Some platforms require a value of
220
+ # <tt>"localhost"</tt> for local connections when using a Firebird
221
+ # database _alias_.
222
+ # <tt>:service</tt>::
223
+ # Specifies a service name for the connection. Only used if <tt>:host</tt>
224
+ # is provided. Required when connecting to a non-standard service.
225
+ # <tt>:port</tt>::
226
+ # Specifies the connection port. Only used if <tt>:host</tt> is provided
227
+ # and <tt>:service</tt> is not. Required when connecting to a non-standard
228
+ # port and <tt>:service</tt> is not defined.
229
+ # <tt>:username</tt>::
230
+ # Specifies the database user. May be omitted or set to +nil+ (together
231
+ # with <tt>:password</tt>) to use the underlying operating system user
232
+ # credentials on supported platforms.
233
+ # <tt>:password</tt>::
234
+ # Specifies the database password. Must be provided if <tt>:username</tt>
235
+ # is explicitly specified; should be omitted if OS user credentials are
236
+ # are being used.
237
+ # <tt>:charset</tt>::
238
+ # Specifies the character set to be used by the connection. Refer to
239
+ # Firebird documentation for valid options.
240
+ class FirebirdAdapter < AbstractAdapter
241
+ @@boolean_domain = { :true => 1, :false => 0 }
242
+ cattr_accessor :boolean_domain
243
+
244
+ def initialize(connection, logger, connection_params=nil)
245
+ super(connection, logger)
246
+ @connection_params = connection_params
247
+ end
248
+
249
+ def adapter_name # :nodoc:
250
+ 'Firebird'
251
+ end
252
+
253
+ # Returns true for Firebird adapter (since Firebird requires primary key
254
+ # values to be pre-fetched before insert). See also #next_sequence_value.
255
+ def prefetch_primary_key?(table_name = nil)
256
+ true
257
+ end
258
+
259
+ def default_sequence_name(table_name, primary_key) # :nodoc:
260
+ "#{table_name}_seq"
261
+ end
262
+
263
+
264
+ # QUOTING ==================================================
265
+
266
+ def quote(value, column = nil) # :nodoc:
267
+ if [Time, DateTime].include?(value.class)
268
+ "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
269
+ else
270
+ super
271
+ end
272
+ end
273
+
274
+ def quote_string(string) # :nodoc:
275
+ string.gsub(/'/, "''")
276
+ end
277
+
278
+ def quote_column_name(column_name) # :nodoc:
279
+ %Q("#{ar_to_fb_case(column_name)}")
280
+ end
281
+
282
+ def quoted_true # :nodoc:
283
+ quote(boolean_domain[:true])
284
+ end
285
+
286
+ def quoted_false # :nodoc:
287
+ quote(boolean_domain[:false])
288
+ end
289
+
290
+
291
+ # CONNECTION MANAGEMENT ====================================
292
+
293
+ def active?
294
+ not @connection.closed?
295
+ end
296
+
297
+ def reconnect!
298
+ @connection.close
299
+ @connection = @connection.database.connect(*@connection_params)
300
+ end
301
+
302
+
303
+ # DATABASE STATEMENTS ======================================
304
+
305
+ def select_all(sql, name = nil) # :nodoc:
306
+ select(sql, name)
307
+ end
308
+
309
+ def select_one(sql, name = nil) # :nodoc:
310
+ result = select(sql, name)
311
+ result.nil? ? nil : result.first
312
+ end
313
+
314
+ def execute(sql, name = nil, &block) # :nodoc:
315
+ log(sql, name) do
316
+ if @transaction
317
+ @connection.execute(sql, @transaction, &block)
318
+ else
319
+ @connection.execute_immediate(sql, &block)
320
+ end
321
+ end
322
+ end
323
+
324
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
325
+ execute(sql, name)
326
+ id_value
327
+ end
328
+
329
+ alias_method :update, :execute
330
+ alias_method :delete, :execute
331
+
332
+ def begin_db_transaction() # :nodoc:
333
+ @transaction = @connection.start_transaction
334
+ end
335
+
336
+ def commit_db_transaction() # :nodoc:
337
+ @transaction.commit
338
+ ensure
339
+ @transaction = nil
340
+ end
341
+
342
+ def rollback_db_transaction() # :nodoc:
343
+ @transaction.rollback
344
+ ensure
345
+ @transaction = nil
346
+ end
347
+
348
+ def add_limit_offset!(sql, options) # :nodoc:
349
+ if options[:limit]
350
+ limit_string = "FIRST #{options[:limit]}"
351
+ limit_string << " SKIP #{options[:offset]}" if options[:offset]
352
+ sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
353
+ end
354
+ end
355
+
356
+ # Returns the next sequence value from a sequence generator. Not generally
357
+ # called directly; used by ActiveRecord to get the next primary key value
358
+ # when inserting a new database record (see #prefetch_primary_key?).
359
+ def next_sequence_value(sequence_name)
360
+ FireRuby::Generator.new(sequence_name, @connection).next(1)
361
+ end
362
+
363
+
364
+ # SCHEMA STATEMENTS ========================================
365
+
366
+ def columns(table_name, name = nil) # :nodoc:
367
+ sql = <<-END_SQL
368
+ SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
369
+ f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
370
+ COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
371
+ COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
372
+ FROM rdb$relation_fields r
373
+ JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
374
+ WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
375
+ ORDER BY r.rdb$field_position
376
+ END_SQL
377
+ execute(sql, name).collect do |field|
378
+ field_values = field.values.collect do |value|
379
+ case value
380
+ when String then value.rstrip
381
+ when FireRuby::Blob then value.to_s
382
+ else value
383
+ end
384
+ end
385
+ FirebirdColumn.new(*field_values)
386
+ end
387
+ end
388
+
389
+ private
390
+ def select(sql, name = nil)
391
+ execute(sql, name).collect do |row|
392
+ hashed_row = {}
393
+ row.each do |column, value|
394
+ value = value.to_s if FireRuby::Blob === value
395
+ hashed_row[fb_to_ar_case(column)] = value
396
+ end
397
+ hashed_row
398
+ end
399
+ end
400
+
401
+ # Maps uppercase Firebird column names to lowercase for ActiveRecord;
402
+ # mixed-case columns retain their original case.
403
+ def fb_to_ar_case(column_name)
404
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
405
+ end
406
+
407
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
408
+ # mixed-case columns retain their original case.
409
+ def ar_to_fb_case(column_name)
410
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
411
+ end
412
+ end
413
+ end
414
+ end