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.
@@ -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