rubyfb 0.5.9 → 0.6

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