activerecord-fb-adapter 0.7.0
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.
- data/lib/active_record/connection_adapters/fb_adapter.rb +951 -0
- metadata +63 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
# Rails 3-specific database adapter for Firebird (http://firebirdsql.org)
|
|
2
|
+
# Author: Brent Rowland <rowland@rowlandresearch.com>
|
|
3
|
+
# Based originally on FireRuby extension by Ken Kunz <kennethkunz@gmail.com>
|
|
4
|
+
|
|
5
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
|
6
|
+
# require 'active_support/core_ext/kernel/requires'
|
|
7
|
+
require 'base64'
|
|
8
|
+
|
|
9
|
+
module Arel
|
|
10
|
+
module Visitors
|
|
11
|
+
class FB < Arel::Visitors::ToSql
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
def visit_Arel_Nodes_SelectStatement(o)
|
|
15
|
+
select_core = o.cores.map { |x| visit_Arel_Nodes_SelectCore(x) }.join
|
|
16
|
+
select_core.sub!(/^\s*SELECT/i, "SELECT #{visit(o.offset)}") if o.offset && !o.limit
|
|
17
|
+
[
|
|
18
|
+
select_core,
|
|
19
|
+
("ORDER BY #{o.orders.map { |x| visit(x) }.join(', ')}" unless o.orders.empty?),
|
|
20
|
+
(limit_offset(o) if o.limit && o.offset),
|
|
21
|
+
(visit(o.limit) if o.limit && !o.offset),
|
|
22
|
+
].compact.join ' '
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def visit_Arel_Nodes_UpdateStatement o
|
|
26
|
+
[
|
|
27
|
+
"UPDATE #{visit o.relation}",
|
|
28
|
+
("SET #{o.values.map { |value| visit(value) }.join ', '}" unless o.values.empty?),
|
|
29
|
+
("WHERE #{o.wheres.map { |x| visit(x) }.join ' AND '}" unless o.wheres.empty?),
|
|
30
|
+
(visit(o.limit) if o.limit),
|
|
31
|
+
].compact.join ' '
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def visit_Arel_Nodes_Limit(o)
|
|
35
|
+
"ROWS #{visit(o.expr)}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def visit_Arel_Nodes_Offset(o)
|
|
39
|
+
"SKIP #{visit(o.expr)}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def limit_offset(o)
|
|
44
|
+
"ROWS #{visit(o.offset.expr) + 1} TO #{visit(o.offset.expr) + visit(o.limit.expr)}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Arel::Visitors::VISITORS['fb'] = Arel::Visitors::FB
|
|
51
|
+
|
|
52
|
+
module ActiveRecord
|
|
53
|
+
class << Base
|
|
54
|
+
def fb_connection(config) # :nodoc:
|
|
55
|
+
config = config.symbolize_keys.merge(:downcase_names => true)
|
|
56
|
+
unless config.has_key?(:database)
|
|
57
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
|
58
|
+
end
|
|
59
|
+
config[:database] = File.expand_path(config[:database]) if config[:host] =~ /localhost/i
|
|
60
|
+
config[:database] = "#{config[:host]}/#{config[:port] || 3050}:#{config[:database]}" if config[:host]
|
|
61
|
+
require 'fb'
|
|
62
|
+
db = Fb::Database.new(config)
|
|
63
|
+
begin
|
|
64
|
+
connection = db.connect
|
|
65
|
+
rescue
|
|
66
|
+
require 'pp'
|
|
67
|
+
pp config unless config[:create]
|
|
68
|
+
connection = config[:create] ? db.create.connect : (raise ConnectionNotEstablished, "No Firebird connections established.")
|
|
69
|
+
end
|
|
70
|
+
ConnectionAdapters::FbAdapter.new(connection, logger, config)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module ConnectionAdapters # :nodoc:
|
|
75
|
+
class FbColumn < Column # :nodoc:
|
|
76
|
+
def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
|
|
77
|
+
@firebird_type = Fb::SqlType.from_code(type, sub_type || 0)
|
|
78
|
+
super(name.downcase, nil, @firebird_type, !null_flag)
|
|
79
|
+
@default = parse_default(default_source) if default_source
|
|
80
|
+
@limit = (@firebird_type == 'BLOB') ? 10 * 1024 * 1024 : length
|
|
81
|
+
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def type
|
|
85
|
+
if @domain =~ /BOOLEAN/
|
|
86
|
+
:boolean
|
|
87
|
+
elsif @type == :binary and @sub_type == 1
|
|
88
|
+
:text
|
|
89
|
+
else
|
|
90
|
+
@type
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
|
|
95
|
+
# This enables Firebird to provide an actual value when context variables are used as column
|
|
96
|
+
# defaults (such as CURRENT_TIMESTAMP).
|
|
97
|
+
def default
|
|
98
|
+
if @default
|
|
99
|
+
sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
|
|
100
|
+
connection = ActiveRecord::Base.connection
|
|
101
|
+
if connection
|
|
102
|
+
type_cast connection.select_one(sql)['cast']
|
|
103
|
+
else
|
|
104
|
+
raise ConnectionNotEstablished, "No Firebird connections established."
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.value_to_boolean(value)
|
|
110
|
+
%W(#{FbAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
def parse_default(default_source)
|
|
115
|
+
default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
|
|
116
|
+
return $1 unless $1.upcase == "NULL"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def column_def
|
|
120
|
+
case @firebird_type
|
|
121
|
+
when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
|
|
122
|
+
when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
|
|
123
|
+
#when 'DOUBLE' then "DOUBLE PRECISION"
|
|
124
|
+
else @firebird_type
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def simplified_type(field_type)
|
|
129
|
+
if field_type == 'TIMESTAMP'
|
|
130
|
+
:datetime
|
|
131
|
+
else
|
|
132
|
+
super
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The Fb adapter relies on the Fb extension.
|
|
138
|
+
#
|
|
139
|
+
# == Usage Notes
|
|
140
|
+
#
|
|
141
|
+
# === Sequence (Generator) Names
|
|
142
|
+
# The Fb adapter supports the same approach adopted for the Oracle
|
|
143
|
+
# adapter. See ActiveRecord::Base#set_sequence_name for more details.
|
|
144
|
+
#
|
|
145
|
+
# Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
|
|
146
|
+
# trigger corresponding to a Firebird sequence generator when using
|
|
147
|
+
# ActiveRecord. In other words, you don't have to try to make Firebird
|
|
148
|
+
# simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
|
|
149
|
+
# new record, ActiveRecord pre-fetches the next sequence value for the table
|
|
150
|
+
# and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
|
|
151
|
+
# next primary key value is the only reliable method for the Fb
|
|
152
|
+
# adapter to report back the +id+ after a successful insert.)
|
|
153
|
+
#
|
|
154
|
+
# === BOOLEAN Domain
|
|
155
|
+
# Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
|
|
156
|
+
# define a +BOOLEAN+ _domain_ for this purpose, e.g.:
|
|
157
|
+
#
|
|
158
|
+
# CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
|
|
159
|
+
#
|
|
160
|
+
# When the Fb adapter encounters a column that is based on a domain
|
|
161
|
+
# that includes "BOOLEAN" in the domain name, it will attempt to treat
|
|
162
|
+
# the column as a +BOOLEAN+.
|
|
163
|
+
#
|
|
164
|
+
# By default, the Fb adapter will assume that the BOOLEAN domain is
|
|
165
|
+
# defined as above. This can be modified if needed. For example, if you
|
|
166
|
+
# have a legacy schema with the following +BOOLEAN+ domain defined:
|
|
167
|
+
#
|
|
168
|
+
# CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
|
|
169
|
+
#
|
|
170
|
+
# ...you can add the following line to your <tt>environment.rb</tt> file:
|
|
171
|
+
#
|
|
172
|
+
# ActiveRecord::ConnectionAdapters::Fb.boolean_domain = { :true => 'T', :false => 'F' }
|
|
173
|
+
#
|
|
174
|
+
# === Column Name Case Semantics
|
|
175
|
+
# Firebird and ActiveRecord have somewhat conflicting case semantics for
|
|
176
|
+
# column names.
|
|
177
|
+
#
|
|
178
|
+
# [*Firebird*]
|
|
179
|
+
# The standard practice is to use unquoted column names, which can be
|
|
180
|
+
# thought of as case-insensitive. (In fact, Firebird converts them to
|
|
181
|
+
# uppercase.) Quoted column names (not typically used) are case-sensitive.
|
|
182
|
+
# [*ActiveRecord*]
|
|
183
|
+
# Attribute accessors corresponding to column names are case-sensitive.
|
|
184
|
+
# The defaults for primary key and inheritance columns are lowercase, and
|
|
185
|
+
# in general, people use lowercase attribute names.
|
|
186
|
+
#
|
|
187
|
+
# In order to map between the differing semantics in a way that conforms
|
|
188
|
+
# to common usage for both Firebird and ActiveRecord, uppercase column names
|
|
189
|
+
# in Firebird are converted to lowercase attribute names in ActiveRecord,
|
|
190
|
+
# and vice-versa. Mixed-case column names retain their case in both
|
|
191
|
+
# directions. Lowercase (quoted) Firebird column names are not supported.
|
|
192
|
+
# This is similar to the solutions adopted by other adapters.
|
|
193
|
+
#
|
|
194
|
+
# In general, the best approach is to use unquoted (case-insensitive) column
|
|
195
|
+
# names in your Firebird DDL (or if you must quote, use uppercase column
|
|
196
|
+
# names). These will correspond to lowercase attributes in ActiveRecord.
|
|
197
|
+
#
|
|
198
|
+
# For example, a Firebird table based on the following DDL:
|
|
199
|
+
#
|
|
200
|
+
# CREATE TABLE products (
|
|
201
|
+
# id BIGINT NOT NULL PRIMARY KEY,
|
|
202
|
+
# "TYPE" VARCHAR(50),
|
|
203
|
+
# name VARCHAR(255) );
|
|
204
|
+
#
|
|
205
|
+
# ...will correspond to an ActiveRecord model class called +Product+ with
|
|
206
|
+
# the following attributes: +id+, +type+, +name+.
|
|
207
|
+
#
|
|
208
|
+
# ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
|
|
209
|
+
# In ActiveRecord, the default inheritance column name is +type+. The word
|
|
210
|
+
# _type_ is a Firebird reserved word, so it must be quoted in any Firebird
|
|
211
|
+
# SQL statements. Because of the case mapping described above, you should
|
|
212
|
+
# always reference this column using quoted-uppercase syntax
|
|
213
|
+
# (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
|
|
214
|
+
# example above). This holds true for any other Firebird reserved words used
|
|
215
|
+
# as column names as well.
|
|
216
|
+
#
|
|
217
|
+
# === Migrations
|
|
218
|
+
# The Fb adapter does not currently support Migrations.
|
|
219
|
+
#
|
|
220
|
+
# == Connection Options
|
|
221
|
+
# The following options are supported by the Fb adapter.
|
|
222
|
+
#
|
|
223
|
+
# <tt>:database</tt>::
|
|
224
|
+
# <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
|
|
225
|
+
# (ii) the full path of a database file; _or_ (iii) a full Firebird
|
|
226
|
+
# connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
|
|
227
|
+
# or <tt>:port</tt> as separate options when using a full connection
|
|
228
|
+
# string.</i>
|
|
229
|
+
# <tt>:username</tt>::
|
|
230
|
+
# Specifies the database user. Defaults to 'sysdba'.
|
|
231
|
+
# <tt>:password</tt>::
|
|
232
|
+
# Specifies the database password. Defaults to 'masterkey'.
|
|
233
|
+
# <tt>:charset</tt>::
|
|
234
|
+
# Specifies the character set to be used by the connection. Refer to the
|
|
235
|
+
# Firebird documentation for valid options.
|
|
236
|
+
class FbAdapter < AbstractAdapter
|
|
237
|
+
@@boolean_domain = { :true => 1, :false => 0, :name => 'BOOLEAN', :type => 'integer' }
|
|
238
|
+
cattr_accessor :boolean_domain
|
|
239
|
+
|
|
240
|
+
def initialize(connection, logger, connection_params=nil)
|
|
241
|
+
super(connection, logger)
|
|
242
|
+
@connection_params = connection_params
|
|
243
|
+
@visitor = Arel::Visitors::FB.new(self)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def self.visitor_for(pool) # :nodoc:
|
|
247
|
+
Arel::Visitors::FB.new(pool)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Returns the human-readable name of the adapter. Use mixed case - one
|
|
251
|
+
# can always use downcase if needed.
|
|
252
|
+
def adapter_name
|
|
253
|
+
'Fb'
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Does this adapter support migrations? Backend specific, as the
|
|
257
|
+
# abstract adapter always returns +false+.
|
|
258
|
+
def supports_migrations?
|
|
259
|
+
true
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Can this adapter determine the primary key for tables not attached
|
|
263
|
+
# to an Active Record class, such as join tables? Backend specific, as
|
|
264
|
+
# the abstract adapter always returns +false+.
|
|
265
|
+
def supports_primary_key?
|
|
266
|
+
true
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
|
270
|
+
# for all adapters except sqlite.
|
|
271
|
+
def supports_count_distinct?
|
|
272
|
+
true
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Does this adapter support DDL rollbacks in transactions? That is, would
|
|
276
|
+
# CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
|
|
277
|
+
# SQL Server, and others support this. MySQL and others do not.
|
|
278
|
+
def supports_ddl_transactions?
|
|
279
|
+
false
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
|
|
283
|
+
# does not.
|
|
284
|
+
def supports_savepoints?
|
|
285
|
+
true
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Should primary key values be selected from their corresponding
|
|
289
|
+
# sequence before the insert statement? If true, next_sequence_value
|
|
290
|
+
# is called before each insert to set the record's primary key.
|
|
291
|
+
# This is false for all adapters but Firebird.
|
|
292
|
+
def prefetch_primary_key?(table_name = nil)
|
|
293
|
+
true
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Does this adapter restrict the number of ids you can use in a list. Oracle has a limit of 1000.
|
|
297
|
+
def ids_in_list_limit
|
|
298
|
+
1499
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
302
|
+
|
|
303
|
+
# Override to turn off referential integrity while executing <tt>&block</tt>.
|
|
304
|
+
# def disable_referential_integrity
|
|
305
|
+
# yield
|
|
306
|
+
# end
|
|
307
|
+
|
|
308
|
+
# CONNECTION MANAGEMENT ====================================
|
|
309
|
+
|
|
310
|
+
# Checks whether the connection to the database is still active. This includes
|
|
311
|
+
# checking whether the database is actually capable of responding, i.e. whether
|
|
312
|
+
# the connection isn't stale.
|
|
313
|
+
def active?
|
|
314
|
+
return false unless @connection.open?
|
|
315
|
+
# return true if @connection.transaction_started
|
|
316
|
+
select("SELECT 1 FROM RDB$DATABASE")
|
|
317
|
+
true
|
|
318
|
+
rescue
|
|
319
|
+
false
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Disconnects from the database if already connected, and establishes a
|
|
323
|
+
# new connection with the database.
|
|
324
|
+
def reconnect!
|
|
325
|
+
disconnect!
|
|
326
|
+
@connection = Fb::Database.connect(@connection_params)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Disconnects from the database if already connected. Otherwise, this
|
|
330
|
+
# method does nothing.
|
|
331
|
+
def disconnect!
|
|
332
|
+
@connection.close rescue nil
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Reset the state of this connection, directing the DBMS to clear
|
|
336
|
+
# transactions and other connection-related server-side state. Usually a
|
|
337
|
+
# database-dependent operation.
|
|
338
|
+
#
|
|
339
|
+
# The default implementation does nothing; the implementation should be
|
|
340
|
+
# overridden by concrete adapters.
|
|
341
|
+
def reset!
|
|
342
|
+
reconnect!
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Returns true if its required to reload the connection between requests for development mode.
|
|
346
|
+
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
|
347
|
+
# def requires_reloading?
|
|
348
|
+
# false
|
|
349
|
+
# end
|
|
350
|
+
|
|
351
|
+
# Checks whether the connection to the database is still active (i.e. not stale).
|
|
352
|
+
# This is done under the hood by calling <tt>active?</tt>. If the connection
|
|
353
|
+
# is no longer active, then this method will reconnect to the database.
|
|
354
|
+
# def verify!(*ignored)
|
|
355
|
+
# reconnect! unless active?
|
|
356
|
+
# end
|
|
357
|
+
|
|
358
|
+
# Provides access to the underlying database driver for this adapter. For
|
|
359
|
+
# example, this method returns a Mysql object in case of MysqlAdapter,
|
|
360
|
+
# and a PGconn object in case of PostgreSQLAdapter.
|
|
361
|
+
#
|
|
362
|
+
# This is useful for when you need to call a proprietary method such as
|
|
363
|
+
# PostgreSQL's lo_* methods.
|
|
364
|
+
# def raw_connection
|
|
365
|
+
# @connection
|
|
366
|
+
# end
|
|
367
|
+
|
|
368
|
+
# def open_transactions
|
|
369
|
+
# @open_transactions ||= 0
|
|
370
|
+
# end
|
|
371
|
+
|
|
372
|
+
# def increment_open_transactions
|
|
373
|
+
# @open_transactions ||= 0
|
|
374
|
+
# @open_transactions += 1
|
|
375
|
+
# end
|
|
376
|
+
|
|
377
|
+
# def decrement_open_transactions
|
|
378
|
+
# @open_transactions -= 1
|
|
379
|
+
# end
|
|
380
|
+
|
|
381
|
+
# def transaction_joinable=(joinable)
|
|
382
|
+
# @transaction_joinable = joinable
|
|
383
|
+
# end
|
|
384
|
+
|
|
385
|
+
def create_savepoint
|
|
386
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def rollback_to_savepoint
|
|
390
|
+
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def release_savepoint
|
|
394
|
+
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# def current_savepoint_name
|
|
398
|
+
# "active_record_#{open_transactions}"
|
|
399
|
+
# end
|
|
400
|
+
|
|
401
|
+
protected
|
|
402
|
+
def translate(sql)
|
|
403
|
+
sql.gsub!(/\bIN\s+\(NULL\)/i, 'IS NULL')
|
|
404
|
+
sql.sub!(/\bWHERE\s.*$/im) do |m|
|
|
405
|
+
m.gsub(/\s=\s*NULL\b/i, ' IS NULL')
|
|
406
|
+
end
|
|
407
|
+
sql.gsub!(/\sIN\s+\([^\)]*\)/mi) do |m|
|
|
408
|
+
m.gsub(/\(([^\)]*)\)/m) { |n| n.gsub(/\@(.*?)\@/m) { |n| "'#{quote_string(Base64.decode64(n[1..-1]))}'" } }
|
|
409
|
+
end
|
|
410
|
+
args = []
|
|
411
|
+
sql.gsub!(/\@(.*?)\@/m) { |m| args << Base64.decode64(m[1..-1]); '?' }
|
|
412
|
+
yield(sql, args) if block_given?
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def expand(sql, args)
|
|
416
|
+
([sql] + args) * ', '
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# def log(sql, args, name, &block)
|
|
420
|
+
# super(expand(sql, args), name, &block)
|
|
421
|
+
# end
|
|
422
|
+
|
|
423
|
+
def translate_exception(e, message)
|
|
424
|
+
case e.message
|
|
425
|
+
when /violation of FOREIGN KEY constraint/
|
|
426
|
+
InvalidForeignKey.new(message, e)
|
|
427
|
+
when /violation of PRIMARY or UNIQUE KEY constraint/
|
|
428
|
+
RecordNotUnique.new(message, e)
|
|
429
|
+
else
|
|
430
|
+
super
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
public
|
|
435
|
+
# from module Quoting
|
|
436
|
+
def quote(value, column = nil)
|
|
437
|
+
# records are quoted as their primary key
|
|
438
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
|
439
|
+
|
|
440
|
+
case value
|
|
441
|
+
when String, ActiveSupport::Multibyte::Chars
|
|
442
|
+
value = value.to_s
|
|
443
|
+
if column && [:integer, :float].include?(column.type)
|
|
444
|
+
value = column.type == :integer ? value.to_i : value.to_f
|
|
445
|
+
value.to_s
|
|
446
|
+
elsif column && column.type != :binary && value.size < 256
|
|
447
|
+
"'#{quote_string(value)}'"
|
|
448
|
+
else
|
|
449
|
+
"@#{Base64.encode64(value).chop}@"
|
|
450
|
+
end
|
|
451
|
+
when NilClass then "NULL"
|
|
452
|
+
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
|
453
|
+
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
|
454
|
+
when Float, Fixnum, Bignum then value.to_s
|
|
455
|
+
# BigDecimals need to be output in a non-normalized form and quoted.
|
|
456
|
+
when BigDecimal then value.to_s('F')
|
|
457
|
+
when Symbol then "'#{quote_string(value.to_s)}'"
|
|
458
|
+
else
|
|
459
|
+
if value.acts_like?(:date)
|
|
460
|
+
quote_date(value)
|
|
461
|
+
elsif value.acts_like?(:time)
|
|
462
|
+
quote_timestamp(value)
|
|
463
|
+
else
|
|
464
|
+
quote_object(value)
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def quote_date(value)
|
|
470
|
+
"@#{Base64.encode64(value.strftime('%Y-%m-%d')).chop}@"
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def quote_timestamp(value)
|
|
474
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
|
475
|
+
value = value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
|
|
476
|
+
"@#{Base64.encode64(value.strftime('%Y-%m-%d %H:%M:%S')).chop}@"
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def quote_string(string) # :nodoc:
|
|
480
|
+
string.gsub(/'/, "''")
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def quote_object(obj)
|
|
484
|
+
if obj.respond_to?(:to_str)
|
|
485
|
+
"@#{Base64.encode64(obj.to_str).chop}@"
|
|
486
|
+
else
|
|
487
|
+
"@#{Base64.encode64(obj.to_yaml).chop}@"
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def quote_column_name(column_name) # :nodoc:
|
|
492
|
+
%Q("#{ar_to_fb_case(column_name.to_s)}")
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Quotes the table name. Defaults to column name quoting.
|
|
496
|
+
# def quote_table_name(table_name)
|
|
497
|
+
# quote_column_name(table_name)
|
|
498
|
+
# end
|
|
499
|
+
|
|
500
|
+
def quoted_true # :nodoc:
|
|
501
|
+
quote(boolean_domain[:true])
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def quoted_false # :nodoc:
|
|
505
|
+
quote(boolean_domain[:false])
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
private
|
|
509
|
+
# Maps uppercase Firebird column names to lowercase for ActiveRecord;
|
|
510
|
+
# mixed-case columns retain their original case.
|
|
511
|
+
def fb_to_ar_case(column_name)
|
|
512
|
+
column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Maps lowercase ActiveRecord column names to uppercase for Fierbird;
|
|
516
|
+
# mixed-case columns retain their original case.
|
|
517
|
+
def ar_to_fb_case(column_name)
|
|
518
|
+
column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
public
|
|
522
|
+
# from module DatabaseStatements
|
|
523
|
+
|
|
524
|
+
# Returns an array of record hashes with the column names as keys and
|
|
525
|
+
# column values as values.
|
|
526
|
+
# def select_all(sql, name = nil, format = :hash) # :nodoc:
|
|
527
|
+
# translate(sql) do |sql, args|
|
|
528
|
+
# log(sql, args, name) do
|
|
529
|
+
# @connection.query(format, sql, *args)
|
|
530
|
+
# end
|
|
531
|
+
# end
|
|
532
|
+
# end
|
|
533
|
+
# Returns an array of record hashes with the column names as keys and
|
|
534
|
+
# column values as values.
|
|
535
|
+
def select_all(arel, name = nil, binds = [])
|
|
536
|
+
select(to_sql(arel, binds), name, binds)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Returns an array of arrays containing the field values.
|
|
540
|
+
# Order is the same as that returned by +columns+.
|
|
541
|
+
def select_rows(sql, name = nil)
|
|
542
|
+
log(sql, name) do
|
|
543
|
+
@connection.query(:array, sql)
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Executes the SQL statement in the context of this connection.
|
|
548
|
+
# def execute(sql, name = nil, skip_logging = false)
|
|
549
|
+
# translate(sql) do |sql, args|
|
|
550
|
+
# if (name == :skip_logging) or skip_logging
|
|
551
|
+
# @connection.execute(sql, *args)
|
|
552
|
+
# else
|
|
553
|
+
# log(sql, args, name) do
|
|
554
|
+
# @connection.execute(sql, *args)
|
|
555
|
+
# end
|
|
556
|
+
# end
|
|
557
|
+
# end
|
|
558
|
+
# end
|
|
559
|
+
|
|
560
|
+
# Executes +sql+ statement in the context of this connection using
|
|
561
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
562
|
+
# the executed +sql+ statement.
|
|
563
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
|
564
|
+
if binds.empty?
|
|
565
|
+
translate(sql) do |sql, args|
|
|
566
|
+
log(expand(sql, args), name) do
|
|
567
|
+
@connection.execute(sql, *args)
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
else
|
|
571
|
+
log(sql, name, binds) do
|
|
572
|
+
args = binds.map { |col, val| type_cast(val, col) }
|
|
573
|
+
@connection.execute(sql, *args)
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Returns the last auto-generated ID from the affected table.
|
|
579
|
+
# def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
|
580
|
+
# execute(sql, name)
|
|
581
|
+
# id_value
|
|
582
|
+
# end
|
|
583
|
+
|
|
584
|
+
# Executes the update statement and returns the number of rows affected.
|
|
585
|
+
# alias_method :update, :execute
|
|
586
|
+
# def update(sql, name = nil)
|
|
587
|
+
# update_sql(sql, name)
|
|
588
|
+
# end
|
|
589
|
+
|
|
590
|
+
# Executes the delete statement and returns the number of rows affected.
|
|
591
|
+
# alias_method :delete, :execute
|
|
592
|
+
# def delete(sql, name = nil)
|
|
593
|
+
# delete_sql(sql, name)
|
|
594
|
+
# end
|
|
595
|
+
|
|
596
|
+
# Checks whether there is currently no transaction active. This is done
|
|
597
|
+
# by querying the database driver, and does not use the transaction
|
|
598
|
+
# house-keeping information recorded by #increment_open_transactions and
|
|
599
|
+
# friends.
|
|
600
|
+
#
|
|
601
|
+
# Returns true if there is no transaction active, false if there is a
|
|
602
|
+
# transaction active, and nil if this information is unknown.
|
|
603
|
+
def outside_transaction?
|
|
604
|
+
!@connection.transaction_started
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Begins the transaction (and turns off auto-committing).
|
|
608
|
+
def begin_db_transaction
|
|
609
|
+
@transaction = @connection.transaction('READ COMMITTED')
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Commits the transaction (and turns on auto-committing).
|
|
613
|
+
def commit_db_transaction
|
|
614
|
+
@transaction = @connection.commit
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# Rolls back the transaction (and turns on auto-committing). Must be
|
|
618
|
+
# done if the transaction block raises an exception or returns false.
|
|
619
|
+
def rollback_db_transaction
|
|
620
|
+
@transaction = @connection.rollback
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL
|
|
624
|
+
# fragment that has the same semantics as LIMIT and OFFSET.
|
|
625
|
+
#
|
|
626
|
+
# +options+ must be a Hash which contains a +:limit+ option
|
|
627
|
+
# and an +:offset+ option.
|
|
628
|
+
#
|
|
629
|
+
# This method *modifies* the +sql+ parameter.
|
|
630
|
+
#
|
|
631
|
+
# ===== Examples
|
|
632
|
+
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
|
633
|
+
# generates
|
|
634
|
+
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
|
635
|
+
def add_limit_offset!(sql, options) # :nodoc:
|
|
636
|
+
if limit = options[:limit]
|
|
637
|
+
if offset = options[:offset]
|
|
638
|
+
sql << " ROWS #{offset.to_i + 1} TO #{offset.to_i + limit.to_i}"
|
|
639
|
+
else
|
|
640
|
+
sql << " ROWS #{limit.to_i}"
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
sql
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def default_sequence_name(table_name, column=nil)
|
|
647
|
+
"#{table_name}_seq"
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Set the sequence to the max value of the table's column.
|
|
651
|
+
def reset_sequence!(table, column, sequence = nil)
|
|
652
|
+
max_id = select_value("select max(#{column}) from #{table}")
|
|
653
|
+
execute("alter sequence #{default_sequence_name(table, column)} restart with #{max_id}")
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
def next_sequence_value(sequence_name)
|
|
657
|
+
select_one("SELECT GEN_ID(#{sequence_name}, 1) FROM RDB$DATABASE").values.first
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
# Inserts the given fixture into the table. Overridden in adapters that require
|
|
661
|
+
# something beyond a simple insert (eg. Oracle).
|
|
662
|
+
# def insert_fixture(fixture, table_name)
|
|
663
|
+
# execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
|
664
|
+
# end
|
|
665
|
+
|
|
666
|
+
# def empty_insert_statement_value
|
|
667
|
+
# "VALUES(DEFAULT)"
|
|
668
|
+
# end
|
|
669
|
+
|
|
670
|
+
# def case_sensitive_equality_operator
|
|
671
|
+
# "="
|
|
672
|
+
# end
|
|
673
|
+
|
|
674
|
+
protected
|
|
675
|
+
# Returns an array of record hashes with the column names as keys and
|
|
676
|
+
# column values as values.
|
|
677
|
+
def select(sql, name = nil, binds = [])
|
|
678
|
+
if binds.empty?
|
|
679
|
+
translate(sql) do |sql, args|
|
|
680
|
+
log(expand(sql, args), name) do
|
|
681
|
+
@connection.query(:hash, sql, *args)
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
else
|
|
685
|
+
log(sql, name, binds) do
|
|
686
|
+
args = binds.map { |col, val| type_cast(val, col) }
|
|
687
|
+
@connection.query(:hash, sql, *args)
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
public
|
|
693
|
+
# from module SchemaStatements
|
|
694
|
+
|
|
695
|
+
# Returns a Hash of mappings from the abstract data types to the native
|
|
696
|
+
# database types. See TableDefinition#column for details on the recognized
|
|
697
|
+
# abstract data types.
|
|
698
|
+
def native_database_types
|
|
699
|
+
{
|
|
700
|
+
:primary_key => "integer not null primary key",
|
|
701
|
+
:string => { :name => "varchar", :limit => 255 },
|
|
702
|
+
:text => { :name => "blob sub_type text" },
|
|
703
|
+
:integer => { :name => "integer" },
|
|
704
|
+
:float => { :name => "float" },
|
|
705
|
+
:decimal => { :name => "decimal" },
|
|
706
|
+
:datetime => { :name => "timestamp" },
|
|
707
|
+
:timestamp => { :name => "timestamp" },
|
|
708
|
+
:time => { :name => "time" },
|
|
709
|
+
:date => { :name => "date" },
|
|
710
|
+
:binary => { :name => "blob" },
|
|
711
|
+
:boolean => { :name => boolean_domain[:name] }
|
|
712
|
+
}
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# Truncates a table alias according to the limits of the current adapter.
|
|
716
|
+
# def table_alias_for(table_name)
|
|
717
|
+
# table_name[0..table_alias_length-1].gsub(/\./, '_')
|
|
718
|
+
# end
|
|
719
|
+
|
|
720
|
+
# def tables(name = nil) end
|
|
721
|
+
def tables(name = nil)
|
|
722
|
+
@connection.table_names
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# Returns an array of indexes for the given table.
|
|
726
|
+
def indexes(table_name, name = nil)
|
|
727
|
+
result = @connection.indexes.values.select {|ix| ix.table_name == table_name && ix.index_name !~ /^rdb\$/ }
|
|
728
|
+
indexes = result.map {|ix| IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns) }
|
|
729
|
+
indexes
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def primary_key(table_name) #:nodoc:
|
|
733
|
+
sql = <<-END_SQL
|
|
734
|
+
SELECT s.rdb$field_name
|
|
735
|
+
FROM rdb$indices i
|
|
736
|
+
JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
|
|
737
|
+
LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
|
|
738
|
+
WHERE i.rdb$relation_name = '#{ar_to_fb_case(table_name)}' and c.rdb$constraint_type = 'PRIMARY KEY';
|
|
739
|
+
END_SQL
|
|
740
|
+
row = select_one(sql)
|
|
741
|
+
row && fb_to_ar_case(row.values.first.rstrip)
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# Returns an array of Column objects for the table specified by +table_name+.
|
|
745
|
+
# See the concrete implementation for details on the expected parameter values.
|
|
746
|
+
def columns(table_name, name = nil)
|
|
747
|
+
sql = <<-END_SQL
|
|
748
|
+
SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
|
|
749
|
+
f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
|
|
750
|
+
COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
|
|
751
|
+
COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
|
|
752
|
+
FROM rdb$relation_fields r
|
|
753
|
+
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
|
754
|
+
WHERE r.rdb$relation_name = '#{ar_to_fb_case(table_name)}'
|
|
755
|
+
ORDER BY r.rdb$field_position
|
|
756
|
+
END_SQL
|
|
757
|
+
select_rows(sql, name).collect do |field|
|
|
758
|
+
field_values = field.collect do |value|
|
|
759
|
+
case value
|
|
760
|
+
when String then value.rstrip
|
|
761
|
+
else value
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
FbColumn.new(*field_values)
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
def create_table(name, options = {}) # :nodoc:
|
|
769
|
+
begin
|
|
770
|
+
super
|
|
771
|
+
rescue
|
|
772
|
+
raise unless non_existent_domain_error?
|
|
773
|
+
create_boolean_domain
|
|
774
|
+
super
|
|
775
|
+
end
|
|
776
|
+
unless options[:id] == false or options[:sequence] == false
|
|
777
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
|
778
|
+
create_sequence(sequence_name)
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
def drop_table(name, options = {}) # :nodoc:
|
|
783
|
+
super(name)
|
|
784
|
+
unless options[:sequence] == false
|
|
785
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
|
786
|
+
drop_sequence(sequence_name) if sequence_exists?(sequence_name)
|
|
787
|
+
end
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
# Adds a new column to the named table.
|
|
791
|
+
# See TableDefinition#column for details of the options you can use.
|
|
792
|
+
def add_column(table_name, column_name, type, options = {})
|
|
793
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
794
|
+
add_column_options!(add_column_sql, options)
|
|
795
|
+
execute(add_column_sql)
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Changes the column's definition according to the new options.
|
|
799
|
+
# See TableDefinition#column for details of the options you can use.
|
|
800
|
+
# ===== Examples
|
|
801
|
+
# change_column(:suppliers, :name, :string, :limit => 80)
|
|
802
|
+
# change_column(:accounts, :description, :text)
|
|
803
|
+
def change_column(table_name, column_name, type, options = {})
|
|
804
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
805
|
+
sql = add_column_options(sql, options)
|
|
806
|
+
execute(sql)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# Sets a new default value for a column. If you want to set the default
|
|
810
|
+
# value to +NULL+, you are out of luck. You need to
|
|
811
|
+
# DatabaseStatements#execute the appropriate SQL statement yourself.
|
|
812
|
+
# ===== Examples
|
|
813
|
+
# change_column_default(:suppliers, :qualification, 'new')
|
|
814
|
+
# change_column_default(:accounts, :authorized, 1)
|
|
815
|
+
def change_column_default(table_name, column_name, default)
|
|
816
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}")
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# Renames a column.
|
|
820
|
+
# ===== Example
|
|
821
|
+
# rename_column(:suppliers, :description, :name)
|
|
822
|
+
def rename_column(table_name, column_name, new_column_name)
|
|
823
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
|
827
|
+
execute("DROP INDEX #{quote_column_name(index_name)}")
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def index_name(table_name, options) #:nodoc:
|
|
831
|
+
if Hash === options # legacy support
|
|
832
|
+
if options[:column]
|
|
833
|
+
"#{table_name}_#{Array.wrap(options[:column]) * '_'}"
|
|
834
|
+
elsif options[:name]
|
|
835
|
+
options[:name]
|
|
836
|
+
else
|
|
837
|
+
raise ArgumentError, "You must specify the index name"
|
|
838
|
+
end
|
|
839
|
+
else
|
|
840
|
+
index_name(table_name, :column => options)
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
|
845
|
+
case type
|
|
846
|
+
when :integer then integer_to_sql(limit)
|
|
847
|
+
when :float then float_to_sql(limit)
|
|
848
|
+
else super
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
private
|
|
853
|
+
# Map logical Rails types to Firebird-specific data types.
|
|
854
|
+
def integer_to_sql(limit)
|
|
855
|
+
return 'integer' if limit.nil?
|
|
856
|
+
case limit
|
|
857
|
+
when 1..2 then 'smallint'
|
|
858
|
+
when 3..4 then 'integer'
|
|
859
|
+
when 5..8 then 'bigint'
|
|
860
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a NUMERIC with PRECISION 0 instead.")
|
|
861
|
+
end
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
def float_to_sql(limit)
|
|
865
|
+
if limit <= 4
|
|
866
|
+
'float'
|
|
867
|
+
else
|
|
868
|
+
'double precision'
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
def non_existent_domain_error?
|
|
873
|
+
$!.message =~ /Specified domain or source column \w+ does not exist/
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
def create_boolean_domain
|
|
877
|
+
sql = <<-end_sql
|
|
878
|
+
CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
|
|
879
|
+
CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
|
|
880
|
+
end_sql
|
|
881
|
+
execute(sql)
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def create_sequence(sequence_name)
|
|
885
|
+
execute("CREATE SEQUENCE #{sequence_name}")
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
def drop_sequence(sequence_name)
|
|
889
|
+
execute("DROP SEQUENCE #{sequence_name}")
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def sequence_exists?(sequence_name)
|
|
893
|
+
@connection.generator_names.include?(sequence_name)
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
public
|
|
897
|
+
# from module DatabaseLimits
|
|
898
|
+
|
|
899
|
+
# the maximum length of a table alias
|
|
900
|
+
def table_alias_length
|
|
901
|
+
31
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
# the maximum length of a column name
|
|
905
|
+
def column_name_length
|
|
906
|
+
31
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# the maximum length of a table name
|
|
910
|
+
def table_name_length
|
|
911
|
+
31
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
# the maximum length of an index name
|
|
915
|
+
def index_name_length
|
|
916
|
+
31
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# the maximum number of columns per table
|
|
920
|
+
# def columns_per_table
|
|
921
|
+
# 1024
|
|
922
|
+
# end
|
|
923
|
+
|
|
924
|
+
# the maximum number of indexes per table
|
|
925
|
+
def indexes_per_table
|
|
926
|
+
65_535
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# the maximum number of columns in a multicolumn index
|
|
930
|
+
# def columns_per_multicolumn_index
|
|
931
|
+
# 16
|
|
932
|
+
# end
|
|
933
|
+
|
|
934
|
+
# the maximum number of elements in an IN (x,y,z) clause
|
|
935
|
+
def in_clause_length
|
|
936
|
+
1499
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# the maximum length of an SQL query
|
|
940
|
+
def sql_query_length
|
|
941
|
+
32767
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
# maximum number of joins in a single query
|
|
945
|
+
# def joins_per_query
|
|
946
|
+
# 256
|
|
947
|
+
# end
|
|
948
|
+
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activerecord-fb-adapter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.7.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Brent Rowland
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-05-07 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: fb
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.7.0
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ! '>='
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 0.7.0
|
|
30
|
+
description:
|
|
31
|
+
email: rowland@rowlandresearch.com
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- lib/active_record/connection_adapters/fb_adapter.rb
|
|
37
|
+
homepage: http://github.com/rowland/activerecord-fb-adapter
|
|
38
|
+
licenses: []
|
|
39
|
+
post_install_message:
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
none: false
|
|
45
|
+
requirements:
|
|
46
|
+
- - ! '>='
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
|
+
none: false
|
|
51
|
+
requirements:
|
|
52
|
+
- - ! '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
requirements:
|
|
56
|
+
- Firebird library fb
|
|
57
|
+
rubyforge_project:
|
|
58
|
+
rubygems_version: 1.8.23
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 3
|
|
61
|
+
summary: ActiveRecord Firebird Adapter for Rails 3. Unlike fb_adapter for Rails 1.x
|
|
62
|
+
and 2.x, this version attempts to support migrations.
|
|
63
|
+
test_files: []
|