activerecord-fb-adapter 0.8.9 → 0.9.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.
- checksums.yaml +7 -0
- data/README.md +82 -0
- data/lib/active_record/connection_adapters/fb/database_limits.rb +42 -0
- data/lib/active_record/connection_adapters/fb/database_statements.rb +127 -0
- data/lib/active_record/connection_adapters/fb/quoting.rb +103 -0
- data/lib/active_record/connection_adapters/fb/schema_statements.rb +247 -0
- data/lib/active_record/connection_adapters/fb/table_definition.rb +14 -0
- data/lib/active_record/connection_adapters/fb_adapter.rb +43 -760
- data/lib/active_record/connection_adapters/fb_column.rb +73 -0
- data/lib/active_record/fb_base.rb +25 -0
- data/lib/arel/visitors/fb.rb +43 -0
- metadata +35 -15
@@ -0,0 +1,14 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Fb
|
4
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
5
|
+
attr_accessor :needs_sequence
|
6
|
+
|
7
|
+
def primary_key(*args)
|
8
|
+
self.needs_sequence = true
|
9
|
+
super(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -2,157 +2,23 @@
|
|
2
2
|
# Author: Brent Rowland <rowland@rowlandresearch.com>
|
3
3
|
# Based originally on FireRuby extension by Ken Kunz <kennethkunz@gmail.com>
|
4
4
|
|
5
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
6
5
|
require 'base64'
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
(visit(o.limit) if o.limit && !o.offset),
|
21
|
-
].compact.join ' '
|
22
|
-
end
|
23
|
-
|
24
|
-
def visit_Arel_Nodes_UpdateStatement o, *a
|
25
|
-
[
|
26
|
-
"UPDATE #{visit o.relation}",
|
27
|
-
("SET #{o.values.map { |value| visit(value) }.join ', '}" unless o.values.empty?),
|
28
|
-
("WHERE #{o.wheres.map { |x| visit(x) }.join ' AND '}" unless o.wheres.empty?),
|
29
|
-
(visit(o.limit) if o.limit),
|
30
|
-
].compact.join ' '
|
31
|
-
end
|
32
|
-
|
33
|
-
def visit_Arel_Nodes_Limit o, *a
|
34
|
-
"ROWS #{visit(o.expr)}"
|
35
|
-
end
|
36
|
-
|
37
|
-
def visit_Arel_Nodes_Offset o, *a
|
38
|
-
"SKIP #{visit(o.expr)}"
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
def limit_offset(o)
|
43
|
-
"ROWS #{visit(o.offset.expr) + 1} TO #{visit(o.offset.expr) + visit(o.limit.expr)}"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
Arel::Visitors::VISITORS['fb'] = Arel::Visitors::FB
|
6
|
+
require 'arel'
|
7
|
+
require 'arel/visitors/fb'
|
8
|
+
require 'arel/visitors/bind_visitor'
|
9
|
+
require 'active_record'
|
10
|
+
require 'active_record/base'
|
11
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
12
|
+
require 'active_record/connection_adapters/fb/database_limits'
|
13
|
+
require 'active_record/connection_adapters/fb/database_statements'
|
14
|
+
require 'active_record/connection_adapters/fb/quoting'
|
15
|
+
require 'active_record/connection_adapters/fb/schema_statements'
|
16
|
+
require 'active_record/connection_adapters/fb/table_definition'
|
17
|
+
require 'active_record/connection_adapters/fb_column'
|
18
|
+
require 'active_record/fb_base'
|
50
19
|
|
51
20
|
module ActiveRecord
|
52
|
-
class << Base
|
53
|
-
def fb_connection(config) # :nodoc:
|
54
|
-
config = config.symbolize_keys.merge(:downcase_names => true)
|
55
|
-
unless config.has_key?(:database)
|
56
|
-
raise ArgumentError, "No database specified. Missing argument: database."
|
57
|
-
end
|
58
|
-
config[:database] = File.expand_path(config[:database]) if config[:host] =~ /localhost/i
|
59
|
-
config[:database] = "#{config[:host]}/#{config[:port] || 3050}:#{config[:database]}" if config[:host]
|
60
|
-
require 'fb'
|
61
|
-
db = Fb::Database.new(config)
|
62
|
-
begin
|
63
|
-
connection = db.connect
|
64
|
-
rescue
|
65
|
-
require 'pp'
|
66
|
-
pp config unless config[:create]
|
67
|
-
connection = config[:create] ? db.create.connect : (raise ConnectionNotEstablished, "No Firebird connections established.")
|
68
|
-
end
|
69
|
-
ConnectionAdapters::FbAdapter.new(connection, logger, config)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
21
|
module ConnectionAdapters # :nodoc:
|
74
|
-
class FbArray < Array
|
75
|
-
def column_types
|
76
|
-
{}
|
77
|
-
end
|
78
|
-
|
79
|
-
def columns
|
80
|
-
self.any? ? self.first.keys : []
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
class FbColumn < Column # :nodoc:
|
85
|
-
def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
|
86
|
-
@firebird_type = Fb::SqlType.from_code(type, sub_type || 0)
|
87
|
-
super(name.downcase, nil, @firebird_type, !null_flag)
|
88
|
-
@default = parse_default(default_source) if default_source
|
89
|
-
case @firebird_type
|
90
|
-
when 'VARCHAR', 'CHAR'
|
91
|
-
@limit = length
|
92
|
-
when 'DECIMAL', 'NUMERIC'
|
93
|
-
@precision, @scale = precision, scale.abs
|
94
|
-
end
|
95
|
-
@domain, @sub_type = domain, sub_type
|
96
|
-
end
|
97
|
-
|
98
|
-
def type
|
99
|
-
if @domain =~ /BOOLEAN/
|
100
|
-
:boolean
|
101
|
-
elsif @type == :binary and @sub_type == 1
|
102
|
-
:text
|
103
|
-
else
|
104
|
-
@type
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
|
109
|
-
# This enables Firebird to provide an actual value when context variables are used as column
|
110
|
-
# defaults (such as CURRENT_TIMESTAMP).
|
111
|
-
def default
|
112
|
-
if @default
|
113
|
-
sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
|
114
|
-
connection = ActiveRecord::Base.connection
|
115
|
-
if connection
|
116
|
-
value = connection.select_one(sql)['cast']
|
117
|
-
if value.acts_like?(:date) or value.acts_like?(:time)
|
118
|
-
nil
|
119
|
-
else
|
120
|
-
type_cast(value)
|
121
|
-
end
|
122
|
-
else
|
123
|
-
raise ConnectionNotEstablished, "No Firebird connections established."
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def self.value_to_boolean(value)
|
129
|
-
%W(#{FbAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
|
130
|
-
end
|
131
|
-
|
132
|
-
private
|
133
|
-
def parse_default(default_source)
|
134
|
-
default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
|
135
|
-
return $1 unless $1.upcase == "NULL"
|
136
|
-
end
|
137
|
-
|
138
|
-
def column_def
|
139
|
-
case @firebird_type
|
140
|
-
when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
|
141
|
-
when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
|
142
|
-
#when 'DOUBLE' then "DOUBLE PRECISION"
|
143
|
-
else @firebird_type
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def simplified_type(field_type)
|
148
|
-
if field_type == 'TIMESTAMP'
|
149
|
-
:datetime
|
150
|
-
else
|
151
|
-
super
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
22
|
# The Fb adapter relies on the Fb extension.
|
157
23
|
#
|
158
24
|
# == Usage Notes
|
@@ -253,17 +119,26 @@ module ActiveRecord
|
|
253
119
|
# Specifies the character set to be used by the connection. Refer to the
|
254
120
|
# Firebird documentation for valid options.
|
255
121
|
class FbAdapter < AbstractAdapter
|
122
|
+
include Fb::DatabaseLimits
|
123
|
+
include Fb::DatabaseStatements
|
124
|
+
include Fb::Quoting
|
125
|
+
include Fb::SchemaStatements
|
126
|
+
|
256
127
|
@@boolean_domain = { :true => 1, :false => 0, :name => 'BOOLEAN', :type => 'integer' }
|
257
128
|
cattr_accessor :boolean_domain
|
258
129
|
|
130
|
+
class BindSubstitution < Arel::Visitors::Fb # :nodoc:
|
131
|
+
include Arel::Visitors::BindVisitor
|
132
|
+
end
|
133
|
+
|
259
134
|
def initialize(connection, logger, config=nil)
|
260
135
|
super(connection, logger)
|
261
136
|
@config = config
|
262
|
-
@visitor = Arel::Visitors::
|
137
|
+
@visitor = Arel::Visitors::Fb.new(self)
|
263
138
|
end
|
264
139
|
|
265
140
|
def self.visitor_for(pool) # :nodoc:
|
266
|
-
Arel::Visitors::
|
141
|
+
Arel::Visitors::Fb.new(pool)
|
267
142
|
end
|
268
143
|
|
269
144
|
# Returns the human-readable name of the adapter. Use mixed case - one
|
@@ -316,13 +191,6 @@ module ActiveRecord
|
|
316
191
|
1499
|
317
192
|
end
|
318
193
|
|
319
|
-
# REFERENTIAL INTEGRITY ====================================
|
320
|
-
|
321
|
-
# Override to turn off referential integrity while executing <tt>&block</tt>.
|
322
|
-
# def disable_referential_integrity
|
323
|
-
# yield
|
324
|
-
# end
|
325
|
-
|
326
194
|
# CONNECTION MANAGEMENT ====================================
|
327
195
|
|
328
196
|
# Checks whether the connection to the database is still active. This includes
|
@@ -331,7 +199,7 @@ module ActiveRecord
|
|
331
199
|
def active?
|
332
200
|
return false unless @connection.open?
|
333
201
|
# return true if @connection.transaction_started
|
334
|
-
|
202
|
+
@connection.query("SELECT 1 FROM RDB$DATABASE")
|
335
203
|
true
|
336
204
|
rescue
|
337
205
|
false
|
@@ -341,12 +209,13 @@ module ActiveRecord
|
|
341
209
|
# new connection with the database.
|
342
210
|
def reconnect!
|
343
211
|
disconnect!
|
344
|
-
@connection = Fb::Database.connect(@config)
|
212
|
+
@connection = ::Fb::Database.connect(@config)
|
345
213
|
end
|
346
214
|
|
347
215
|
# Disconnects from the database if already connected. Otherwise, this
|
348
216
|
# method does nothing.
|
349
217
|
def disconnect!
|
218
|
+
super
|
350
219
|
@connection.close rescue nil
|
351
220
|
end
|
352
221
|
|
@@ -363,60 +232,23 @@ module ActiveRecord
|
|
363
232
|
# Returns true if its required to reload the connection between requests for development mode.
|
364
233
|
# This is not the case for FirebirdSQL and it's not necessary for any adapters except SQLite.
|
365
234
|
def requires_reloading?
|
366
|
-
|
235
|
+
false
|
367
236
|
end
|
368
237
|
|
369
|
-
|
370
|
-
|
371
|
-
# is no longer active, then this method will reconnect to the database.
|
372
|
-
# def verify!(*ignored)
|
373
|
-
# reconnect! unless active?
|
374
|
-
# end
|
375
|
-
|
376
|
-
# Provides access to the underlying database driver for this adapter. For
|
377
|
-
# example, this method returns a Mysql object in case of MysqlAdapter,
|
378
|
-
# and a PGconn object in case of PostgreSQLAdapter.
|
379
|
-
#
|
380
|
-
# This is useful for when you need to call a proprietary method such as
|
381
|
-
# PostgreSQL's lo_* methods.
|
382
|
-
# def raw_connection
|
383
|
-
# @connection
|
384
|
-
# end
|
385
|
-
|
386
|
-
# def open_transactions
|
387
|
-
# @open_transactions ||= 0
|
388
|
-
# end
|
389
|
-
|
390
|
-
# def increment_open_transactions
|
391
|
-
# @open_transactions ||= 0
|
392
|
-
# @open_transactions += 1
|
393
|
-
# end
|
394
|
-
|
395
|
-
# def decrement_open_transactions
|
396
|
-
# @open_transactions -= 1
|
397
|
-
# end
|
398
|
-
|
399
|
-
# def transaction_joinable=(joinable)
|
400
|
-
# @transaction_joinable = joinable
|
401
|
-
# end
|
402
|
-
|
403
|
-
def create_savepoint
|
404
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
238
|
+
def create_savepoint(name = current_savepoint_name)
|
239
|
+
execute("SAVEPOINT #{name}")
|
405
240
|
end
|
406
241
|
|
407
|
-
def rollback_to_savepoint
|
408
|
-
execute("ROLLBACK TO SAVEPOINT #{
|
242
|
+
def rollback_to_savepoint(name = current_savepoint_name)
|
243
|
+
execute("ROLLBACK TO SAVEPOINT #{name}")
|
409
244
|
end
|
410
245
|
|
411
|
-
def release_savepoint
|
412
|
-
execute("RELEASE SAVEPOINT #{
|
246
|
+
def release_savepoint(name = current_savepoint_name)
|
247
|
+
execute("RELEASE SAVEPOINT #{name}")
|
413
248
|
end
|
414
249
|
|
415
|
-
|
416
|
-
# "active_record_#{open_transactions}"
|
417
|
-
# end
|
250
|
+
protected
|
418
251
|
|
419
|
-
protected
|
420
252
|
if defined?(Encoding)
|
421
253
|
def decode(s)
|
422
254
|
Base64.decode64(s).force_encoding(@connection.encoding)
|
@@ -427,11 +259,15 @@ module ActiveRecord
|
|
427
259
|
end
|
428
260
|
end
|
429
261
|
|
430
|
-
def translate(sql)
|
262
|
+
def translate(sql, binds = [])
|
431
263
|
sql.gsub!(/\sIN\s+\([^\)]*\)/mi) do |m|
|
432
|
-
m.gsub(/\(([^\)]*)\)/m)
|
264
|
+
m.gsub(/\(([^\)]*)\)/m) do |n|
|
265
|
+
n.gsub(/\@(.*?)\@/m) do |o|
|
266
|
+
"'#{quote_string(decode(o[1..-1]))}'"
|
267
|
+
end
|
268
|
+
end
|
433
269
|
end
|
434
|
-
args =
|
270
|
+
args = binds.map { |col, val| type_cast(val, col) }
|
435
271
|
sql.gsub!(/\@(.*?)\@/m) { |m| args << decode(m[1..-1]); '?' }
|
436
272
|
yield(sql, args) if block_given?
|
437
273
|
end
|
@@ -440,569 +276,16 @@ module ActiveRecord
|
|
440
276
|
([sql] + args) * ', '
|
441
277
|
end
|
442
278
|
|
443
|
-
# def log(sql, args, name, &block)
|
444
|
-
# super(expand(sql, args), name, &block)
|
445
|
-
# end
|
446
|
-
|
447
279
|
def translate_exception(e, message)
|
448
280
|
case e.message
|
449
281
|
when /violation of FOREIGN KEY constraint/
|
450
282
|
InvalidForeignKey.new(message, e)
|
451
|
-
when /violation of PRIMARY or UNIQUE KEY constraint/
|
283
|
+
when /violation of PRIMARY or UNIQUE KEY constraint/, /attempt to store duplicate value/
|
452
284
|
RecordNotUnique.new(message, e)
|
453
285
|
else
|
454
286
|
super
|
455
287
|
end
|
456
288
|
end
|
457
|
-
|
458
|
-
public
|
459
|
-
# from module Quoting
|
460
|
-
def quote(value, column = nil)
|
461
|
-
# records are quoted as their primary key
|
462
|
-
return value.quoted_id if value.respond_to?(:quoted_id)
|
463
|
-
|
464
|
-
case value
|
465
|
-
when String, ActiveSupport::Multibyte::Chars
|
466
|
-
value = value.to_s
|
467
|
-
if column && [:integer, :float].include?(column.type)
|
468
|
-
value = column.type == :integer ? value.to_i : value.to_f
|
469
|
-
value.to_s
|
470
|
-
elsif column && column.type != :binary && value.size < 256 && !value.include?('@')
|
471
|
-
"'#{quote_string(value)}'"
|
472
|
-
else
|
473
|
-
"@#{Base64.encode64(value).chop}@"
|
474
|
-
end
|
475
|
-
when NilClass then "NULL"
|
476
|
-
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
477
|
-
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
478
|
-
when Float, Fixnum, Bignum then value.to_s
|
479
|
-
# BigDecimals need to be output in a non-normalized form and quoted.
|
480
|
-
when BigDecimal then value.to_s('F')
|
481
|
-
when Symbol then "'#{quote_string(value.to_s)}'"
|
482
|
-
else
|
483
|
-
if value.acts_like?(:date)
|
484
|
-
quote_date(value)
|
485
|
-
elsif value.acts_like?(:time)
|
486
|
-
quote_timestamp(value)
|
487
|
-
else
|
488
|
-
quote_object(value)
|
489
|
-
end
|
490
|
-
end
|
491
|
-
end
|
492
|
-
|
493
|
-
def quote_date(value)
|
494
|
-
"@#{Base64.encode64(value.strftime('%Y-%m-%d')).chop}@"
|
495
|
-
end
|
496
|
-
|
497
|
-
def quote_timestamp(value)
|
498
|
-
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
499
|
-
value = value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
|
500
|
-
"@#{Base64.encode64(value.strftime('%Y-%m-%d %H:%M:%S')).chop}@"
|
501
|
-
end
|
502
|
-
|
503
|
-
def quote_string(string) # :nodoc:
|
504
|
-
string.gsub(/'/, "''")
|
505
|
-
end
|
506
|
-
|
507
|
-
def quote_object(obj)
|
508
|
-
if obj.respond_to?(:to_str)
|
509
|
-
"@#{Base64.encode64(obj.to_str).chop}@"
|
510
|
-
else
|
511
|
-
"@#{Base64.encode64(obj.to_yaml).chop}@"
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
|
-
def quote_column_name(column_name) # :nodoc:
|
516
|
-
if @connection.dialect == 1
|
517
|
-
%Q(#{ar_to_fb_case(column_name.to_s)})
|
518
|
-
else
|
519
|
-
%Q("#{ar_to_fb_case(column_name.to_s)}")
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
def quote_table_name_for_assignment(table, attr)
|
524
|
-
quote_column_name(attr)
|
525
|
-
end if ::ActiveRecord::VERSION::MAJOR >= 4
|
526
|
-
|
527
|
-
# Quotes the table name. Defaults to column name quoting.
|
528
|
-
# def quote_table_name(table_name)
|
529
|
-
# quote_column_name(table_name)
|
530
|
-
# end
|
531
|
-
|
532
|
-
def quoted_true # :nodoc:
|
533
|
-
quote(boolean_domain[:true])
|
534
|
-
end
|
535
|
-
|
536
|
-
def quoted_false # :nodoc:
|
537
|
-
quote(boolean_domain[:false])
|
538
|
-
end
|
539
|
-
|
540
|
-
def type_cast(value, column)
|
541
|
-
return super unless value == true || value == false
|
542
|
-
|
543
|
-
value ? quoted_true : quoted_false
|
544
|
-
end
|
545
|
-
|
546
|
-
private
|
547
|
-
# Maps uppercase Firebird column names to lowercase for ActiveRecord;
|
548
|
-
# mixed-case columns retain their original case.
|
549
|
-
def fb_to_ar_case(column_name)
|
550
|
-
column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
|
551
|
-
end
|
552
|
-
|
553
|
-
# Maps lowercase ActiveRecord column names to uppercase for Fierbird;
|
554
|
-
# mixed-case columns retain their original case.
|
555
|
-
def ar_to_fb_case(column_name)
|
556
|
-
column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
|
557
|
-
end
|
558
|
-
|
559
|
-
public
|
560
|
-
# from module DatabaseStatements
|
561
|
-
|
562
|
-
# Returns an array of record hashes with the column names as keys and
|
563
|
-
# column values as values.
|
564
|
-
# def select_all(sql, name = nil, format = :hash) # :nodoc:
|
565
|
-
# translate(sql) do |sql, args|
|
566
|
-
# log(sql, args, name) do
|
567
|
-
# @connection.query(format, sql, *args)
|
568
|
-
# end
|
569
|
-
# end
|
570
|
-
# end
|
571
|
-
# Returns an array of record hashes with the column names as keys and
|
572
|
-
# column values as values.
|
573
|
-
def select_all(arel, name = nil, binds = [])
|
574
|
-
add_column_types(select(to_sql(arel, binds), name, binds))
|
575
|
-
end
|
576
|
-
|
577
|
-
# Returns an array of arrays containing the field values.
|
578
|
-
# Order is the same as that returned by +columns+.
|
579
|
-
def select_rows(sql, name = nil)
|
580
|
-
log(sql, name) do
|
581
|
-
@connection.query(:array, sql)
|
582
|
-
end
|
583
|
-
end
|
584
|
-
|
585
|
-
# Executes the SQL statement in the context of this connection.
|
586
|
-
def execute(sql, name = nil, skip_logging = false)
|
587
|
-
translate(sql) do |sql, args|
|
588
|
-
if (name == :skip_logging) or skip_logging
|
589
|
-
@connection.execute(sql, *args)
|
590
|
-
else
|
591
|
-
log(sql, args, name) do
|
592
|
-
@connection.execute(sql, *args)
|
593
|
-
end
|
594
|
-
end
|
595
|
-
end
|
596
|
-
end
|
597
|
-
|
598
|
-
# Executes +sql+ statement in the context of this connection using
|
599
|
-
# +binds+ as the bind substitutes. +name+ is logged along with
|
600
|
-
# the executed +sql+ statement.
|
601
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
602
|
-
translate(sql) do |sql, args|
|
603
|
-
unless binds.empty?
|
604
|
-
args = binds.map { |col, val| type_cast(val, col) } + args
|
605
|
-
end
|
606
|
-
log(expand(sql, args), name) do
|
607
|
-
result, rows = @connection.execute(sql, *args) { |cursor| [cursor.fields, cursor.fetchall] }
|
608
|
-
if result.respond_to?(:map)
|
609
|
-
cols = result.map { |col| col.name }
|
610
|
-
ActiveRecord::Result.new(cols, rows)
|
611
|
-
else
|
612
|
-
result
|
613
|
-
end
|
614
|
-
end
|
615
|
-
end
|
616
|
-
end
|
617
|
-
|
618
|
-
def explain(arel, binds = [])
|
619
|
-
to_sql(arel, binds)
|
620
|
-
end
|
621
|
-
|
622
|
-
# Returns the last auto-generated ID from the affected table.
|
623
|
-
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
624
|
-
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
|
625
|
-
value = exec_insert(sql, name, binds)
|
626
|
-
id_value
|
627
|
-
end
|
628
|
-
|
629
|
-
# Executes the update statement and returns the number of rows affected.
|
630
|
-
# alias_method :update, :execute
|
631
|
-
# def update(sql, name = nil)
|
632
|
-
# update_sql(sql, name)
|
633
|
-
# end
|
634
|
-
|
635
|
-
# Executes the delete statement and returns the number of rows affected.
|
636
|
-
# alias_method :delete, :execute
|
637
|
-
# def delete(sql, name = nil)
|
638
|
-
# delete_sql(sql, name)
|
639
|
-
# end
|
640
|
-
|
641
|
-
# Checks whether there is currently no transaction active. This is done
|
642
|
-
# by querying the database driver, and does not use the transaction
|
643
|
-
# house-keeping information recorded by #increment_open_transactions and
|
644
|
-
# friends.
|
645
|
-
#
|
646
|
-
# Returns true if there is no transaction active, false if there is a
|
647
|
-
# transaction active, and nil if this information is unknown.
|
648
|
-
def outside_transaction?
|
649
|
-
!@connection.transaction_started
|
650
|
-
end
|
651
|
-
|
652
|
-
# Begins the transaction (and turns off auto-committing).
|
653
|
-
def begin_db_transaction
|
654
|
-
@transaction = @connection.transaction('READ COMMITTED')
|
655
|
-
end
|
656
|
-
|
657
|
-
# Commits the transaction (and turns on auto-committing).
|
658
|
-
def commit_db_transaction
|
659
|
-
@transaction = @connection.commit
|
660
|
-
end
|
661
|
-
|
662
|
-
# Rolls back the transaction (and turns on auto-committing). Must be
|
663
|
-
# done if the transaction block raises an exception or returns false.
|
664
|
-
def rollback_db_transaction
|
665
|
-
@transaction = @connection.rollback
|
666
|
-
end
|
667
|
-
|
668
|
-
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL
|
669
|
-
# fragment that has the same semantics as LIMIT and OFFSET.
|
670
|
-
#
|
671
|
-
# +options+ must be a Hash which contains a +:limit+ option
|
672
|
-
# and an +:offset+ option.
|
673
|
-
#
|
674
|
-
# This method *modifies* the +sql+ parameter.
|
675
|
-
#
|
676
|
-
# ===== Examples
|
677
|
-
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
678
|
-
# generates
|
679
|
-
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
680
|
-
def add_limit_offset!(sql, options) # :nodoc:
|
681
|
-
if limit = options[:limit]
|
682
|
-
if offset = options[:offset]
|
683
|
-
sql << " ROWS #{offset.to_i + 1} TO #{offset.to_i + limit.to_i}"
|
684
|
-
else
|
685
|
-
sql << " ROWS #{limit.to_i}"
|
686
|
-
end
|
687
|
-
end
|
688
|
-
sql
|
689
|
-
end
|
690
|
-
|
691
|
-
def default_sequence_name(table_name, column = nil)
|
692
|
-
"#{table_name.to_s[0, table_name_length - 4]}_seq"
|
693
|
-
end
|
694
|
-
|
695
|
-
# Set the sequence to the max value of the table's column.
|
696
|
-
def reset_sequence!(table, column, sequence = nil)
|
697
|
-
max_id = select_value("select max(#{column}) from #{table}")
|
698
|
-
execute("alter sequence #{default_sequence_name(table, column)} restart with #{max_id}")
|
699
|
-
end
|
700
|
-
|
701
|
-
def next_sequence_value(sequence_name)
|
702
|
-
select_one("SELECT NEXT VALUE FOR #{sequence_name} FROM RDB$DATABASE").values.first
|
703
|
-
end
|
704
|
-
|
705
|
-
# Inserts the given fixture into the table. Overridden in adapters that require
|
706
|
-
# something beyond a simple insert (eg. Oracle).
|
707
|
-
# def insert_fixture(fixture, table_name)
|
708
|
-
# execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
|
709
|
-
# end
|
710
|
-
|
711
|
-
# def empty_insert_statement_value
|
712
|
-
# "VALUES(DEFAULT)"
|
713
|
-
# end
|
714
|
-
|
715
|
-
# def case_sensitive_equality_operator
|
716
|
-
# "="
|
717
|
-
# end
|
718
|
-
|
719
|
-
protected
|
720
|
-
# add column_types method returns empty hash, requred for rails 4 compatibility
|
721
|
-
def add_column_types obj
|
722
|
-
FbArray.new(obj)
|
723
|
-
end
|
724
|
-
|
725
|
-
# Returns an array of record hashes with the column names as keys and
|
726
|
-
# column values as values.
|
727
|
-
def select(sql, name = nil, binds = [])
|
728
|
-
translate(sql) do |sql, args|
|
729
|
-
unless binds.empty?
|
730
|
-
args = binds.map { |col, val| type_cast(val, col) } + args
|
731
|
-
end
|
732
|
-
log(expand(sql, args), name) do
|
733
|
-
@connection.query(:hash, sql, *args)
|
734
|
-
end
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
|
-
public
|
739
|
-
# from module SchemaStatements
|
740
|
-
|
741
|
-
# Returns a Hash of mappings from the abstract data types to the native
|
742
|
-
# database types. See TableDefinition#column for details on the recognized
|
743
|
-
# abstract data types.
|
744
|
-
def native_database_types
|
745
|
-
{
|
746
|
-
:primary_key => "integer not null primary key",
|
747
|
-
:string => { :name => "varchar", :limit => 255 },
|
748
|
-
:text => { :name => "blob sub_type text" },
|
749
|
-
:integer => { :name => "integer" },
|
750
|
-
:float => { :name => "float" },
|
751
|
-
:decimal => { :name => "decimal" },
|
752
|
-
:datetime => { :name => "timestamp" },
|
753
|
-
:timestamp => { :name => "timestamp" },
|
754
|
-
:time => { :name => "time" },
|
755
|
-
:date => { :name => "date" },
|
756
|
-
:binary => { :name => "blob" },
|
757
|
-
:boolean => { :name => boolean_domain[:name] }
|
758
|
-
}
|
759
|
-
end
|
760
|
-
|
761
|
-
# Truncates a table alias according to the limits of the current adapter.
|
762
|
-
# def table_alias_for(table_name)
|
763
|
-
# table_name[0..table_alias_length-1].gsub(/\./, '_')
|
764
|
-
# end
|
765
|
-
|
766
|
-
# def tables(name = nil) end
|
767
|
-
def tables(name = nil)
|
768
|
-
@connection.table_names
|
769
|
-
end
|
770
|
-
|
771
|
-
# Returns an array of indexes for the given table.
|
772
|
-
def indexes(table_name, name = nil)
|
773
|
-
result = @connection.indexes.values.select {|ix| ix.table_name == table_name && ix.index_name !~ /^rdb\$/ }
|
774
|
-
indexes = result.map {|ix| IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns) }
|
775
|
-
indexes
|
776
|
-
end
|
777
|
-
|
778
|
-
def primary_key(table_name) #:nodoc:
|
779
|
-
sql = <<-END_SQL
|
780
|
-
SELECT s.rdb$field_name
|
781
|
-
FROM rdb$indices i
|
782
|
-
JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
|
783
|
-
LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
|
784
|
-
WHERE i.rdb$relation_name = '#{ar_to_fb_case(table_name)}' and c.rdb$constraint_type = 'PRIMARY KEY';
|
785
|
-
END_SQL
|
786
|
-
row = select_one(sql)
|
787
|
-
row && fb_to_ar_case(row.values.first.rstrip)
|
788
|
-
end
|
789
|
-
|
790
|
-
# Returns an array of Column objects for the table specified by +table_name+.
|
791
|
-
# See the concrete implementation for details on the expected parameter values.
|
792
|
-
def columns(table_name, name = nil)
|
793
|
-
sql = <<-END_SQL
|
794
|
-
SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
|
795
|
-
f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
|
796
|
-
COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
|
797
|
-
COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
|
798
|
-
FROM rdb$relation_fields r
|
799
|
-
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
800
|
-
WHERE r.rdb$relation_name = '#{ar_to_fb_case(table_name)}'
|
801
|
-
ORDER BY r.rdb$field_position
|
802
|
-
END_SQL
|
803
|
-
select_rows(sql, name).collect do |field|
|
804
|
-
field_values = field.collect do |value|
|
805
|
-
case value
|
806
|
-
when String then value.rstrip
|
807
|
-
else value
|
808
|
-
end
|
809
|
-
end
|
810
|
-
FbColumn.new(*field_values)
|
811
|
-
end
|
812
|
-
end
|
813
|
-
|
814
|
-
def create_table(name, options = {}) # :nodoc:
|
815
|
-
begin
|
816
|
-
super
|
817
|
-
rescue
|
818
|
-
raise unless non_existent_domain_error?
|
819
|
-
create_boolean_domain
|
820
|
-
super
|
821
|
-
end
|
822
|
-
unless options[:id] == false or options[:sequence] == false
|
823
|
-
sequence_name = options[:sequence] || default_sequence_name(name)
|
824
|
-
create_sequence(sequence_name)
|
825
|
-
end
|
826
|
-
end
|
827
|
-
|
828
|
-
def drop_table(name, options = {}) # :nodoc:
|
829
|
-
super(name)
|
830
|
-
unless options[:sequence] == false
|
831
|
-
sequence_name = options[:sequence] || default_sequence_name(name)
|
832
|
-
drop_sequence(sequence_name) if sequence_exists?(sequence_name)
|
833
|
-
end
|
834
|
-
end
|
835
|
-
|
836
|
-
# Adds a new column to the named table.
|
837
|
-
# See TableDefinition#column for details of the options you can use.
|
838
|
-
def add_column(table_name, column_name, type, options = {})
|
839
|
-
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])}"
|
840
|
-
add_column_options!(add_column_sql, options)
|
841
|
-
begin
|
842
|
-
execute(add_column_sql)
|
843
|
-
rescue
|
844
|
-
raise unless non_existent_domain_error?
|
845
|
-
create_boolean_domain
|
846
|
-
execute(add_column_sql)
|
847
|
-
end
|
848
|
-
if options[:position]
|
849
|
-
# position is 1-based but add 1 to skip id column
|
850
|
-
alter_position_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} POSITION #{options[:position] + 1}"
|
851
|
-
execute(alter_position_sql)
|
852
|
-
end
|
853
|
-
end
|
854
|
-
|
855
|
-
# Changes the column's definition according to the new options.
|
856
|
-
# See TableDefinition#column for details of the options you can use.
|
857
|
-
# ===== Examples
|
858
|
-
# change_column(:suppliers, :name, :string, :limit => 80)
|
859
|
-
# change_column(:accounts, :description, :text)
|
860
|
-
def change_column(table_name, column_name, type, options = {})
|
861
|
-
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])}"
|
862
|
-
add_column_options!(sql, options)
|
863
|
-
execute(sql)
|
864
|
-
end
|
865
|
-
|
866
|
-
# Sets a new default value for a column. If you want to set the default
|
867
|
-
# value to +NULL+, you are out of luck. You need to
|
868
|
-
# DatabaseStatements#execute the appropriate SQL statement yourself.
|
869
|
-
# ===== Examples
|
870
|
-
# change_column_default(:suppliers, :qualification, 'new')
|
871
|
-
# change_column_default(:accounts, :authorized, 1)
|
872
|
-
def change_column_default(table_name, column_name, default)
|
873
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}")
|
874
|
-
end
|
875
|
-
|
876
|
-
# Renames a column.
|
877
|
-
# ===== Example
|
878
|
-
# rename_column(:suppliers, :description, :name)
|
879
|
-
def rename_column(table_name, column_name, new_column_name)
|
880
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
881
|
-
end
|
882
|
-
|
883
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
884
|
-
execute("DROP INDEX #{quote_column_name(index_name)}")
|
885
|
-
end
|
886
|
-
|
887
|
-
def index_name(table_name, options) #:nodoc:
|
888
|
-
if Hash === options # legacy support
|
889
|
-
if options[:column]
|
890
|
-
"#{table_name}_#{Array.wrap(options[:column]) * '_'}"
|
891
|
-
elsif options[:name]
|
892
|
-
options[:name]
|
893
|
-
else
|
894
|
-
raise ArgumentError, "You must specify the index name"
|
895
|
-
end
|
896
|
-
else
|
897
|
-
index_name(table_name, :column => options)
|
898
|
-
end
|
899
|
-
end
|
900
|
-
|
901
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
902
|
-
case type
|
903
|
-
when :integer then integer_to_sql(limit)
|
904
|
-
when :float then float_to_sql(limit)
|
905
|
-
else super
|
906
|
-
end
|
907
|
-
end
|
908
|
-
|
909
|
-
private
|
910
|
-
# Map logical Rails types to Firebird-specific data types.
|
911
|
-
def integer_to_sql(limit)
|
912
|
-
return 'integer' if limit.nil?
|
913
|
-
case limit
|
914
|
-
when 1..2 then 'smallint'
|
915
|
-
when 3..4 then 'integer'
|
916
|
-
when 5..8 then 'bigint'
|
917
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a NUMERIC with PRECISION 0 instead.")
|
918
|
-
end
|
919
|
-
end
|
920
|
-
|
921
|
-
def float_to_sql(limit)
|
922
|
-
if limit.nil? || limit <= 4
|
923
|
-
'float'
|
924
|
-
else
|
925
|
-
'double precision'
|
926
|
-
end
|
927
|
-
end
|
928
|
-
|
929
|
-
def non_existent_domain_error?
|
930
|
-
$!.message =~ /Specified domain or source column \w+ does not exist/
|
931
|
-
end
|
932
|
-
|
933
|
-
def create_boolean_domain
|
934
|
-
sql = <<-end_sql
|
935
|
-
CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
|
936
|
-
CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
|
937
|
-
end_sql
|
938
|
-
execute(sql)
|
939
|
-
end
|
940
|
-
|
941
|
-
def create_sequence(sequence_name)
|
942
|
-
execute("CREATE SEQUENCE #{sequence_name}")
|
943
|
-
end
|
944
|
-
|
945
|
-
def drop_sequence(sequence_name)
|
946
|
-
execute("DROP SEQUENCE #{sequence_name}")
|
947
|
-
end
|
948
|
-
|
949
|
-
def sequence_exists?(sequence_name)
|
950
|
-
@connection.generator_names.include?(sequence_name)
|
951
|
-
end
|
952
|
-
|
953
|
-
public
|
954
|
-
# from module DatabaseLimits
|
955
|
-
|
956
|
-
# the maximum length of a table alias
|
957
|
-
def table_alias_length
|
958
|
-
31
|
959
|
-
end
|
960
|
-
|
961
|
-
# the maximum length of a column name
|
962
|
-
def column_name_length
|
963
|
-
31
|
964
|
-
end
|
965
|
-
|
966
|
-
# the maximum length of a table name
|
967
|
-
def table_name_length
|
968
|
-
31
|
969
|
-
end
|
970
|
-
|
971
|
-
# the maximum length of an index name
|
972
|
-
def index_name_length
|
973
|
-
31
|
974
|
-
end
|
975
|
-
|
976
|
-
# the maximum number of columns per table
|
977
|
-
# def columns_per_table
|
978
|
-
# 1024
|
979
|
-
# end
|
980
|
-
|
981
|
-
# the maximum number of indexes per table
|
982
|
-
def indexes_per_table
|
983
|
-
65_535
|
984
|
-
end
|
985
|
-
|
986
|
-
# the maximum number of columns in a multicolumn index
|
987
|
-
# def columns_per_multicolumn_index
|
988
|
-
# 16
|
989
|
-
# end
|
990
|
-
|
991
|
-
# the maximum number of elements in an IN (x,y,z) clause
|
992
|
-
def in_clause_length
|
993
|
-
1499
|
994
|
-
end
|
995
|
-
|
996
|
-
# the maximum length of an SQL query
|
997
|
-
def sql_query_length
|
998
|
-
32767
|
999
|
-
end
|
1000
|
-
|
1001
|
-
# maximum number of joins in a single query
|
1002
|
-
# def joins_per_query
|
1003
|
-
# 256
|
1004
|
-
# end
|
1005
|
-
|
1006
289
|
end
|
1007
290
|
end
|
1008
291
|
end
|