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.
@@ -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
- module Arel
9
- module Visitors
10
- class FB < Arel::Visitors::ToSql
11
- protected
12
-
13
- def visit_Arel_Nodes_SelectStatement o, *a
14
- select_core = o.cores.map { |x| visit_Arel_Nodes_SelectCore(x, *a) }.join
15
- select_core.sub!(/^\s*SELECT/i, "SELECT #{visit(o.offset)}") if o.offset && !o.limit
16
- [
17
- select_core,
18
- ("ORDER BY #{o.orders.map { |x| visit(x) }.join(', ')}" unless o.orders.empty?),
19
- (limit_offset(o) if o.limit && o.offset),
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::FB.new(self)
137
+ @visitor = Arel::Visitors::Fb.new(self)
263
138
  end
264
139
 
265
140
  def self.visitor_for(pool) # :nodoc:
266
- Arel::Visitors::FB.new(pool)
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
- select("SELECT 1 FROM RDB$DATABASE")
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
- false
235
+ false
367
236
  end
368
237
 
369
- # Checks whether the connection to the database is still active (i.e. not stale).
370
- # This is done under the hood by calling <tt>active?</tt>. If the connection
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 #{current_savepoint_name}")
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 #{current_savepoint_name}")
246
+ def release_savepoint(name = current_savepoint_name)
247
+ execute("RELEASE SAVEPOINT #{name}")
413
248
  end
414
249
 
415
- # def current_savepoint_name
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) { |n| n.gsub(/\@(.*?)\@/m) { |n| "'#{quote_string(decode(n[1..-1]))}'" } }
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