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.
- data/CHANGELOG +91 -0
- data/lib/active_record.rb +2 -2
- data/lib/active_record/acts/list.rb +16 -12
- data/lib/active_record/acts/tree.rb +2 -2
- data/lib/active_record/aggregations.rb +6 -0
- data/lib/active_record/associations.rb +38 -16
- data/lib/active_record/associations/has_many_association.rb +2 -1
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/base.rb +46 -33
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +33 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +11 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +41 -21
- data/lib/active_record/connection_adapters/firebird_adapter.rb +414 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -29
- data/lib/active_record/connection_adapters/oci_adapter.rb +141 -21
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +82 -21
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +39 -6
- data/lib/active_record/fixtures.rb +1 -0
- data/lib/active_record/migration.rb +30 -13
- data/lib/active_record/validations.rb +18 -7
- data/lib/active_record/vendor/mysql.rb +89 -12
- data/lib/active_record/version.rb +2 -2
- data/rakefile +38 -3
- data/test/abstract_unit.rb +5 -0
- data/test/aggregations_test.rb +19 -0
- data/test/associations_go_eager_test.rb +26 -2
- data/test/associations_test.rb +29 -10
- data/test/base_test.rb +57 -6
- data/test/binary_test.rb +3 -3
- data/test/connections/native_db2/connection.rb +1 -1
- data/test/connections/native_firebird/connection.rb +24 -0
- data/test/connections/native_mysql/connection.rb +1 -1
- data/test/connections/native_oci/connection.rb +1 -1
- data/test/connections/native_postgresql/connection.rb +6 -6
- data/test/connections/native_sqlite/connection.rb +1 -1
- data/test/connections/native_sqlite3/connection.rb +1 -1
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/connections/native_sqlserver/connection.rb +1 -1
- data/test/connections/native_sqlserver_odbc/connection.rb +1 -1
- data/test/default_test_firebird.rb +16 -0
- data/test/deprecated_associations_test.rb +1 -1
- data/test/finder_test.rb +11 -1
- data/test/fixtures/author.rb +30 -30
- data/test/fixtures/comment.rb +1 -1
- data/test/fixtures/company.rb +3 -1
- data/test/fixtures/customer.rb +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +54 -0
- data/test/fixtures/db_definitions/firebird.sql +259 -0
- data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
- data/test/fixtures/db_definitions/firebird2.sql +6 -0
- data/test/fixtures/db_definitions/oci.sql +8 -0
- data/test/fixtures/db_definitions/postgresql.sql +3 -2
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/mixin.rb +11 -1
- data/test/fixtures/mixins.yml +20 -1
- data/test/fixtures_test.rb +65 -45
- data/test/inheritance_test.rb +1 -1
- data/test/migration_test.rb +7 -1
- data/test/mixin_test.rb +267 -98
- data/test/multiple_db_test.rb +13 -1
- data/test/pk_test.rb +1 -0
- metadata +11 -5
- data/lib/active_record/vendor/mysql411.rb +0 -311
- 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[
|
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
|
-
|
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[
|
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
|
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 "
|
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
|
-
|
45
|
-
return rt
|
51
|
+
rt, @runtime = @runtime, 0
|
52
|
+
rt
|
46
53
|
end
|
47
54
|
|
48
|
-
|
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
|
-
|
51
|
-
if
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
63
|
-
nil
|
78
|
+
yield
|
64
79
|
end
|
65
|
-
|
66
|
-
log_info(
|
67
|
-
|
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
|