rubyfb 0.5.2-x86-mswin32-60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/CHANGELOG +6 -0
  2. data/LICENSE +411 -0
  3. data/Manifest +75 -0
  4. data/README +460 -0
  5. data/Rakefile +21 -0
  6. data/examples/example01.rb +65 -0
  7. data/ext/AddUser.c +464 -0
  8. data/ext/AddUser.h +37 -0
  9. data/ext/Backup.c +783 -0
  10. data/ext/Backup.h +37 -0
  11. data/ext/Blob.c +421 -0
  12. data/ext/Blob.h +65 -0
  13. data/ext/Common.c +54 -0
  14. data/ext/Common.h +37 -0
  15. data/ext/Connection.c +863 -0
  16. data/ext/Connection.h +50 -0
  17. data/ext/DataArea.c +274 -0
  18. data/ext/DataArea.h +38 -0
  19. data/ext/Database.c +449 -0
  20. data/ext/Database.h +48 -0
  21. data/ext/FireRuby.c +240 -0
  22. data/ext/FireRuby.h +50 -0
  23. data/ext/FireRubyException.c +268 -0
  24. data/ext/FireRubyException.h +51 -0
  25. data/ext/Generator.c +689 -0
  26. data/ext/Generator.h +53 -0
  27. data/ext/RemoveUser.c +212 -0
  28. data/ext/RemoveUser.h +37 -0
  29. data/ext/Restore.c +855 -0
  30. data/ext/Restore.h +37 -0
  31. data/ext/ResultSet.c +810 -0
  32. data/ext/ResultSet.h +60 -0
  33. data/ext/Row.c +965 -0
  34. data/ext/Row.h +55 -0
  35. data/ext/ServiceManager.c +316 -0
  36. data/ext/ServiceManager.h +48 -0
  37. data/ext/Services.c +124 -0
  38. data/ext/Services.h +42 -0
  39. data/ext/Statement.c +785 -0
  40. data/ext/Statement.h +62 -0
  41. data/ext/Transaction.c +684 -0
  42. data/ext/Transaction.h +50 -0
  43. data/ext/TypeMap.c +1182 -0
  44. data/ext/TypeMap.h +51 -0
  45. data/ext/extconf.rb +30 -0
  46. data/lib/SQLType.rb +224 -0
  47. data/lib/active_record/connection_adapters/rubyfb_adapter.rb +805 -0
  48. data/lib/mkdoc +1 -0
  49. data/lib/rubyfb.rb +2 -0
  50. data/lib/rubyfb_lib.so +0 -0
  51. data/lib/src.rb +1800 -0
  52. data/mswin32fb/fbclient_ms.lib +0 -0
  53. data/mswin32fb/ibase.h +2555 -0
  54. data/mswin32fb/iberror.h +1741 -0
  55. data/rubyfb.gemspec +31 -0
  56. data/test/AddRemoveUserTest.rb +56 -0
  57. data/test/BackupRestoreTest.rb +99 -0
  58. data/test/BlobTest.rb +57 -0
  59. data/test/CharacterSetTest.rb +63 -0
  60. data/test/ConnectionTest.rb +111 -0
  61. data/test/DDLTest.rb +54 -0
  62. data/test/DatabaseTest.rb +83 -0
  63. data/test/GeneratorTest.rb +50 -0
  64. data/test/KeyTest.rb +140 -0
  65. data/test/ResultSetTest.rb +162 -0
  66. data/test/RoleTest.rb +73 -0
  67. data/test/RowCountTest.rb +65 -0
  68. data/test/RowTest.rb +203 -0
  69. data/test/SQLTest.rb +182 -0
  70. data/test/SQLTypeTest.rb +101 -0
  71. data/test/ServiceManagerTest.rb +29 -0
  72. data/test/StatementTest.rb +135 -0
  73. data/test/TestSetup.rb +11 -0
  74. data/test/TransactionTest.rb +112 -0
  75. data/test/TypeTest.rb +92 -0
  76. data/test/UnitTest.rb +65 -0
  77. metadata +143 -0
data/ext/TypeMap.h ADDED
@@ -0,0 +1,51 @@
1
+ /*------------------------------------------------------------------------------
2
+ * TypeMap.h
3
+ *----------------------------------------------------------------------------*/
4
+ /**
5
+ * Copyright � Peter Wood, 2005
6
+ *
7
+ * The contents of this file are subject to the Mozilla Public License Version
8
+ * 1.1 (the "License"); you may not use this file except in compliance with the
9
+ * License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.mozilla.org/MPL/
12
+ *
13
+ * Software distributed under the License is distributed on an "AS IS" basis,
14
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
15
+ * the specificlanguage governing rights and limitations under the License.
16
+ *
17
+ * The Original Code is the FireRuby extension for the Ruby language.
18
+ *
19
+ * The Initial Developer of the Original Code is Peter Wood. All Rights
20
+ * Reserved.
21
+ *
22
+ * @author Peter Wood
23
+ * @version 1.0
24
+ */
25
+ #ifndef FIRERUBY_TYPE_MAP_H
26
+ #define FIRERUBY_TYPE_MAP_H
27
+
28
+ #ifndef IBASE_H_INCLUDED
29
+ #include "ibase.h"
30
+ #define IBASE_H_INCLUDED
31
+ #endif
32
+
33
+ #ifndef RUBY_H_INCLUDED
34
+ #include "ruby.h"
35
+ #define RUBY_H_INCLUDED
36
+ #endif
37
+
38
+ #ifndef FIRERUBY_DATABASE_H
39
+ #include "ResultSet.h"
40
+ #endif
41
+
42
+ /* Function prototypes. */
43
+ VALUE toValue(XSQLVAR *, isc_db_handle *, isc_tr_handle *);
44
+ VALUE toArray(VALUE);
45
+ void setParameters(XSQLDA *, VALUE, VALUE);
46
+ VALUE getModule(const char *);
47
+ VALUE getClass(const char *);
48
+ VALUE getClassInModule(const char *, VALUE);
49
+ VALUE getModuleInModule(const char *, VALUE);
50
+
51
+ #endif /* FIRERUBY_TYPE_MAP_H */
data/ext/extconf.rb ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV['ARCHFLAGS']='-arch '+`arch`.strip if PLATFORM.include?("darwin")
4
+
5
+ require 'mkmf'
6
+
7
+ # Add the framework link for Mac OS X.
8
+ if PLATFORM.include?("darwin")
9
+ $LDFLAGS = $LDFLAGS + " -framework Firebird"
10
+ $CFLAGS = $CFLAGS + " -DOS_UNIX"
11
+ firebird_include="/Library/Frameworks/Firebird.framework/Headers"
12
+ firebird_lib="/Library/Frameworks/Firebird.framework/Libraries"
13
+ elsif PLATFORM.include?("win32")
14
+ $LDFLAGS = $LDFLAGS + " fbclient_ms.lib"
15
+ $CFLAGS = "-MT #{$CFLAGS}".gsub!(/-MD\s*/, '') + " -DOS_WIN32"
16
+ dir_config("win32")
17
+ dir_config("winsdk")
18
+ dir_config("dotnet")
19
+ firebird_include="../mswin32fb"
20
+ firebird_lib="../mswin32fb"
21
+ elsif PLATFORM.include?("linux")
22
+ $LDFLAGS = $LDFLAGS + " -lfbclient -lpthread"
23
+ $CFLAGS = $CFLAGS + " -DOS_UNIX"
24
+ end
25
+
26
+ # Make sure the firebird stuff is included.
27
+ dir_config("firebird", firebird_include, firebird_lib)
28
+
29
+ # Generate the Makefile.
30
+ create_makefile("rubyfb_lib")
data/lib/SQLType.rb ADDED
@@ -0,0 +1,224 @@
1
+ #-------------------------------------------------------------------------------
2
+ # SQLType.rb
3
+ #-------------------------------------------------------------------------------
4
+ # Copyright � Peter Wood, 2005
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved.
20
+
21
+ module FireRuby
22
+ # This class is used to represent SQL table column tables.
23
+ class SQLType
24
+ # A definition for a base SQL type.
25
+ BIGINT = :BIGINT
26
+
27
+ # A definition for a base SQL type.
28
+ BLOB = :BLOB
29
+
30
+ # A definition for a base SQL type.
31
+ CHAR = :CHAR
32
+
33
+ # A definition for a base SQL type.
34
+ DATE = :DATE
35
+
36
+ # A definition for a base SQL type.
37
+ DECIMAL = :DECIMAL
38
+
39
+ # A definition for a base SQL type.
40
+ DOUBLE = :DOUBLE
41
+
42
+ # A definition for a base SQL type.
43
+ FLOAT = :FLOAT
44
+
45
+ # A definition for a base SQL type.
46
+ INTEGER = :INTEGER
47
+
48
+ # A definition for a base SQL type.
49
+ NUMERIC = :NUMERIC
50
+
51
+ # A definition for a base SQL type.
52
+ SMALLINT = :SMALLINT
53
+
54
+ # A definition for a base SQL type.
55
+ TIME = :TIME
56
+
57
+ # A definition for a base SQL type.
58
+ TIMESTAMP = :TIMESTAMP
59
+
60
+ # A definition for a base SQL type.
61
+ VARCHAR = :VARCHAR
62
+
63
+ # Attribute accessor.
64
+ attr_reader :type, :length, :precision, :scale, :subtype
65
+
66
+
67
+ # This is the constructor for the SQLType class.
68
+ #
69
+ # ==== Parameters
70
+ # type:: The base type for the SQLType object. Must be one of the
71
+ # base types defined within the class.
72
+ # length:: The length setting for the type. Defaults to nil.
73
+ # precision:: The precision setting for the type. Defaults to nil.
74
+ # scale:: The scale setting for the type. Defaults to nil.
75
+ # subtype:: The SQL sub-type setting. Defaults to nil.
76
+ def initialize(type, length=nil, precision=nil, scale=nil, subtype=nil)
77
+ @type = type
78
+ @length = length
79
+ @precision = precision
80
+ @scale = scale
81
+ @subtype = subtype
82
+ end
83
+
84
+
85
+ # This class method fetches the type details for a named table. The
86
+ # method returns a hash that links column names to SQLType objects.
87
+ #
88
+ # ==== Parameters
89
+ # table:: A string containing the name of the table.
90
+ # connection:: A reference to the connection to be used to determine
91
+ # the type information.
92
+ #
93
+ # ==== Exception
94
+ # FireRubyException:: Generated if an invalid table name is specified
95
+ # or an SQL error occurs.
96
+ def SQLType.for_table(table, connection)
97
+ # Check for naughty table names.
98
+ if /\s+/ =~ table
99
+ raise FireRubyException.new("'#{table}' is not a valid table name.")
100
+ end
101
+
102
+ types = {}
103
+ begin
104
+ sql = "SELECT RF.RDB$FIELD_NAME, F.RDB$FIELD_TYPE, "\
105
+ "F.RDB$FIELD_LENGTH, F.RDB$FIELD_PRECISION, "\
106
+ "F.RDB$FIELD_SCALE * -1, F.RDB$FIELD_SUB_TYPE "\
107
+ "FROM RDB$RELATION_FIELDS RF, RDB$FIELDS F "\
108
+ "WHERE RF.RDB$RELATION_NAME = UPPER('#{table}') "\
109
+ "AND RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME"
110
+
111
+ connection.start_transaction do |tx|
112
+ tx.execute(sql) do |row|
113
+ sql_type = SQLType.to_base_type(row[1], row[5])
114
+ type = nil
115
+ case sql_type
116
+ when BLOB
117
+ type = SQLType.new(sql_type, nil, nil, nil, row[5])
118
+
119
+ when CHAR, VARCHAR
120
+ type = SQLType.new(sql_type, row[2])
121
+
122
+ when DECIMAL, NUMERIC
123
+ type = SQLType.new(sql_type, nil, row[3], row[4])
124
+
125
+ else
126
+ type = SQLType.new(sql_type)
127
+ end
128
+ types[row[0].strip] = type
129
+ end
130
+
131
+ end
132
+ end
133
+ types
134
+ end
135
+
136
+
137
+ # This method overloads the equivalence test operator for the SQLType
138
+ # class.
139
+ #
140
+ # ==== Parameters
141
+ # object:: A reference to the object to be compared with.
142
+ def ==(object)
143
+ result = false
144
+ if object.instance_of?(SQLType)
145
+ result = (@type == object.type &&
146
+ @length == object.length &&
147
+ @precision == object.precision &&
148
+ @scale == object.scale &&
149
+ @subtype == object.subtype)
150
+ end
151
+ result
152
+ end
153
+
154
+
155
+ # This method generates a textual description for a SQLType object.
156
+ def to_s
157
+ if @type == SQLType::DECIMAL or @type == SQLType::NUMERIC
158
+ "#{@type.id2name}(#{@precision},#{@scale})"
159
+ elsif @type == SQLType::BLOB
160
+ "#{@type.id2name} SUB TYPE #{@subtype}"
161
+ elsif @type == SQLType::CHAR or @type == SQLType::VARCHAR
162
+ "#{@type.id2name}(#{@length})"
163
+ else
164
+ @type.id2name
165
+ end
166
+ end
167
+
168
+
169
+ # This class method converts a Firebird internal type to a SQLType base
170
+ # type.
171
+ #
172
+ # ==== Parameters
173
+ # type:: A reference to the Firebird field type value.
174
+ # subtype:: A reference to the Firebird field subtype value.
175
+ def SQLType.to_base_type(type, subtype)
176
+ case type
177
+ when 16 # BIGINT, DECIMAL, NUMERIC
178
+ case subtype
179
+ when 1 then SQLType::NUMERIC
180
+ when 2 then SQLType::DECIMAL
181
+ else SQLType::BIGINT
182
+ end
183
+
184
+ when 261 # BLOB
185
+ SQLType::BLOB
186
+
187
+ when 14 # CHAR
188
+ SQLType::CHAR
189
+
190
+ when 12 # DATE
191
+ SQLType::DATE
192
+
193
+ when 27 # DOUBLE
194
+ SQLType::DOUBLE
195
+
196
+ when 10 # FLOAT
197
+ SQLType::FLOAT
198
+
199
+ when 8 # INTEGER, DECIMAL, NUMERIC
200
+ case subtype
201
+ when 1 then SQLType::NUMERIC
202
+ when 2 then SQLType::DECIMAL
203
+ else SQLType::INTEGER
204
+ end
205
+
206
+ when 7 # SMALLINT, DECIMAL, NUMERIC
207
+ case subtype
208
+ when 1 then SQLType::NUMERIC
209
+ when 2 then SQLType::DECIMAL
210
+ else SQLType::SMALLINT
211
+ end
212
+
213
+ when 13 # TIME
214
+ SQLType::TIME
215
+
216
+ when 35 # TIMESTAMP
217
+ SQLType::TIMESTAMP
218
+
219
+ when 37 # VARCHAR
220
+ SQLType::VARCHAR
221
+ end
222
+ end
223
+ end # End of the SQLType class.
224
+ end # End of the FireRuby module.
@@ -0,0 +1,805 @@
1
+ # Author: Ken Kunz <kennethkunz@gmail.com>
2
+ require 'active_record/connection_adapters/abstract_adapter'
3
+
4
+ module FireRuby # :nodoc: all
5
+ NON_EXISTENT_DOMAIN_ERROR = "335544569"
6
+ class Database
7
+ def self.db_string_for(config)
8
+ unless config.has_key?(:database)
9
+ raise ArgumentError, "No database specified. Missing argument: database."
10
+ end
11
+ host_string = config.values_at(:host, :service, :port).compact.first(2).join("/") if config[:host]
12
+ [host_string, config[:database]].join(":")
13
+ end
14
+
15
+ def self.new_from_config(config)
16
+ db = new db_string_for(config)
17
+ db.character_set = config[:charset]
18
+ return db
19
+ end
20
+ end
21
+ end
22
+
23
+ module ActiveRecord
24
+ class Base
25
+ def self.rubyfb_connection(config) # :nodoc:
26
+ require_library_or_gem 'rubyfb'
27
+ config.symbolize_keys!
28
+ db = FireRuby::Database.new_from_config(config)
29
+ connection_params = config.values_at(:username, :password)
30
+ connection = db.connect(*connection_params)
31
+ ConnectionAdapters::RubyfbAdapter.new(connection, logger, connection_params)
32
+ end
33
+
34
+ after_save :write_blobs
35
+ def write_blobs #:nodoc:
36
+ if connection.is_a?(ConnectionAdapters::RubyfbAdapter)
37
+ connection.write_blobs(self.class.table_name, self.class, attributes)
38
+ end
39
+ end
40
+
41
+ private :write_blobs
42
+ end
43
+
44
+ module ConnectionAdapters
45
+ class FirebirdColumn < Column # :nodoc:
46
+ VARCHAR_MAX_LENGTH = 32_765
47
+
48
+ def initialize(connection, name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
49
+ @firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
50
+
51
+ super(name.downcase, nil, @firebird_type, !null_flag)
52
+
53
+ @limit = decide_limit(length)
54
+ @domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale.abs
55
+ @type = simplified_type(@firebird_type)
56
+ @default = parse_default(default_source) if default_source
57
+ @default = type_cast(decide_default(connection)) if @default
58
+ end
59
+
60
+ def self.value_to_boolean(value)
61
+ %W(#{RubyfbAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
62
+ end
63
+
64
+ private
65
+ def parse_default(default_source)
66
+ default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
67
+ return $1 unless $1.upcase == "NULL"
68
+ end
69
+
70
+ def decide_default(connection)
71
+ if @default =~ /^'?(\d*\.?\d+)'?$/ or
72
+ @default =~ /^'(.*)'$/ && [:text, :string, :binary, :boolean].include?(type)
73
+ $1
74
+ else
75
+ firebird_cast_default(connection)
76
+ end
77
+ end
78
+
79
+ # Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
80
+ # This enables Firebird to provide an actual value when context variables are used as column
81
+ # defaults (such as CURRENT_TIMESTAMP).
82
+ def firebird_cast_default(connection)
83
+ sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
84
+ connection.select_rows(sql).first[0]
85
+ end
86
+
87
+ def decide_limit(length)
88
+ if text? or number?
89
+ length
90
+ end
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
+ case field_type
105
+ when /timestamp/i
106
+ :datetime
107
+ when /decimal|numeric|number/i
108
+ @scale == 0 ? :integer : :decimal
109
+ when /blob/i
110
+ @subtype == 1 ? :text : :binary
111
+ else
112
+ if @domain =~ /boolean/i
113
+ :boolean
114
+ else
115
+ super
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
122
+ # extension, version 0.4.0 or later (available as a gem or from
123
+ # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with
124
+ # Firebird 1.5.x on Linux, OS X and Win32 platforms.
125
+ #
126
+ # == Usage Notes
127
+ #
128
+ # === Sequence (Generator) Names
129
+ # The Firebird adapter supports the same approach adopted for the Oracle
130
+ # adapter. See ActiveRecord::Base#set_sequence_name for more details.
131
+ #
132
+ # Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
133
+ # trigger corresponding to a Firebird sequence generator when using
134
+ # ActiveRecord. In other words, you don't have to try to make Firebird
135
+ # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
136
+ # new record, ActiveRecord pre-fetches the next sequence value for the table
137
+ # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
138
+ # next primary key value is the only reliable method for the Firebird
139
+ # adapter to report back the +id+ after a successful insert.)
140
+ #
141
+ # === BOOLEAN Domain
142
+ # Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
143
+ # define a +BOOLEAN+ _domain_ for this purpose, e.g.:
144
+ #
145
+ # CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1) OR VALUE IS NULL);
146
+ #
147
+ # When the Firebird adapter encounters a column that is based on a domain
148
+ # that includes "BOOLEAN" in the domain name, it will attempt to treat
149
+ # the column as a +BOOLEAN+.
150
+ #
151
+ # By default, the Firebird adapter will assume that the BOOLEAN domain is
152
+ # defined as above. This can be modified if needed. For example, if you
153
+ # have a legacy schema with the following +BOOLEAN+ domain defined:
154
+ #
155
+ # CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
156
+ #
157
+ # ...you can add the following line to your <tt>environment.rb</tt> file:
158
+ #
159
+ # ActiveRecord::ConnectionAdapters::RubyfbAdapter.boolean_domain = { :true => 'T', :false => 'F' }
160
+ #
161
+ # === BLOB Elements
162
+ # The Firebird adapter currently provides only limited support for +BLOB+
163
+ # columns. You cannot currently retrieve a +BLOB+ as an IO stream.
164
+ # When selecting a +BLOB+, the entire element is converted into a String.
165
+ # +BLOB+ handling is supported by writing an empty +BLOB+ to the database on
166
+ # insert/update and then executing a second query to save the +BLOB+.
167
+ #
168
+ # === Column Name Case Semantics
169
+ # Firebird and ActiveRecord have somewhat conflicting case semantics for
170
+ # column names.
171
+ #
172
+ # [*Firebird*]
173
+ # The standard practice is to use unquoted column names, which can be
174
+ # thought of as case-insensitive. (In fact, Firebird converts them to
175
+ # uppercase.) Quoted column names (not typically used) are case-sensitive.
176
+ # [*ActiveRecord*]
177
+ # Attribute accessors corresponding to column names are case-sensitive.
178
+ # The defaults for primary key and inheritance columns are lowercase, and
179
+ # in general, people use lowercase attribute names.
180
+ #
181
+ # In order to map between the differing semantics in a way that conforms
182
+ # to common usage for both Firebird and ActiveRecord, uppercase column names
183
+ # in Firebird are converted to lowercase attribute names in ActiveRecord,
184
+ # and vice-versa. Mixed-case column names retain their case in both
185
+ # directions. Lowercase (quoted) Firebird column names are not supported.
186
+ # This is similar to the solutions adopted by other adapters.
187
+ #
188
+ # In general, the best approach is to use unqouted (case-insensitive) column
189
+ # names in your Firebird DDL (or if you must quote, use uppercase column
190
+ # names). These will correspond to lowercase attributes in ActiveRecord.
191
+ #
192
+ # For example, a Firebird table based on the following DDL:
193
+ #
194
+ # CREATE TABLE products (
195
+ # id BIGINT NOT NULL PRIMARY KEY,
196
+ # "TYPE" VARCHAR(50),
197
+ # name VARCHAR(255) );
198
+ #
199
+ # ...will correspond to an ActiveRecord model class called +Product+ with
200
+ # the following attributes: +id+, +type+, +name+.
201
+ #
202
+ # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
203
+ # In ActiveRecord, the default inheritance column name is +type+. The word
204
+ # _type_ is a Firebird reserved word, so it must be quoted in any Firebird
205
+ # SQL statements. Because of the case mapping described above, you should
206
+ # always reference this column using quoted-uppercase syntax
207
+ # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
208
+ # example above). This holds true for any other Firebird reserved words used
209
+ # as column names as well.
210
+ #
211
+ # === Migrations
212
+ # The Firebird Adapter now supports Migrations.
213
+ #
214
+ # ==== Create/Drop Table and Sequence Generators
215
+ # Creating or dropping a table will automatically create/drop a
216
+ # correpsonding sequence generator, using the default naming convension.
217
+ # You can specify a different name using the <tt>:sequence</tt> option; no
218
+ # generator is created if <tt>:sequence</tt> is set to +false+.
219
+ #
220
+ # ==== Rename Table
221
+ # The Firebird #rename_table Migration should be used with caution.
222
+ # Firebird 1.5 lacks built-in support for this feature, so it is
223
+ # implemented by making a copy of the original table (including column
224
+ # definitions, indexes and data records), and then dropping the original
225
+ # table. Constraints and Triggers are _not_ properly copied, so avoid
226
+ # this method if your original table includes constraints (other than
227
+ # the primary key) or triggers. (Consider manually copying your table
228
+ # or using a view instead.)
229
+ #
230
+ # == Connection Options
231
+ # The following options are supported by the Firebird adapter. None of the
232
+ # options have default values.
233
+ #
234
+ # <tt>:database</tt>::
235
+ # <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
236
+ # (ii) the full path of a database file; _or_ (iii) a full Firebird
237
+ # connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
238
+ # or <tt>:port</tt> as separate options when using a full connection
239
+ # string.</i>
240
+ # <tt>:host</tt>::
241
+ # Set to <tt>"remote.host.name"</tt> for remote database connections.
242
+ # May be omitted for local connections if a full database path is
243
+ # specified for <tt>:database</tt>. Some platforms require a value of
244
+ # <tt>"localhost"</tt> for local connections when using a Firebird
245
+ # database _alias_.
246
+ # <tt>:service</tt>::
247
+ # Specifies a service name for the connection. Only used if <tt>:host</tt>
248
+ # is provided. Required when connecting to a non-standard service.
249
+ # <tt>:port</tt>::
250
+ # Specifies the connection port. Only used if <tt>:host</tt> is provided
251
+ # and <tt>:service</tt> is not. Required when connecting to a non-standard
252
+ # port and <tt>:service</tt> is not defined.
253
+ # <tt>:username</tt>::
254
+ # Specifies the database user. May be omitted or set to +nil+ (together
255
+ # with <tt>:password</tt>) to use the underlying operating system user
256
+ # credentials on supported platforms.
257
+ # <tt>:password</tt>::
258
+ # Specifies the database password. Must be provided if <tt>:username</tt>
259
+ # is explicitly specified; should be omitted if OS user credentials are
260
+ # are being used.
261
+ # <tt>:charset</tt>::
262
+ # Specifies the character set to be used by the connection. Refer to
263
+ # Firebird documentation for valid options.
264
+ class RubyfbAdapter < AbstractAdapter
265
+ TEMP_COLUMN_NAME = 'AR$TEMP_COLUMN'
266
+
267
+ @@boolean_domain = { :name => "d_boolean", :type => "smallint", :true => 1, :false => 0 }
268
+ cattr_accessor :boolean_domain
269
+
270
+ def initialize(connection, logger, connection_params = nil)
271
+ super(connection, logger)
272
+ @connection_params = connection_params
273
+ end
274
+
275
+ def adapter_name # :nodoc:
276
+ 'Rubyfb'
277
+ end
278
+
279
+ def supports_migrations? # :nodoc:
280
+ true
281
+ end
282
+
283
+ def native_database_types # :nodoc:
284
+ {
285
+ :primary_key => "BIGINT NOT NULL PRIMARY KEY",
286
+ :string => { :name => "varchar", :limit => 255 },
287
+ :text => { :name => "blob sub_type text" },
288
+ :integer => { :name => "bigint" },
289
+ :decimal => { :name => "decimal" },
290
+ :numeric => { :name => "numeric" },
291
+ :float => { :name => "float" },
292
+ :datetime => { :name => "timestamp" },
293
+ :timestamp => { :name => "timestamp" },
294
+ :time => { :name => "time" },
295
+ :date => { :name => "date" },
296
+ :binary => { :name => "blob sub_type 0" },
297
+ :boolean => boolean_domain
298
+ }
299
+ end
300
+
301
+ # Returns true for Firebird adapter (since Firebird requires primary key
302
+ # values to be pre-fetched before insert). See also #next_sequence_value.
303
+ def prefetch_primary_key?(table_name = nil)
304
+ true
305
+ end
306
+
307
+ def default_sequence_name(table_name, primary_key = nil) # :nodoc:
308
+ "#{table_name}_seq"
309
+ end
310
+
311
+
312
+ # QUOTING ==================================================
313
+
314
+ # We use quoting in order to implement BLOB handling. In order to
315
+ # do this we quote a BLOB to an empty string which will force Firebird
316
+ # to create an empty BLOB in the db for us. Quoting is used in some
317
+ # other places besides insert/update like for column defaults. That is
318
+ # why we are checking caller to see where we're coming from. This isn't
319
+ # perfect but It works.
320
+ def quote(value, column = nil) # :nodoc:
321
+ if [Time, DateTime].include?(value.class)
322
+ "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
323
+ elsif value && column && [:text, :binary].include?(column.type) && caller.to_s !~ /add_column_options!/i
324
+ "''"
325
+ else
326
+ super
327
+ end
328
+ end
329
+
330
+ def quote_string(string) # :nodoc:
331
+ string.gsub(/'/, "''")
332
+ end
333
+
334
+ def quote_column_name(column_name) # :nodoc:
335
+ %Q("#{ar_to_fb_case(column_name.to_s)}")
336
+ end
337
+
338
+ def quoted_true # :nodoc:
339
+ quote(boolean_domain[:true])
340
+ end
341
+
342
+ def quoted_false # :nodoc:
343
+ quote(boolean_domain[:false])
344
+ end
345
+
346
+
347
+ # CONNECTION MANAGEMENT ====================================
348
+
349
+ def active? # :nodoc:
350
+ return false if @connection.closed?
351
+ begin
352
+ execute('select first 1 cast(1 as smallint) from rdb$database')
353
+ true
354
+ rescue
355
+ false
356
+ end
357
+ end
358
+
359
+ def disconnect! # :nodoc:
360
+ @connection.close rescue nil
361
+ end
362
+
363
+ def reconnect! # :nodoc:
364
+ disconnect!
365
+ @connection = @connection.database.connect(*@connection_params)
366
+ end
367
+
368
+
369
+ # DATABASE STATEMENTS ======================================
370
+
371
+ def select_rows(sql, name = nil)
372
+ select_raw(sql, name).last
373
+ end
374
+
375
+ def execute(sql, name = nil, &block) # :nodoc:
376
+ exec_result = execute_statement(sql, name, &block)
377
+ if exec_result.instance_of?(FireRuby::ResultSet)
378
+ exec_result.close
379
+ exec_result = nil
380
+ end
381
+ return exec_result
382
+ end
383
+
384
+ def begin_db_transaction() # :nodoc:
385
+ @transaction = @connection.start_transaction
386
+ end
387
+
388
+ def commit_db_transaction() # :nodoc:
389
+ @transaction.commit
390
+ ensure
391
+ @transaction = nil
392
+ end
393
+
394
+ def rollback_db_transaction() # :nodoc:
395
+ @transaction.rollback
396
+ ensure
397
+ @transaction = nil
398
+ end
399
+
400
+ def add_limit_offset!(sql, options) # :nodoc:
401
+ if options[:limit]
402
+ limit_string = "FIRST #{options[:limit]}"
403
+ limit_string << " SKIP #{options[:offset]}" if options[:offset]
404
+ sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
405
+ end
406
+ end
407
+
408
+ # Returns the next sequence value from a sequence generator. Not generally
409
+ # called directly; used by ActiveRecord to get the next primary key value
410
+ # when inserting a new database record (see #prefetch_primary_key?).
411
+ def next_sequence_value(sequence_name)
412
+ FireRuby::Generator.new(sequence_name, @connection).next(1)
413
+ end
414
+
415
+ # Inserts the given fixture into the table. Overridden to properly handle blobs.
416
+ def insert_fixture(fixture, table_name)
417
+ super
418
+
419
+ klass = fixture.class_name.constantize rescue nil
420
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
421
+ write_blobs(table_name, klass, fixture)
422
+ end
423
+ end
424
+
425
+ # Writes BLOB values from attributes, as indicated by the BLOB columns of klass.
426
+ def write_blobs(table_name, klass, attributes)
427
+ id = quote(attributes[klass.primary_key])
428
+ klass.columns.select { |col| col.sql_type =~ /BLOB$/i }.each do |col|
429
+ value = attributes[col.name]
430
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
431
+ value = value.read if value.respond_to?(:read)
432
+ next if value.nil? || (value == '')
433
+ s = FireRuby::Statement.new(@connection, @transaction, "UPDATE #{table_name} set #{col.name} = ? WHERE #{klass.primary_key} = #{id}", 3)
434
+ s.execute_for([value.to_s])
435
+ s.close
436
+ end
437
+ end
438
+
439
+
440
+ # SCHEMA STATEMENTS ========================================
441
+
442
+ def current_database # :nodoc:
443
+ file = @connection.database.file.split(':').last
444
+ File.basename(file, '.*')
445
+ end
446
+
447
+ def recreate_database! # :nodoc:
448
+ sql = "SELECT rdb$character_set_name FROM rdb$database"
449
+ charset = select_rows(sql).first[0].rstrip
450
+ disconnect!
451
+ @connection.database.drop(*@connection_params)
452
+ FireRuby::Database.create(@connection.database.file,
453
+ @connection_params[0], @connection_params[1], 4096, charset)
454
+ end
455
+
456
+ def tables(name = nil) # :nodoc:
457
+ sql = "SELECT rdb$relation_name FROM rdb$relations WHERE rdb$system_flag = 0"
458
+ select_rows(sql, name).collect { |row| row[0].rstrip.downcase }
459
+ end
460
+
461
+ def indexes(table_name, name = nil) # :nodoc:
462
+ index_metadata(table_name, false, name).inject([]) do |indexes, row|
463
+ if indexes.empty? or indexes.last.name != row[0]
464
+ indexes << IndexDefinition.new(table_name, row[0].rstrip.downcase, row[1] == 1, [])
465
+ end
466
+ indexes.last.columns << row[2].rstrip.downcase
467
+ indexes
468
+ end
469
+ end
470
+
471
+ def columns(table_name, name = nil) # :nodoc:
472
+ sql = <<-end_sql
473
+ SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
474
+ f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
475
+ COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
476
+ COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
477
+ FROM rdb$relation_fields r
478
+ JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
479
+ WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
480
+ ORDER BY r.rdb$field_position
481
+ end_sql
482
+
483
+ select_rows(sql, name).collect do |row|
484
+ field_values = row.collect do |value|
485
+ case value
486
+ when String then value.rstrip
487
+ else value
488
+ end
489
+ end
490
+ FirebirdColumn.new(self, *field_values)
491
+ end
492
+ end
493
+
494
+ def create_table(name, options = {}) # :nodoc:
495
+ begin
496
+ super
497
+ rescue StatementInvalid
498
+ raise unless non_existent_domain_error?
499
+ create_boolean_domain
500
+ super
501
+ end
502
+ unless options[:id] == false or options[:sequence] == false
503
+ sequence_name = options[:sequence] || default_sequence_name(name)
504
+ create_sequence(sequence_name)
505
+ end
506
+ end
507
+
508
+ def drop_table(name, options = {}) # :nodoc:
509
+ super(name)
510
+ unless options[:sequence] == false
511
+ sequence_name = options[:sequence] || default_sequence_name(name)
512
+ drop_sequence(sequence_name) if sequence_exists?(sequence_name)
513
+ end
514
+ end
515
+
516
+ def add_column(table_name, column_name, type, options = {}) # :nodoc:
517
+ super
518
+ rescue StatementInvalid
519
+ raise unless non_existent_domain_error?
520
+ create_boolean_domain
521
+ super
522
+ end
523
+
524
+ def change_column(table_name, column_name, type, options = {}) # :nodoc:
525
+ change_column_type(table_name, column_name, type, options)
526
+ change_column_position(table_name, column_name, options[:position]) if options.include?(:position)
527
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
528
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
529
+ end
530
+
531
+ def change_column_default(table_name, column_name, default) # :nodoc:
532
+ table_name = table_name.to_s.upcase
533
+ sql = <<-end_sql
534
+ UPDATE rdb$relation_fields f1
535
+ SET f1.rdb$default_source =
536
+ (SELECT f2.rdb$default_source FROM rdb$relation_fields f2
537
+ WHERE f2.rdb$relation_name = '#{table_name}'
538
+ AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}'),
539
+ f1.rdb$default_value =
540
+ (SELECT f2.rdb$default_value FROM rdb$relation_fields f2
541
+ WHERE f2.rdb$relation_name = '#{table_name}'
542
+ AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}')
543
+ WHERE f1.rdb$relation_name = '#{table_name}'
544
+ AND f1.rdb$field_name = '#{ar_to_fb_case(column_name.to_s)}'
545
+ end_sql
546
+ transaction do
547
+ add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default)
548
+ execute_statement(sql)
549
+ remove_column(table_name, TEMP_COLUMN_NAME)
550
+ end
551
+ end
552
+
553
+ def change_column_null(table_name, column_name, null, default = nil)
554
+ table_name = table_name.to_s.upcase
555
+ column_name = column_name.to_s.upcase
556
+
557
+ unless null || default.nil?
558
+ execute_statement("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
559
+ end
560
+ execute_statement("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = #{null ? 'null' : '1'} WHERE (RDB$FIELD_NAME = '#{column_name}') and (RDB$RELATION_NAME = '#{table_name}')")
561
+ end
562
+
563
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
564
+ execute_statement("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}")
565
+ end
566
+
567
+ def remove_index(table_name, options) #:nodoc:
568
+ execute_statement("DROP INDEX #{quote_column_name(index_name(table_name, options))}")
569
+ end
570
+
571
+ def rename_table(name, new_name) # :nodoc:
572
+ if table_has_constraints_or_dependencies?(name)
573
+ raise ActiveRecordError,
574
+ "Table #{name} includes constraints or dependencies that are not supported by " <<
575
+ "the Firebird rename_table migration. Try explicitly removing the constraints/" <<
576
+ "dependencies first, or manually renaming the table."
577
+ end
578
+
579
+ transaction do
580
+ copy_table(name, new_name)
581
+ copy_table_indexes(name, new_name)
582
+ end
583
+ begin
584
+ copy_table_data(name, new_name)
585
+ copy_sequence_value(name, new_name)
586
+ rescue
587
+ drop_table(new_name)
588
+ raise
589
+ end
590
+ drop_table(name)
591
+ end
592
+
593
+ def dump_schema_information # :nodoc:
594
+ super << ";\n"
595
+ end
596
+
597
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc:
598
+ case type
599
+ when :integer then integer_sql_type(limit)
600
+ when :float then float_sql_type(limit)
601
+ when :string then super(type, limit, precision, scale)
602
+ else super(type, limit, precision, scale)
603
+ end
604
+ end
605
+
606
+ private
607
+ def execute_statement(sql, name = nil, &block) # :nodoc:
608
+ @fbe = nil
609
+ log(sql, name) do
610
+ begin
611
+ if @transaction
612
+ @connection.execute(sql, @transaction, &block)
613
+ else
614
+ @connection.execute_immediate(sql, &block)
615
+ end
616
+ rescue Exception => e
617
+ @fbe = e
618
+ raise e
619
+ end
620
+ end
621
+ rescue Exception => se
622
+ def se.nested=value
623
+ @nested=value
624
+ end
625
+ def se.nested
626
+ @nested
627
+ end
628
+ se.nested = @fbe
629
+ raise se
630
+ end
631
+
632
+ def integer_sql_type(limit)
633
+ case limit
634
+ when (1..2) then 'smallint'
635
+ when (3..4) then 'integer'
636
+ else 'bigint'
637
+ end
638
+ end
639
+
640
+ def float_sql_type(limit)
641
+ limit.to_i <= 4 ? 'float' : 'double precision'
642
+ end
643
+
644
+ def select(sql, name = nil)
645
+ fields, rows = select_raw(sql, name)
646
+ result = []
647
+ for row in rows
648
+ row_hash = {}
649
+ fields.each_with_index do |f, i|
650
+ row_hash[f] = row[i]
651
+ end
652
+ result << row_hash
653
+ end
654
+ result
655
+ end
656
+
657
+ def select_raw(sql, name = nil)
658
+ fields = []
659
+ rows = []
660
+ execute_statement(sql, name) do |row|
661
+ array_row = []
662
+ row.each do |column, value|
663
+ fields << fb_to_ar_case(column) if row.number == 1
664
+
665
+ if FireRuby::Blob === value
666
+ temp = value.to_s
667
+ value.close
668
+ value = temp
669
+ end
670
+ array_row << value
671
+ end
672
+ rows << array_row
673
+ end
674
+ return fields, rows
675
+ end
676
+
677
+ def primary_key(table_name)
678
+ if pk_row = index_metadata(table_name, true).to_a.first
679
+ pk_row[2].rstrip.downcase
680
+ end
681
+ end
682
+
683
+ def index_metadata(table_name, pk, name = nil)
684
+ sql = <<-end_sql
685
+ SELECT i.rdb$index_name, i.rdb$unique_flag, s.rdb$field_name
686
+ FROM rdb$indices i
687
+ JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
688
+ LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
689
+ WHERE i.rdb$relation_name = '#{table_name.to_s.upcase}'
690
+ end_sql
691
+ if pk
692
+ sql << "AND c.rdb$constraint_type = 'PRIMARY KEY'\n"
693
+ else
694
+ sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n"
695
+ end
696
+ sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n"
697
+ execute_statement(sql, name)
698
+ end
699
+
700
+ def change_column_type(table_name, column_name, type, options = {})
701
+ sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit])}"
702
+ execute_statement(sql)
703
+ rescue StatementInvalid
704
+ raise unless non_existent_domain_error?
705
+ create_boolean_domain
706
+ execute_statement(sql)
707
+ end
708
+
709
+ def change_column_position(table_name, column_name, position)
710
+ execute_statement("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} POSITION #{position}")
711
+ end
712
+
713
+ def copy_table(from, to)
714
+ table_opts = {}
715
+ if pk = primary_key(from)
716
+ table_opts[:primary_key] = pk
717
+ else
718
+ table_opts[:id] = false
719
+ end
720
+ create_table(to, table_opts) do |table|
721
+ from_columns = columns(from).reject { |col| col.name == table_opts[:primary_key] }
722
+ from_columns.each do |column|
723
+ col_opts = [:limit, :default, :null].inject({}) { |opts, opt| opts.merge(opt => column.send(opt)) }
724
+ table.column column.name, column.type, col_opts
725
+ end
726
+ end
727
+ end
728
+
729
+ def copy_table_indexes(from, to)
730
+ indexes(from).each do |index|
731
+ unless index.name[from.to_s]
732
+ raise ActiveRecordError,
733
+ "Cannot rename index #{index.name}, because the index name does not include " <<
734
+ "the original table name (#{from}). Try explicitly removing the index on the " <<
735
+ "original table and re-adding it on the new (renamed) table."
736
+ end
737
+ options = {}
738
+ options[:name] = index.name.gsub(from.to_s, to.to_s)
739
+ options[:unique] = index.unique
740
+ add_index(to, index.columns, options)
741
+ end
742
+ end
743
+
744
+ def copy_table_data(from, to)
745
+ execute_statement("INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}")
746
+ end
747
+
748
+ def copy_sequence_value(from, to)
749
+ sequence_value = FireRuby::Generator.new(default_sequence_name(from), @connection).last
750
+ execute_statement("SET GENERATOR #{default_sequence_name(to)} TO #{sequence_value}")
751
+ end
752
+
753
+ def sequence_exists?(sequence_name)
754
+ FireRuby::Generator.exists?(sequence_name, @connection)
755
+ end
756
+
757
+ def create_sequence(sequence_name)
758
+ FireRuby::Generator.create(sequence_name.to_s, @connection)
759
+ end
760
+
761
+ def drop_sequence(sequence_name)
762
+ FireRuby::Generator.new(sequence_name.to_s, @connection).drop
763
+ end
764
+
765
+ def create_boolean_domain
766
+ sql = <<-end_sql
767
+ CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
768
+ CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
769
+ end_sql
770
+ execute_statement(sql) rescue nil
771
+ end
772
+
773
+ def table_has_constraints_or_dependencies?(table_name)
774
+ table_name = table_name.to_s.upcase
775
+ sql = <<-end_sql
776
+ SELECT 1 FROM rdb$relation_constraints
777
+ WHERE rdb$relation_name = '#{table_name}'
778
+ AND rdb$constraint_type IN ('UNIQUE', 'FOREIGN KEY', 'CHECK')
779
+ UNION
780
+ SELECT 1 FROM rdb$dependencies
781
+ WHERE rdb$depended_on_name = '#{table_name}'
782
+ AND rdb$depended_on_type = 0
783
+ end_sql
784
+ !select(sql).empty?
785
+ end
786
+
787
+ def non_existent_domain_error?
788
+ $!.message.include? FireRuby::NON_EXISTENT_DOMAIN_ERROR
789
+ end
790
+
791
+ # Maps uppercase Firebird column names to lowercase for ActiveRecord;
792
+ # mixed-case columns retain their original case.
793
+ def fb_to_ar_case(column_name)
794
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
795
+ end
796
+
797
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
798
+ # mixed-case columns retain their original case.
799
+ def ar_to_fb_case(column_name)
800
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
801
+ end
802
+ end
803
+ end
804
+ end
805
+