activerecord-fb-adapter 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|