rubyfb 0.5.9 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,8 @@
1
1
  # Author: Ken Kunz <kennethkunz@gmail.com>
2
+ require 'digest/sha1'
2
3
  require 'active_record/connection_adapters/abstract_adapter'
3
4
  require 'active_support/core_ext/kernel/requires'
5
+ require 'rubyfb_options'
4
6
 
5
7
  if defined?(Arel) then
6
8
  if Rubyfb::Options.fb15_compat
@@ -28,15 +30,15 @@ module Rubyfb # :nodoc: all
28
30
  end
29
31
  end
30
32
 
31
- class ProcedureCall
32
- class SQLParser < ActiveRecord::Base
33
- def self.bind_params(sql_array)
34
- sanitize_sql_array(sql_array)
35
- end
33
+ class SQLBinder < ActiveRecord::Base
34
+ def self.bind(sql, binds)
35
+ sanitize_sql_array([sql] + binds)
36
36
  end
37
-
37
+ end
38
+
39
+ class ProcedureCall
38
40
  def sql_value_list(values)
39
- SQLParser.bind_params([param_names.collect{|p| '?'}.join(',')] + param_names.collect{|p| values[p]})
41
+ Rubyfb::SQLBinder.bind(param_names.collect{|p| '?'}.join(','), param_names.collect{|p| values[p]})
40
42
  end
41
43
  end
42
44
  end
@@ -44,7 +46,7 @@ end
44
46
  module ActiveRecord
45
47
  class Base
46
48
  def self.rubyfb_connection(config) # :nodoc:
47
- require_library_or_gem 'rubyfb'
49
+ require 'rubyfb'
48
50
  config.symbolize_keys!
49
51
  db = Rubyfb::Database.new_from_config(config)
50
52
  connection_params = config.values_at(:username, :password)
@@ -52,17 +54,42 @@ module ActiveRecord
52
54
  connection_params << {Rubyfb::Connection::SQL_ROLE_NAME=>config[:sql_role_name]}
53
55
  end
54
56
  connection = db.connect(*connection_params)
55
- ConnectionAdapters::RubyfbAdapter.new(connection, logger, connection_params)
57
+
58
+ if ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 1
59
+ ConnectionAdapters::RubyfbAR31Adapter.new(connection, logger, connection_params)
60
+ else
61
+ ConnectionAdapters::RubyfbAdapter.new(connection, logger, connection_params)
62
+ end
56
63
  end
57
64
 
58
- after_save :write_blobs
59
- def write_blobs #:nodoc:
60
- if connection.is_a?(ConnectionAdapters::RubyfbAdapter)
61
- connection.write_blobs(self.class.table_name, self.class, attributes)
65
+ after_save :rubyfb_write_blobs
66
+
67
+ def rubyfb_write_blobs #:nodoc:
68
+ if connection.is_a?(ConnectionAdapters::RubyfbAdapter)
69
+ connection.write_blobs(self.class.table_name, self.class, attributes, true)
62
70
  end
63
71
  end
72
+ private :rubyfb_write_blobs
73
+ end
74
+
75
+ #FIXME ugly - but ... https://github.com/rails/rails/issues/1623
76
+ module FinderMethods
77
+ def exists?(id = nil)
78
+ id = id.id if ActiveRecord::Base === id
79
+
80
+ join_dependency = construct_join_dependency_for_association_find
81
+ relation = construct_relation_for_association_find(join_dependency)
82
+ relation = relation.except(:select).select("1 as o").limit(1)
83
+
84
+ case id
85
+ when Array, Hash
86
+ relation = relation.where(id)
87
+ else
88
+ relation = relation.where(table[primary_key].eq(id)) if id
89
+ end
64
90
 
65
- private :write_blobs
91
+ connection.select_value(relation.to_sql) ? true : false
92
+ end
66
93
  end
67
94
 
68
95
  module ConnectionAdapters
@@ -71,18 +98,19 @@ module ActiveRecord
71
98
 
72
99
  def initialize(connection, name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
73
100
  @firebird_type = Rubyfb::SQLType.to_base_type(type, sub_type).to_s
101
+ @domain, @sub_type = domain, sub_type
74
102
 
75
103
  super(name.downcase, nil, @firebird_type, !null_flag)
76
-
104
+
105
+ @precision, @scale = precision, (scale.nil? ? 0 : scale.abs)
77
106
  @limit = decide_limit(length)
78
- @domain, @sub_type, @precision, @scale = domain, sub_type, precision, (scale.nil? ? 0 : scale.abs)
79
107
  @type = simplified_type(@firebird_type)
80
108
  @default = parse_default(default_source) if default_source
81
109
  @default = type_cast(decide_default(connection)) if @default
82
110
  end
83
111
 
84
112
  def self.value_to_boolean(value)
85
- %W(#{RubyfbAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
113
+ (TRUE_VALUES + [RubyfbAdapter.boolean_domain[:true]]).include?(value) || super
86
114
  end
87
115
 
88
116
  private
@@ -131,7 +159,7 @@ module ActiveRecord
131
159
  when /decimal|numeric|number/i
132
160
  @scale == 0 ? :integer : :decimal
133
161
  when /blob/i
134
- @subtype == 1 ? :text : :binary
162
+ @sub_type == 1 ? :text : :binary
135
163
  else
136
164
  if @domain =~ RubyfbAdapter.boolean_domain[:domain_pattern] || name =~ RubyfbAdapter.boolean_domain[:name_pattern]
137
165
  :boolean
@@ -253,7 +281,7 @@ module ActiveRecord
253
281
  # or using a view instead.)
254
282
  #
255
283
  # == Connection Options
256
- # The following options are supported by the Firebird adapter. None of the
284
+ # The following options are supported by the Rubyfb adapter. None of the
257
285
  # options have default values.
258
286
  #
259
287
  # <tt>:database</tt>::
@@ -297,16 +325,56 @@ module ActiveRecord
297
325
  def initialize(connection, logger, connection_params = nil)
298
326
  super(connection, logger)
299
327
  @connection_params = connection_params
328
+ @transaction = nil
329
+ @blobs_disabled = 0
330
+ @statements = {}
331
+ end
332
+
333
+ ADAPTER_NAME = 'Rubyfb'.freeze
334
+
335
+ def adapter_name #:nodoc:
336
+ ADAPTER_NAME
337
+ end
338
+
339
+ def supports_migrations? #:nodoc:
340
+ true
300
341
  end
301
342
 
302
- def adapter_name # :nodoc:
303
- 'Rubyfb'
343
+ def supports_statement_cache?
344
+ true
304
345
  end
305
346
 
306
- def supports_migrations? # :nodoc:
347
+ def supports_ddl_transactions?
307
348
  true
308
349
  end
309
350
 
351
+ # maximum length of identifiers
352
+ IDENTIFIER_MAX_LENGTH = 30
353
+
354
+ def table_alias_length #:nodoc:
355
+ IDENTIFIER_MAX_LENGTH
356
+ end
357
+
358
+ # the maximum length of a table name
359
+ def table_name_length
360
+ IDENTIFIER_MAX_LENGTH
361
+ end
362
+
363
+ # the maximum length of a column name
364
+ def column_name_length
365
+ IDENTIFIER_MAX_LENGTH
366
+ end
367
+
368
+ # the maximum length of an index name
369
+ def index_name_length
370
+ IDENTIFIER_MAX_LENGTH
371
+ end
372
+
373
+ def in_clause_length
374
+ 1000
375
+ end
376
+ alias ids_in_list_limit in_clause_length
377
+
310
378
  def native_database_types # :nodoc:
311
379
  {
312
380
  :primary_key => "BIGINT NOT NULL PRIMARY KEY",
@@ -332,7 +400,7 @@ module ActiveRecord
332
400
  end
333
401
 
334
402
  def default_sequence_name(table_name, primary_key = nil) # :nodoc:
335
- "#{table_name}_seq"
403
+ "#{table_name}_seq".upcase
336
404
  end
337
405
 
338
406
 
@@ -340,14 +408,11 @@ module ActiveRecord
340
408
 
341
409
  # We use quoting in order to implement BLOB handling. In order to
342
410
  # do this we quote a BLOB to an empty string which will force Firebird
343
- # to create an empty BLOB in the db for us. Quoting is used in some
344
- # other places besides insert/update like for column defaults. That is
345
- # why we are checking caller to see where we're coming from. This isn't
346
- # perfect but It works.
411
+ # to create an empty BLOB in the db for us.
347
412
  def quote(value, column = nil) # :nodoc:
348
- if [Time, DateTime].include?(value.class)
349
- "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
350
- elsif value && column && [:text, :binary].include?(column.type) && caller.to_s !~ /add_column_options!/i
413
+ if [Date, Time].include?(value.class)
414
+ "CAST('#{type_cast(value, column).to_s(:db)}' AS #{value.acts_like?(:time) ? 'TIMESTAMP' : 'DATE'})"
415
+ elsif @blobs_disabled.nonzero? && value && column && [:text, :binary].include?(column.type)
351
416
  "''"
352
417
  else
353
418
  super
@@ -370,37 +435,55 @@ module ActiveRecord
370
435
  quote(boolean_domain[:false])
371
436
  end
372
437
 
438
+ def type_cast(value, column)
439
+ case value
440
+ when true, false
441
+ value ? boolean_domain[:true] : boolean_domain[:false]
442
+ when Date, Time
443
+ if value.acts_like?(:time)
444
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
445
+ value = value.send(zone_conversion_method) if value.respond_to?(zone_conversion_method)
446
+ else
447
+ value
448
+ end
449
+ else
450
+ super
451
+ end
452
+ end
373
453
 
374
454
  # CONNECTION MANAGEMENT ====================================
375
-
376
455
  def active? # :nodoc:
377
- return false if @connection.closed?
378
- begin
379
- execute('select first 1 cast(1 as smallint) from rdb$database')
380
- true
381
- rescue
382
- false
383
- end
456
+ return false if @connection.closed?
457
+ begin
458
+ execute('select first 1 cast(1 as smallint) from rdb$database')
459
+ true
460
+ rescue
461
+ false
462
+ end
384
463
  end
385
464
 
386
465
  def disconnect! # :nodoc:
466
+ clear_cache!
387
467
  @connection.close rescue nil
388
468
  end
389
469
 
470
+ def reset!
471
+ clear_cache!
472
+ super
473
+ end
474
+
390
475
  def reconnect! # :nodoc:
391
476
  disconnect!
392
477
  @connection = @connection.database.connect(*@connection_params)
393
478
  end
394
479
 
395
-
396
480
  # DATABASE STATEMENTS ======================================
397
-
398
481
  def select_rows(sql, name = nil)
399
482
  select_raw(sql, name).last
400
483
  end
401
484
 
402
485
  def execute(sql, name = nil, &block) # :nodoc:
403
- exec_result = execute_statement(sql, name, &block)
486
+ exec_result = exec_query(sql, name, [], &block)
404
487
  if exec_result.instance_of?(Rubyfb::ResultSet)
405
488
  exec_result.close
406
489
  exec_result = nil
@@ -408,6 +491,47 @@ module ActiveRecord
408
491
  return exec_result
409
492
  end
410
493
 
494
+ def clear_cache!
495
+ @statements.each_value do |s|
496
+ s.close
497
+ end
498
+ @statements.clear
499
+ end
500
+
501
+ def exec_query(sql, name = 'SQL', binds = [], &block)
502
+ log(sql, name, binds) do
503
+ unless binds.empty?
504
+ cache = @statements[sql]
505
+ binds = binds.map do |col, val|
506
+ type_cast(val, col)
507
+ end
508
+ end
509
+ s = cache || @connection.create_statement(sql)
510
+ s.prepare(@transaction) unless s.prepared?
511
+ if Rubyfb::Statement::DDL_STATEMENT == s.type
512
+ clear_cache!
513
+ elsif cache.nil? && !binds.empty?
514
+ @statements[sql] = cache = s
515
+ end
516
+ if cache
517
+ s.exec(binds, @transaction, &block)
518
+ else
519
+ s.exec_and_close(binds, @transaction, &block)
520
+ end
521
+ end
522
+ end
523
+
524
+ def exec_insert(sql, name, binds)
525
+ with_blobs_disabled do
526
+ super
527
+ end
528
+ end
529
+ alias :exec_update :exec_insert
530
+
531
+ def last_inserted_id(result)
532
+ nil #TODO
533
+ end
534
+
411
535
  def begin_db_transaction() # :nodoc:
412
536
  @transaction = @connection.start_transaction
413
537
  end
@@ -436,36 +560,56 @@ module ActiveRecord
436
560
  # called directly; used by ActiveRecord to get the next primary key value
437
561
  # when inserting a new database record (see #prefetch_primary_key?).
438
562
  def next_sequence_value(sequence_name)
439
- Rubyfb::Generator.new(sequence_name, @connection).next(1)
563
+ Rubyfb::Generator.new(quote_generator_name(sequence_name), @connection).next(1, @transaction)
440
564
  end
441
565
 
442
566
  # Inserts the given fixture into the table. Overridden to properly handle blobs.
443
- def insert_fixture(fixture, table_name)
444
- super
445
-
446
- klass = fixture.class_name.constantize rescue nil
567
+ def insert_fixture(fixture, table_name) #:nodoc:
568
+ if ActiveRecord::Base.pluralize_table_names
569
+ klass = table_name.singularize.camelize
570
+ else
571
+ klass = table_name.camelize
572
+ end
573
+ klass = klass.constantize rescue nil
447
574
  if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
448
- write_blobs(table_name, klass, fixture)
575
+ with_blobs_disabled do
576
+ super
577
+ end
578
+ write_blobs(table_name, klass, fixture, false)
579
+ else
580
+ super
449
581
  end
450
582
  end
451
-
583
+
452
584
  # Writes BLOB values from attributes, as indicated by the BLOB columns of klass.
453
- def write_blobs(table_name, klass, attributes)
454
- id = quote(attributes[klass.primary_key])
585
+ def write_blobs(table_name, klass, attributes, enable_coders) #:nodoc:
586
+ # is class with composite primary key>
587
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
588
+ if is_with_cpk
589
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
590
+ else
591
+ id = quote(attributes[klass.primary_key])
592
+ end
455
593
  klass.columns.select { |col| col.sql_type =~ /BLOB$/i }.each do |col|
456
594
  value = attributes[col.name]
457
- value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
458
- value = value.read if value.respond_to?(:read)
459
595
  next if value.nil? || (value == '')
460
- s = Rubyfb::Statement.new(@connection, @transaction, "UPDATE #{table_name} set #{col.name} = ? WHERE #{klass.primary_key} = #{id}", 3)
461
- s.execute_for([value.to_s])
462
- s.close
596
+
597
+ klass.serialized_attributes[col.name].tap do |coder|
598
+ if enable_coders && coder
599
+ value = dump_blob_value(col, coder, value)
600
+ elsif value.respond_to?(:read)
601
+ value = value.read
602
+ end
603
+ end
604
+ uncached do
605
+ sql = is_with_cpk ? "UPDATE #{quote_table_name(table_name)} set #{quote_column_name(col.name)} = ? WHERE #{klass.composite_where_clause(id)}" :
606
+ "UPDATE #{quote_table_name(table_name)} set #{quote_column_name(col.name)} = ? WHERE #{quote_column_name(klass.primary_key)} = #{id}"
607
+ @connection.execute_for(sql, [value.to_s], @transaction)
608
+ end
463
609
  end
464
610
  end
465
611
 
466
-
467
612
  # SCHEMA STATEMENTS ========================================
468
-
469
613
  def current_database # :nodoc:
470
614
  file = @connection.database.file.split(':').last
471
615
  File.basename(file, '.*')
@@ -492,7 +636,7 @@ module ActiveRecord
492
636
  def primary_key(table_name)
493
637
  if pk_row = index_metadata(table_name, true).to_a.first
494
638
  pk_row[2].rstrip.downcase
495
- end
639
+ end
496
640
  end
497
641
 
498
642
  def indexes(table_name, name = nil) # :nodoc:
@@ -508,9 +652,9 @@ module ActiveRecord
508
652
  def columns(table_name, name = nil) # :nodoc:
509
653
  sql = <<-end_sql
510
654
  SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
511
- f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
512
- COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
513
- COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
655
+ COALESCE(f.rdb$character_length, f.rdb$field_length) as rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
656
+ COALESCE(r.rdb$default_source, f.rdb$default_source) as rdb$default_source,
657
+ COALESCE(r.rdb$null_flag, f.rdb$null_flag) as rdb$null_flag
514
658
  FROM rdb$relation_fields r
515
659
  JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
516
660
  WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
@@ -528,28 +672,70 @@ module ActiveRecord
528
672
  end
529
673
  end
530
674
 
531
- def create_table(name, options = {}) # :nodoc:
532
- begin
533
- super
534
- rescue StatementInvalid
535
- raise unless non_existent_domain_error?
536
- create_boolean_domain
537
- super
675
+ alias_method :super_create_table, :create_table
676
+ def create_table_and_sequence(name, options = {}, &block) # :nodoc:
677
+ create_sequence = options[:id] != false
678
+ table_td = nil
679
+ super_create_table(name, options) do |td|
680
+ unless create_sequence
681
+ class << td
682
+ attr_accessor :create_sequence
683
+ def primary_key(*args)
684
+ self.create_sequence = true
685
+ super(*args)
686
+ end
687
+ end
688
+ table_td = td
689
+ end
690
+ yield td if block_given?
538
691
  end
539
- unless options[:id] == false or options[:sequence] == false
692
+ if create_sequence || table_td.create_sequence
540
693
  sequence_name = options[:sequence] || default_sequence_name(name)
541
- create_sequence(sequence_name)
694
+ create_sequence(name, sequence_name)
542
695
  end
543
696
  end
697
+
698
+ def create_table(name, options = {}, &block) # :nodoc:
699
+ create_table_and_sequence(name, options, &block)
700
+ rescue StatementInvalid
701
+ raise unless non_existent_domain_error?
702
+ create_boolean_domain
703
+ create_table_and_sequence(name, options, &block)
704
+ end
544
705
 
545
706
  def drop_table(name, options = {}) # :nodoc:
546
707
  super(name)
547
708
  unless options[:sequence] == false
548
709
  sequence_name = options[:sequence] || default_sequence_name(name)
549
- drop_sequence(sequence_name) if sequence_exists?(sequence_name)
710
+ if sequence_exists?(sequence_name)
711
+ drop_sequence(sequence_name)
712
+ end
550
713
  end
551
714
  end
552
715
 
716
+ # returned shortened index name if default is too large (from oracle-enhanced)
717
+ def index_name(table_name, options) #:nodoc:
718
+ default_name = super(table_name, options).to_s
719
+ # sometimes options can be String or Array with column names
720
+ options = {} unless options.is_a?(Hash)
721
+ identifier_max_length = options[:identifier_max_length] || index_name_length
722
+ return default_name if default_name.length <= identifier_max_length
723
+
724
+ # remove 'index', 'on' and 'and' keywords
725
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
726
+
727
+ # leave just first three letters from each word
728
+ if shortened_name.length > identifier_max_length
729
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
730
+ end
731
+ # generate unique name using hash function
732
+ if shortened_name.length > identifier_max_length
733
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
734
+ end
735
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
736
+ shortened_name
737
+ end
738
+
553
739
  def add_column(table_name, column_name, type, options = {}) # :nodoc:
554
740
  super
555
741
  rescue StatementInvalid
@@ -582,7 +768,7 @@ module ActiveRecord
582
768
  end_sql
583
769
  transaction do
584
770
  add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default)
585
- execute_statement(sql)
771
+ exec_query(sql)
586
772
  remove_column(table_name, TEMP_COLUMN_NAME)
587
773
  end
588
774
  end
@@ -592,17 +778,17 @@ module ActiveRecord
592
778
  column_name = column_name.to_s.upcase
593
779
 
594
780
  unless null || default.nil?
595
- execute_statement("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
781
+ exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
596
782
  end
597
- execute_statement("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = #{null ? 'null' : '1'} WHERE (RDB$FIELD_NAME = '#{column_name}') and (RDB$RELATION_NAME = '#{table_name}')")
783
+ exec_query("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = #{null ? 'null' : '1'} WHERE (RDB$FIELD_NAME = '#{column_name}') and (RDB$RELATION_NAME = '#{table_name}')")
598
784
  end
599
785
 
600
786
  def rename_column(table_name, column_name, new_column_name) # :nodoc:
601
- execute_statement("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}")
787
+ exec_query("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}")
602
788
  end
603
789
 
604
790
  def remove_index(table_name, options) #:nodoc:
605
- execute_statement("DROP INDEX #{quote_column_name(index_name(table_name, options))}")
791
+ exec_query("DROP INDEX #{quote_column_name(index_name(table_name, options))}")
606
792
  end
607
793
 
608
794
  def rename_table(name, new_name) # :nodoc:
@@ -644,30 +830,37 @@ module ActiveRecord
644
830
  @connection.prepare_call(procedure_name).execute(values, @transaction)
645
831
  end
646
832
 
647
- private
648
- def execute_statement(sql, name = nil, &block) # :nodoc:
649
- @fbe = nil
650
- log(sql, name) do
651
- begin
652
- if @transaction
653
- @connection.execute(sql, @transaction, &block)
654
- else
655
- @connection.execute_immediate(sql, &block)
656
- end
657
- rescue Exception => e
658
- @fbe = e
659
- raise e
660
- end
833
+ protected
834
+
835
+ def log(sql, name, binds = nil) #:nodoc:
836
+ super sql, name
837
+ end
838
+
839
+ def dump_blob_value(column, coder, value)
840
+ column.text? ? value.to_yaml : value
841
+ end
842
+
843
+ def translate_exception(exception, message)
844
+ if exception.kind_of?(Rubyfb::FireRubyException)
845
+ case exception.sql_code
846
+ when -803
847
+ RecordNotUnique.new(message, exception)
848
+ when -530
849
+ InvalidForeignKey.new(message, exception)
850
+ else
851
+ super
661
852
  end
662
- rescue Exception => se
663
- def se.nested=value
664
- @nested=value
665
- end
666
- def se.nested
667
- @nested
668
- end
669
- se.nested = @fbe
670
- raise se
853
+ else
854
+ super
855
+ end
856
+ end
857
+
858
+ private
859
+ def with_blobs_disabled
860
+ @blobs_disabled += 1
861
+ yield if block_given?
862
+ ensure
863
+ @blobs_disabled -= 1
671
864
  end
672
865
 
673
866
  def integer_sql_type(limit)
@@ -682,8 +875,8 @@ module ActiveRecord
682
875
  limit.to_i <= 4 ? 'float' : 'double precision'
683
876
  end
684
877
 
685
- def select(sql, name = nil)
686
- fields, rows = select_raw(sql, name)
878
+ def select(sql, name = nil, binds = [])
879
+ fields, rows = select_raw(sql, name, binds)
687
880
  result = []
688
881
  for row in rows
689
882
  row_hash = {}
@@ -695,18 +888,36 @@ module ActiveRecord
695
888
  result
696
889
  end
697
890
 
698
- def select_raw(sql, name = nil)
891
+ def create_time_with_default_timezone(value)
892
+ year, month, day, hour, min, sec, usec = case value
893
+ when Time
894
+ [value.year, value.month, value.day, value.hour, value.min, value.sec, value.usec]
895
+ else
896
+ [value.year, value.month, value.day, value.hour, value.min, value.sec, 0]
897
+ end
898
+ # code from Time.time_with_datetime_fallback
899
+ begin
900
+ Time.send(Base.default_timezone, year, month, day, hour, min, sec, usec)
901
+ rescue
902
+ offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
903
+ ::DateTime.civil(year, month, day, hour, min, sec, offset)
904
+ end
905
+ end
906
+
907
+ def select_raw(sql, name = nil, binds = [])
699
908
  fields = []
700
909
  rows = []
701
- execute_statement(sql, name) do |row|
910
+ exec_query(sql, name, binds) do |row|
702
911
  array_row = []
703
912
  row.each do |column, value|
704
913
  fields << fb_to_ar_case(column) if row.number == 1
705
-
706
- if Rubyfb::Blob === value
707
- temp = value.to_s
708
- value.close
709
- value = temp
914
+ case value
915
+ when Rubyfb::Blob
916
+ temp = value.to_s
917
+ value.close
918
+ value = temp
919
+ when Time, DateTime
920
+ value = create_time_with_default_timezone(value)
710
921
  end
711
922
  array_row << value
712
923
  end
@@ -729,20 +940,20 @@ module ActiveRecord
729
940
  sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n"
730
941
  end
731
942
  sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n"
732
- execute_statement(sql, name)
943
+ exec_query(sql, name)
733
944
  end
734
945
 
735
946
  def change_column_type(table_name, column_name, type, options = {})
736
947
  sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit])}"
737
- execute_statement(sql)
948
+ exec_query(sql)
738
949
  rescue StatementInvalid
739
950
  raise unless non_existent_domain_error?
740
951
  create_boolean_domain
741
- execute_statement(sql)
952
+ exec_query(sql)
742
953
  end
743
954
 
744
955
  def change_column_position(table_name, column_name, position)
745
- execute_statement("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} POSITION #{position}")
956
+ exec_query("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} POSITION #{position}")
746
957
  end
747
958
 
748
959
  def copy_table(from, to)
@@ -777,24 +988,52 @@ module ActiveRecord
777
988
  end
778
989
 
779
990
  def copy_table_data(from, to)
780
- execute_statement("INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}")
991
+ exec_query("INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}")
781
992
  end
782
993
 
783
994
  def copy_sequence_value(from, to)
784
- sequence_value = Rubyfb::Generator.new(default_sequence_name(from), @connection).last
785
- execute_statement("SET GENERATOR #{default_sequence_name(to)} TO #{sequence_value}")
995
+ sequence_value = Rubyfb::Generator.new(quote_generator_name(default_sequence_name(from)), @connection).last(@transaction)
996
+ exec_query("SET GENERATOR #{quote_generator_name(default_sequence_name(to))} TO #{sequence_value}")
997
+ end
998
+
999
+ def quote_generator_name(generator_name)
1000
+ quote_table_name(generator_name.to_s)
786
1001
  end
787
1002
 
788
1003
  def sequence_exists?(sequence_name)
789
- Rubyfb::Generator.exists?(sequence_name, @connection)
1004
+ #don't quote - here the generatod name is used as data not as metadata
1005
+ Rubyfb::Generator.exists?(sequence_name, @connection, @transaction)
790
1006
  end
791
1007
 
792
- def create_sequence(sequence_name)
793
- Rubyfb::Generator.create(sequence_name.to_s, @connection)
1008
+ def create_sequence(table_name, sequence_name)
1009
+ g = Rubyfb::Generator.create(quote_generator_name(sequence_name), @connection, @transaction)
1010
+ g.next(1000, @transaction) #leave a gap for tests
1011
+
1012
+ pk_sql = <<-end_sql
1013
+ SELECT s.rdb$field_name as field_name
1014
+ from RDB$RELATION_CONSTRAINTS c
1015
+ join rdb$index_segments s on s.rdb$index_name=c.RDB$INDEX_NAME
1016
+ WHERE c.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
1017
+ and c.rdb$relation_name='#{table_name.to_s.upcase}'
1018
+ order by s.rdb$field_position
1019
+ end_sql
1020
+ pk_fields = select_values(pk_sql)
1021
+ if 1 == pk_fields.size
1022
+ trigger_sql = <<-end_sql
1023
+ CREATE TRIGGER #{quote_table_name(table_name.to_s + '_arsq')} FOR #{quote_table_name(table_name)}
1024
+ ACTIVE BEFORE INSERT POSITION 0
1025
+ AS
1026
+ BEGIN
1027
+ IF (NEW.#{quote_column_name(pk_fields[0].strip)} IS NULL) THEN
1028
+ NEW.#{quote_column_name(pk_fields[0].strip)} = GEN_ID(#{quote_generator_name(sequence_name)},1);
1029
+ END
1030
+ end_sql
1031
+ execute(trigger_sql)
1032
+ end
794
1033
  end
795
1034
 
796
1035
  def drop_sequence(sequence_name)
797
- Rubyfb::Generator.new(sequence_name.to_s, @connection).drop
1036
+ Rubyfb::Generator.new(quote_generator_name(sequence_name), @connection).drop(@transaction)
798
1037
  end
799
1038
 
800
1039
  def create_boolean_domain
@@ -802,7 +1041,7 @@ module ActiveRecord
802
1041
  CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
803
1042
  CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
804
1043
  end_sql
805
- execute_statement(sql) rescue nil
1044
+ exec_query(sql) rescue nil
806
1045
  end
807
1046
 
808
1047
  def table_has_constraints_or_dependencies?(table_name)
@@ -835,5 +1074,16 @@ module ActiveRecord
835
1074
  column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
836
1075
  end
837
1076
  end
1077
+
1078
+ class RubyfbAR31Adapter < RubyfbAdapter
1079
+ protected
1080
+ def log(sql, name, binds = nil) #:nodoc:
1081
+ super sql, name, binds
1082
+ end
1083
+
1084
+ def dump_blob_value(column, coder, value)
1085
+ coder.dump(value)
1086
+ end
1087
+ end
838
1088
  end
839
1089
  end