Class: ActiveRecord::ConnectionAdapters::FbAdapter
- Inherits:
-
AbstractAdapter
- Object
- AbstractAdapter
- ActiveRecord::ConnectionAdapters::FbAdapter
- Defined in:
- lib/active_record/connection_adapters/fb_adapter.rb
Overview
The Fb adapter relies on the Fb extension.
Usage Notes
Sequence (Generator) Names
The Fb adapter supports the same approach adopted for the Oracle adapter. See ActiveRecord::Base#set_sequence_name for more details.
Note that in general there is no need to create a BEFORE INSERT
trigger corresponding to a Firebird sequence generator when using ActiveRecord. In other words, you don’t have to try to make Firebird simulate an AUTO_INCREMENT
or IDENTITY
column. When saving a new record, ActiveRecord pre-fetches the next sequence value for the table and explicitly includes it in the INSERT
statement. (Pre-fetching the next primary key value is the only reliable method for the Fb adapter to report back the id
after a successful insert.)
BOOLEAN Domain
Firebird 1.5 does not provide a native BOOLEAN
type. But you can easily define a BOOLEAN
domain for this purpose, e.g.:
CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
When the Fb adapter encounters a column that is based on a domain that includes “BOOLEAN” in the domain name, it will attempt to treat the column as a BOOLEAN
.
By default, the Fb adapter will assume that the BOOLEAN domain is defined as above. This can be modified if needed. For example, if you have a legacy schema with the following BOOLEAN
domain defined:
CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
…you can add the following line to your environment.rb
file:
ActiveRecord::ConnectionAdapters::Fb.boolean_domain = { :true => 'T', :false => 'F' }
Column Name Case Semantics
Firebird and ActiveRecord have somewhat conflicting case semantics for column names.
- Firebird
-
The standard practice is to use unquoted column names, which can be thought of as case-insensitive. (In fact, Firebird converts them to uppercase.) Quoted column names (not typically used) are case-sensitive.
- ActiveRecord
-
Attribute accessors corresponding to column names are case-sensitive. The defaults for primary key and inheritance columns are lowercase, and in general, people use lowercase attribute names.
In order to map between the differing semantics in a way that conforms to common usage for both Firebird and ActiveRecord, uppercase column names in Firebird are converted to lowercase attribute names in ActiveRecord, and vice-versa. Mixed-case column names retain their case in both directions. Lowercase (quoted) Firebird column names are not supported. This is similar to the solutions adopted by other adapters.
In general, the best approach is to use unquoted (case-insensitive) column names in your Firebird DDL (or if you must quote, use uppercase column names). These will correspond to lowercase attributes in ActiveRecord.
For example, a Firebird table based on the following DDL:
CREATE TABLE products (
id BIGINT NOT NULL PRIMARY KEY,
"TYPE" VARCHAR(50),
name VARCHAR(255) );
…will correspond to an ActiveRecord model class called Product
with the following attributes: id
, type
, name
.
Quoting "TYPE"
and other Firebird reserved words:
In ActiveRecord, the default inheritance column name is type
. The word type is a Firebird reserved word, so it must be quoted in any Firebird SQL statements. Because of the case mapping described above, you should always reference this column using quoted-uppercase syntax ("TYPE"
) within Firebird DDL or other SQL statements (as in the example above). This holds true for any other Firebird reserved words used as column names as well.
Migrations
The Fb adapter does not currently support Migrations.
Connection Options
The following options are supported by the Fb adapter.
:database
-
Required option. Specifies one of: (i) a Firebird database alias; (ii) the full path of a database file; or (iii) a full Firebird connection string. Do not specify
:host
,:service
or:port
as separate options when using a full connection string. :username
-
Specifies the database user. Defaults to ‘sysdba’.
:password
-
Specifies the database password. Defaults to ‘masterkey’.
:charset
-
Specifies the character set to be used by the connection. Refer to the Firebird documentation for valid options.
Constant Summary collapse
- @@boolean_domain =
{ :true => 1, :false => 0 }
Instance Method Summary collapse
-
#active? ⇒ Boolean
CONNECTION MANAGEMENT ====================================.
-
#adapter_name ⇒ Object
:nodoc:.
-
#add_limit_offset!(sql, options) ⇒ Object
:nodoc:.
-
#add_lock!(sql, options) ⇒ Object
:nodoc:.
-
#begin_db_transaction ⇒ Object
:nodoc:.
-
#columns(table_name, name = nil) ⇒ Object
SCHEMA STATEMENTS ========================================.
-
#commit_db_transaction ⇒ Object
:nodoc:.
-
#default_sequence_name(table_name, primary_key) ⇒ Object
:nodoc:.
- #disconnect! ⇒ Object
-
#execute(sql, name = nil, &block) ⇒ Object
(also: #update, #delete)
:nodoc:.
- #expand(sql, args) ⇒ Object
-
#indexes(table_name, name = nil) ⇒ Object
:nodoc:.
-
#initialize(connection, logger, connection_params = nil) ⇒ FbAdapter
constructor
A new instance of FbAdapter.
-
#insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) ⇒ Object
:nodoc:.
- #log(sql, args, name, &block) ⇒ Object
- #native_database_types ⇒ Object
-
#next_sequence_value(sequence_name) ⇒ Object
Returns the next sequence value from a sequence generator.
-
#prefetch_primary_key?(table_name = nil) ⇒ Boolean
Returns true for Fb adapter (since Firebird requires primary key values to be pre-fetched before insert).
-
#quote(value, column = nil) ⇒ Object
QUOTING ==================================================.
-
#quote_column_name(column_name) ⇒ Object
:nodoc:.
- #quote_date(value) ⇒ Object
- #quote_number(value) ⇒ Object
- #quote_object(obj) ⇒ Object
-
#quote_string(string) ⇒ Object
:nodoc:.
- #quote_timestamp(value) ⇒ Object
-
#quoted_false ⇒ Object
:nodoc:.
-
#quoted_true ⇒ Object
:nodoc:.
- #reconnect! ⇒ Object
- #remove_index(table_name, options = {}) ⇒ Object
- #rename_column(table_name, column_name, new_column_name) ⇒ Object
-
#rollback_db_transaction ⇒ Object
:nodoc:.
-
#select_all(sql, name = nil, format = :hash) ⇒ Object
:nodoc:.
-
#select_one(sql, name = nil, format = :hash) ⇒ Object
:nodoc:.
- #supports_migrations? ⇒ Boolean
- #table_alias_length ⇒ Object
- #tables(name = nil) ⇒ Object
-
#translate(sql) {|sql, args| ... } ⇒ Object
DATABASE STATEMENTS ======================================.
Constructor Details
#initialize(connection, logger, connection_params = nil) ⇒ FbAdapter
Returns a new instance of FbAdapter.
195 196 197 198 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 195 def initialize(connection, logger, connection_params=nil) super(connection, logger) @connection_params = connection_params end |
Instance Method Details
#active? ⇒ Boolean
CONNECTION MANAGEMENT ====================================
274 275 276 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 274 def active? @connection.open? end |
#adapter_name ⇒ Object
:nodoc:
200 201 202 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 200 def adapter_name # :nodoc: 'Fb' end |
#add_limit_offset!(sql, options) ⇒ Object
:nodoc:
358 359 360 361 362 363 364 365 366 367 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 358 def add_limit_offset!(sql, ) # :nodoc: if limit = [:limit] if offset = [:offset] sql << " ROWS #{offset.to_i + 1} TO #{offset.to_i + limit.to_i}" else sql << " ROWS #{limit.to_i}" end end sql end |
#add_lock!(sql, options) ⇒ Object
:nodoc:
354 355 356 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 354 def add_lock!(sql, ) # :nodoc: sql end |
#begin_db_transaction ⇒ Object
:nodoc:
342 343 344 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 342 def begin_db_transaction() # :nodoc: @transaction = @connection.transaction('READ COMMITTED') end |
#columns(table_name, name = nil) ⇒ Object
SCHEMA STATEMENTS ========================================
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 378 def columns(table_name, name = nil) # :nodoc: sql = <<-END_SQL SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type, f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale, COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source, COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag FROM rdb$relation_fields r JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}' ORDER BY r.rdb$field_position END_SQL select_all(sql, name, :array).collect do |field| field_values = field.collect do |value| case value when String then value.rstrip else value end end FbColumn.new(*field_values) end end |
#commit_db_transaction ⇒ Object
:nodoc:
346 347 348 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 346 def commit_db_transaction() # :nodoc: @transaction = @connection.commit end |
#default_sequence_name(table_name, primary_key) ⇒ Object
:nodoc:
210 211 212 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 210 def default_sequence_name(table_name, primary_key) # :nodoc: "#{table_name}_seq" end |
#disconnect! ⇒ Object
278 279 280 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 278 def disconnect! @connection.close rescue nil end |
#execute(sql, name = nil, &block) ⇒ Object Also known as: update, delete
:nodoc:
326 327 328 329 330 331 332 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 326 def execute(sql, name = nil, &block) # :nodoc: translate(sql) do |sql, args| log(sql, args, name) do @connection.execute(sql, *args, &block) end end end |
#expand(sql, args) ⇒ Object
302 303 304 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 302 def (sql, args) sql + ', ' + args * ', ' end |
#indexes(table_name, name = nil) ⇒ Object
:nodoc:
404 405 406 407 408 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 404 def indexes(table_name, name = nil) #:nodoc: result = @connection.indexes.values.select {|ix| ix.table_name == table_name && ix.index_name !~ /^rdb\$/ } indexes = result.map {|ix| IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns) } indexes end |
#insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) ⇒ Object
:nodoc:
334 335 336 337 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 334 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc: execute(sql, name) id_value end |
#log(sql, args, name, &block) ⇒ Object
306 307 308 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 306 def log(sql, args, name, &block) super((sql, args), name, &block) end |
#native_database_types ⇒ Object
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 426 def native_database_types { :primary_key => "integer not null primary key", :string => { :name => "varchar", :limit => 255 }, :text => { :name => "blob sub_type text" }, :integer => { :name => "integer" }, :float => { :name => "float" }, :decimal => { :name => "decimal" }, :datetime => { :name => "timestamp" }, :timestamp => { :name => "timestamp" }, :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "blob" }, :boolean => { :name => "integer" } } end |
#next_sequence_value(sequence_name) ⇒ Object
Returns the next sequence value from a sequence generator. Not generally called directly; used by ActiveRecord to get the next primary key value when inserting a new database record (see #prefetch_primary_key?).
372 373 374 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 372 def next_sequence_value(sequence_name) select_one("SELECT GEN_ID(#{sequence_name}, 1) FROM RDB$DATABASE", nil, :array).first end |
#prefetch_primary_key?(table_name = nil) ⇒ Boolean
Returns true for Fb adapter (since Firebird requires primary key values to be pre-fetched before insert). See also #next_sequence_value.
206 207 208 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 206 def prefetch_primary_key?(table_name = nil) true end |
#quote(value, column = nil) ⇒ Object
QUOTING ==================================================
217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 217 def quote(value, column = nil) # :nodoc: case value when String "@#{Base64.encode64(value).chop}@" when Float, Fixnum, Bignum, BigDecimal then quote_number(value) when Date then quote_date(value) when Time, DateTime then (value) when NilClass then "NULL" when TrueClass then (column && column.type == :integer ? '1' : quoted_true) when FalseClass then (column && column.type == :integer ? '0' : quoted_false) else quote_object(value) end end |
#quote_column_name(column_name) ⇒ Object
:nodoc:
259 260 261 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 259 def quote_column_name(column_name) # :nodoc: %Q("#{ar_to_fb_case(column_name.to_s)}") end |
#quote_date(value) ⇒ Object
237 238 239 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 237 def quote_date(value) "@#{Base64.encode64(value.strftime('%Y-%m-%d')).chop}@" end |
#quote_number(value) ⇒ Object
232 233 234 235 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 232 def quote_number(value) # "@#{Base64.encode64(value.to_s).chop}@" value.to_s end |
#quote_object(obj) ⇒ Object
249 250 251 252 253 254 255 256 257 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 249 def quote_object(obj) if obj.respond_to?(:quoted_id) obj.quoted_id elsif obj.respond_to?(:to_str) "@#{Base64.encode64(obj.to_str).chop}@" else "@#{Base64.encode64(obj.to_yaml).chop}@" end end |
#quote_string(string) ⇒ Object
:nodoc:
245 246 247 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 245 def quote_string(string) # :nodoc: string.gsub(/'/, "''") end |
#quote_timestamp(value) ⇒ Object
241 242 243 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 241 def (value) "@#{Base64.encode64(value.strftime('%Y-%m-%d %H:%M:%S')).chop}@" end |
#quoted_false ⇒ Object
:nodoc:
267 268 269 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 267 def quoted_false # :nodoc: quote(boolean_domain[:false]) end |
#quoted_true ⇒ Object
:nodoc:
263 264 265 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 263 def quoted_true # :nodoc: quote(boolean_domain[:true]) end |
#reconnect! ⇒ Object
282 283 284 285 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 282 def reconnect! disconnect! @connection = Fb::Database.connect(@connection_params) end |
#remove_index(table_name, options = {}) ⇒ Object
418 419 420 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 418 def remove_index(table_name, = {}) execute "DROP INDEX #{quote_column_name(index_name(table_name, ))}" end |
#rename_column(table_name, column_name, new_column_name) ⇒ Object
414 415 416 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 414 def rename_column(table_name, column_name, new_column_name) execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TO #{new_column_name}" end |
#rollback_db_transaction ⇒ Object
:nodoc:
350 351 352 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 350 def rollback_db_transaction() # :nodoc: @transaction = @connection.rollback end |
#select_all(sql, name = nil, format = :hash) ⇒ Object
:nodoc:
310 311 312 313 314 315 316 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 310 def select_all(sql, name = nil, format = :hash) # :nodoc: translate(sql) do |sql, args| log(sql, args, name) do @connection.query(format, sql, *args) end end end |
#select_one(sql, name = nil, format = :hash) ⇒ Object
:nodoc:
318 319 320 321 322 323 324 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 318 def select_one(sql, name = nil, format = :hash) # :nodoc: translate(sql) do |sql, args| log(sql, args, name) do @connection.query(format, sql, *args).first end end end |
#supports_migrations? ⇒ Boolean
422 423 424 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 422 def supports_migrations? false end |
#table_alias_length ⇒ Object
410 411 412 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 410 def table_alias_length 255 end |
#tables(name = nil) ⇒ Object
400 401 402 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 400 def tables(name = nil) @connection.table_names.map {|t| t.downcase } end |
#translate(sql) {|sql, args| ... } ⇒ Object
DATABASE STATEMENTS ======================================
289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/active_record/connection_adapters/fb_adapter.rb', line 289 def translate(sql) sql.gsub!(/\bIN\s+\(NULL\)/i, 'IS NULL') sql.sub!(/\bWHERE\s.*$/im) do |m| m.gsub(/\s=\s*NULL\b/i, ' IS NULL') end sql.gsub!(/\sIN\s+\([^\)]*\)/mi) do |m| m.gsub(/\(([^\)]*)\)/m) { |n| n.gsub(/\@(.*?)\@/m) { |n| "'#{quote_string(Base64.decode64(n[1..-1]))}'" } } end args = [] sql.gsub!(/\@(.*?)\@/m) { |m| args << Base64.decode64(m[1..-1]); '?' } yield(sql, args) if block_given? end |