ibm_db 5.5.1 → 5.6.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.
@@ -1,7 +1,7 @@
1
1
  # +----------------------------------------------------------------------+
2
2
  # | Licensed Materials - Property of IBM |
3
3
  # | |
4
- # | (C) Copyright IBM Corporation 2006 - 2023 |
4
+ # | (C) Copyright IBM Corporation 2006 - 2025 |
5
5
  # +----------------------------------------------------------------------+
6
6
  # | Authors: Antonio Cangiano <cangiano@ca.ibm.com> |
7
7
  # | : Mario Ds Briggs <mario.briggs@in.ibm.com> |
@@ -17,6 +17,76 @@ require 'active_record/connection_adapters/sql_type_metadata'
17
17
  require 'active_record/connection_adapters/statement_pool'
18
18
  require 'active_record/connection_adapters'
19
19
 
20
+ # Ensure ActiveRecord and Rails Generators are loaded
21
+ require "active_record"
22
+ ActiveRecord::ConnectionAdapters.register(
23
+ "ibm_db",
24
+ "ActiveRecord::ConnectionAdapters::IBM_DBAdapter",
25
+ "active_record/connection_adapters/ibm_db_adapter"
26
+ )
27
+ require "rails/generators/database"
28
+
29
+ module Rails
30
+ module Generators
31
+ class Database
32
+ DATABASES << "ibm_db" unless DATABASES.include?("ibm_db")
33
+
34
+ class << self
35
+ alias_method :original_build, :build
36
+
37
+ def build(database_name)
38
+ return IBMDB.new if database_name == "ibm_db"
39
+ original_build(database_name)
40
+ end
41
+
42
+ alias_method :original_all, :all
43
+
44
+ def all
45
+ original_all + [IBMDB.new]
46
+ end
47
+ end
48
+ end
49
+
50
+ class IBMDB < Database
51
+ def name
52
+ "ibm_db"
53
+ end
54
+
55
+ def service
56
+ {
57
+ "image" => "ibm_db:latest",
58
+ "restart" => "unless-stopped",
59
+ "networks" => ["default"],
60
+ "volumes" => ["ibm-db-data:/var/lib/ibmdb"],
61
+ "environment" => {
62
+ "IBM_DB_ALLOW_EMPTY_PASSWORD" => "true",
63
+ }
64
+ }
65
+ end
66
+
67
+ def port
68
+ nil # Default DB2 port
69
+ end
70
+
71
+ def gem
72
+ ["ibm_db", [">= 5.5"]]
73
+ end
74
+
75
+ def base_package
76
+ nil
77
+ end
78
+
79
+ def build_package
80
+ nil
81
+ end
82
+
83
+ def feature_name
84
+ nil
85
+ end
86
+ end
87
+ end
88
+ end
89
+
20
90
  module CallChain
21
91
  def self.caller_method(depth = 1)
22
92
  parse_caller(caller(depth + 1).first).last
@@ -48,7 +118,7 @@ module ActiveRecord
48
118
 
49
119
  module Persistence
50
120
  module ClassMethods
51
- def _insert_record(values, returning) # :nodoc:
121
+ def _insert_record(connection, values, returning) # :nodoc:
52
122
  primary_key = self.primary_key
53
123
  primary_key_value = nil
54
124
 
@@ -61,16 +131,18 @@ module ActiveRecord
61
131
 
62
132
  im = Arel::InsertManager.new(arel_table)
63
133
 
64
- if values.empty?
65
- im.insert(connection.empty_insert_statement_value(primary_key, arel_table[name].relation.name))
66
- else
67
- im.insert(values.transform_keys { |name| arel_table[name] })
68
- end
134
+ with_connection do |c|
135
+ if values.empty?
136
+ im.insert(connection.empty_insert_statement_value(primary_key, arel_table[name].relation.name))
137
+ else
138
+ im.insert(values.transform_keys { |name| arel_table[name] })
139
+ end
69
140
 
70
- connection.insert(
71
- im, "#{self} Create", primary_key || false, primary_key_value,
72
- returning: returning
73
- )
141
+ connection.insert(
142
+ im, "#{self} Create", primary_key || false, primary_key_value,
143
+ returning: returning
144
+ )
145
+ end
74
146
  end
75
147
  end
76
148
  end
@@ -145,6 +217,9 @@ module ActiveRecord
145
217
  end
146
218
 
147
219
  def visit_ColumnDefinition(o)
220
+ if @conn.instance_of? IBM_DBAdapter
221
+ @conn.puts_log "visit_ColumnDefinition #{o.name} #{o} #{@conn} #{@conn.servertype}"
222
+ end
148
223
  o.sql_type = type_to_sql(o.type, **o.options)
149
224
  column_sql = +"#{quote_column_name(o.name)} #{o.sql_type}"
150
225
  add_column_options!(column_sql, column_options(o))
@@ -220,41 +295,6 @@ module ActiveRecord
220
295
  end
221
296
  end
222
297
 
223
- class Relation
224
- def insert(values)
225
- primary_key_value = nil
226
-
227
- if primary_key && values.is_a?(Hash)
228
- primary_key_value = values[values.keys.find do |k|
229
- k.name == primary_key
230
- end]
231
-
232
- if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
233
- primary_key_value = connection.next_sequence_value(klass.sequence_name)
234
- values[klass.arel_table[klass.primary_key]] = primary_key_value
235
- end
236
- end
237
-
238
- im = arel.create_insert
239
- im.into @table
240
-
241
- conn = @klass.connection
242
- substitutes = values.sort_by { |arel_attr, _| arel_attr.name }
243
- binds = substitutes.map do |arel_attr, value|
244
- [@klass.columns_hash[arel_attr.name], value]
245
- end
246
-
247
- substitutes, binds = substitute_values values
248
- if values.empty? # empty insert
249
- im.values = Arel.sql(connection.empty_insert_statement_value(klass.primary_key, klass.table_name))
250
- else
251
- im.insert substitutes
252
- end
253
-
254
- conn.insert(im, 'SQL', primary_key, primary_key_value, nil, binds)
255
- end
256
- end
257
-
258
298
  class Base
259
299
  # Method required to handle LOBs and XML fields.
260
300
  # An after save callback checks if a marker has been inserted through
@@ -262,87 +302,91 @@ module ActiveRecord
262
302
  # the actual large object through a prepared statement (param binding).
263
303
  after_save :handle_lobs
264
304
  def handle_lobs
265
- return unless self.class.connection.is_a?(ConnectionAdapters::IBM_DBAdapter)
266
-
267
- # Checks that the insert or update had at least a BLOB, CLOB or XML field
268
- self.class.connection.sql.each do |clob_sql|
269
- next unless clob_sql =~ /BLOB\('(.*)'\)/i ||
270
- clob_sql =~ /@@@IBMTEXT@@@/i ||
271
- clob_sql =~ /@@@IBMXML@@@/i ||
272
- clob_sql =~ /@@@IBMBINARY@@@/i
273
-
274
- update_query = "UPDATE #{self.class.table_name} SET ("
275
- counter = 0
276
- values = []
277
- params = []
278
- # Selects only binary, text and xml columns
279
- self.class.columns.select { |col| col.sql_type.to_s =~ /blob|binary|clob|text|xml/i }.each do |col|
280
- update_query << if counter.zero?
281
- "#{col.name}".to_s
305
+ # return unless self.class.with_connection.is_a?(ConnectionAdapters::IBM_DBAdapter)
306
+
307
+ self.class.with_connection do |conn|
308
+ if conn.is_a?(ConnectionAdapters::IBM_DBAdapter)
309
+ # Checks that the insert or update had at least a BLOB, CLOB or XML field
310
+ conn.sql.each do |clob_sql|
311
+ next unless clob_sql =~ /BLOB\('(.*)'\)/i ||
312
+ clob_sql =~ /@@@IBMTEXT@@@/i ||
313
+ clob_sql =~ /@@@IBMXML@@@/i ||
314
+ clob_sql =~ /@@@IBMBINARY@@@/i
315
+
316
+ update_query = "UPDATE #{self.class.table_name} SET ("
317
+ counter = 0
318
+ values = []
319
+ params = []
320
+ # Selects only binary, text and xml columns
321
+ self.class.columns.select { |col| col.sql_type.to_s =~ /blob|binary|clob|text|xml/i }.each do |col|
322
+ update_query << if counter.zero?
323
+ "#{col.name}".to_s
324
+ else
325
+ ",#{col.name}".to_s
326
+ end
327
+
328
+ # Add a '?' for the parameter or a NULL if the value is nil or empty
329
+ # (except for a CLOB field where '' can be a value)
330
+ if self[col.name].nil? ||
331
+ self[col.name] == {} ||
332
+ self[col.name] == [] ||
333
+ (self[col.name] == '' && !(col.sql_type.to_s =~ /text|clob/i))
334
+ params << 'NULL'
335
+ else
336
+ values << if col.cast_type.is_a?(::ActiveRecord::Type::Serialized)
337
+ YAML.dump(self[col.name])
282
338
  else
283
- ",#{col.name}".to_s
339
+ self[col.name]
284
340
  end
341
+ params << '?'
342
+ end
343
+ counter += 1
344
+ end
285
345
 
286
- # Add a '?' for the parameter or a NULL if the value is nil or empty
287
- # (except for a CLOB field where '' can be a value)
288
- if self[col.name].nil? ||
289
- self[col.name] == {} ||
290
- self[col.name] == [] ||
291
- (self[col.name] == '' && !(col.sql_type.to_s =~ /text|clob/i))
292
- params << 'NULL'
293
- else
294
- values << if col.cast_type.is_a?(::ActiveRecord::Type::Serialized)
295
- YAML.dump(self[col.name])
296
- else
297
- self[col.name]
298
- end
299
- params << '?'
300
- end
301
- counter += 1
302
- end
303
-
304
- # no subsequent update is required if no relevant columns are found
305
- next if counter.zero?
346
+ # no subsequent update is required if no relevant columns are found
347
+ next if counter.zero?
306
348
 
307
- update_query << ') = '
308
- # IBM_DB accepts 'SET (column) = NULL' but not (NULL),
309
- # therefore the sql needs to be changed for a single NULL field.
310
- update_query << if params.size == 1 && params[0] == 'NULL'
311
- 'NULL'
312
- else
313
- '(' + params.join(',') + ')'
314
- end
349
+ update_query << ') = '
350
+ # IBM_DB accepts 'SET (column) = NULL' but not (NULL),
351
+ # therefore the sql needs to be changed for a single NULL field.
352
+ update_query << if params.size == 1 && params[0] == 'NULL'
353
+ 'NULL'
354
+ else
355
+ '(' + params.join(',') + ')'
356
+ end
315
357
 
316
- update_query << " WHERE #{self.class.primary_key} = ?"
317
- values << self[self.class.primary_key.downcase]
358
+ update_query << " WHERE #{self.class.primary_key} = ?"
359
+ values << self[self.class.primary_key.downcase]
318
360
 
319
- begin
320
- unless (stmt = IBM_DB.prepare(self.class.connection.connection, update_query))
321
- error_msg = IBM_DB.getErrormsg(self.class.connection.connection, IBM_DB::DB_CONN)
322
- if error_msg && !error_msg.empty?
323
- raise "Statement prepare for updating LOB/XML column failed : #{error_msg}"
324
- end
325
- raise StandardError.new('An unexpected error occurred during update of LOB/XML column')
326
- end
361
+ begin
362
+ unless (stmt = IBM_DB.prepare(conn.connection, update_query))
363
+ error_msg = IBM_DB.getErrormsg(conn.connection, IBM_DB::DB_CONN)
364
+ if error_msg && !error_msg.empty?
365
+ raise "Statement prepare for updating LOB/XML column failed : #{error_msg}"
366
+ end
367
+ raise StandardError.new('An unexpected error occurred during update of LOB/XML column')
368
+ end
327
369
 
328
- self.class.connection.log_query(update_query, 'update of LOB/XML field(s)in handle_lobs')
370
+ conn.log_query(update_query, 'update of LOB/XML field(s)in handle_lobs')
329
371
 
330
- # rollback any failed LOB/XML field updates (and remove associated marker)
331
- unless IBM_DB.execute(stmt, values)
332
- error_msg = "Failed to insert/update LOB/XML field(s) due to: #{IBM_DB.getErrormsg(stmt,
372
+ # rollback any failed LOB/XML field updates (and remove associated marker)
373
+ unless IBM_DB.execute(stmt, values)
374
+ error_msg = "Failed to insert/update LOB/XML field(s) due to: #{IBM_DB.getErrormsg(stmt,
333
375
  IBM_DB::DB_STMT)}"
334
- self.class.connection.execute('ROLLBACK')
335
- raise error_msg
376
+ conn.execute('ROLLBACK')
377
+ raise error_msg
378
+ end
379
+ rescue StandardError => e
380
+ raise e
381
+ ensure
382
+ IBM_DB.free_stmt(stmt) if stmt
383
+ end
384
+ # if clob_sql
385
+ # connection.sql.each
336
386
  end
337
- rescue StandardError => e
338
- raise e
339
- ensure
340
- IBM_DB.free_stmt(stmt) if stmt
341
- end
342
- # if clob_sql
343
- # connection.sql.each
344
- end
345
- self.class.connection.handle_lobs_triggered = true
387
+ conn.handle_lobs_triggered = true
388
+ end # if conn.is_a?
389
+ end # with_connection
346
390
  # if connection.kind_of?
347
391
  # handle_lobs
348
392
  end
@@ -387,10 +431,6 @@ module ActiveRecord
387
431
  username = config[:username].to_s
388
432
  password = config[:password].to_s
389
433
 
390
- if config.has_key?(:dbops) && config[:dbops] == true
391
- return ConnectionAdapters::IBM_DBAdapter.new(nil, isAr3, logger, config, {})
392
- end
393
-
394
434
  # Retrieves the database alias (local catalog name) or remote name
395
435
  # (for remote TCP/IP connections) from the +config+ hash
396
436
  # or raises ArgumentError in case of failure.
@@ -453,17 +493,13 @@ module ActiveRecord
453
493
  # No host implies a local catalog-based connection: +database+ represents catalog alias
454
494
  connection = IBM_DB.connect(database, username, password, conn_options, set_quoted_literal_replacement)
455
495
  end
496
+ return connection, isAr3, config, conn_options
456
497
  rescue StandardError => e
457
498
  raise "Failed to connect to [#{database}] due to: #{e}"
458
499
  end
459
500
  # Verifies that the connection was successful
460
501
  raise "An unexpected error occured during connect attempt to [#{database}]" unless connection
461
502
 
462
- # Creates an instance of *IBM_DBAdapter* based on the +connection+
463
- # and credentials provided in +config+
464
- ConnectionAdapters::IBM_DBAdapter.new(connection, isAr3, logger, config, conn_options)
465
-
466
- # If the connection failure was not caught previoulsy, it raises a Runtime error
467
503
  # method self.ibm_db_connection
468
504
  end
469
505
 
@@ -475,37 +511,26 @@ module ActiveRecord
475
511
  end
476
512
 
477
513
  module ConnectionAdapters
478
- class Column
479
- def self.binary_to_string(value)
480
- puts_log 'binary_to_string'
481
- # Returns a string removing the eventual BLOB scalar function
482
- value.to_s.gsub(/"SYSIBM"."BLOB"\('(.*)'\)/i, '\1')
483
- end
484
-
485
- # whether the column is auto-populated by the database using a sequence
486
- def auto_incremented_by_db?
487
- true
488
- end
489
-
490
- def auto_increment?
491
- true
492
- end
493
- alias_method :auto_incremented_by_db?, :auto_increment?
494
- end
495
-
496
514
  module Quoting
497
515
  def lookup_cast_type_from_column(column) # :nodoc:
498
516
  lookup_cast_type(column.sql_type_metadata.sql_type)
499
517
  end
500
- end
501
518
 
502
- module Savepoints
503
- def create_savepoint(name = current_savepoint_name)
504
- puts_log 'create_savepoint'
505
- # Turns off auto-committing
506
- auto_commit_off
507
- # Create savepoint
508
- internal_execute("SAVEPOINT #{name} ON ROLLBACK RETAIN CURSORS", 'TRANSACTION')
519
+ module ClassMethods
520
+ def quote_table_name(name)
521
+ if name.start_with? '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
522
+ name = "\"#{name}\""
523
+ else
524
+ name = name.to_s
525
+ end
526
+ name
527
+ # @servertype.check_reserved_words(name).gsub('"', '').gsub("'",'')
528
+ end
529
+
530
+ def quote_column_name(name)
531
+ name = name.to_s
532
+ name.gsub('"', '').gsub("'", '')
533
+ end
509
534
  end
510
535
  end
511
536
 
@@ -599,209 +624,6 @@ module ActiveRecord
599
624
  # class IBM_DBColumn
600
625
  end
601
626
 
602
- module ColumnMethods
603
- def primary_key(name, type = :primary_key, **options)
604
- puts_log '16'
605
- column(name, type, options.merge(primary_key: true))
606
- end
607
-
608
- # #class Table
609
- class Table < ActiveRecord::ConnectionAdapters::Table
610
- include ColumnMethods
611
-
612
- # Method to parse the passed arguments and create the ColumnDefinition object of the specified type
613
- def ibm_parse_column_attributes_args(type, *args)
614
- puts_log 'ibm_parse_column_attributes_args'
615
- options = {}
616
- options = args.delete_at(args.length - 1) if args.last.is_a?(Hash)
617
- args.each do |name|
618
- column name, type.to_sym, options
619
- # end args.each
620
- end
621
- end
622
- private :ibm_parse_column_attributes_args
623
-
624
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type xml
625
- # This method is different as compared to def char (sql is being issued explicitly
626
- # as compared to def char where method column(which will generate the sql is being called)
627
- # in order to handle the DEFAULT and NULL option for the native XML datatype
628
- def xml(*args)
629
- puts_log '18'
630
- args.delete_at(args.length - 1) if args.last.is_a?(Hash)
631
- sql_segment = "ALTER TABLE #{@base.quote_table_name(@table_name)} ADD COLUMN "
632
- args.each do |name|
633
- sql = sql_segment + " #{@base.quote_column_name(name)} xml"
634
- @base.execute(sql, 'add_xml_column')
635
- end
636
- self
637
- end
638
-
639
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type double
640
- def double(*args)
641
- puts_log '19'
642
- ibm_parse_column_attributes_args('double', *args)
643
- self
644
- end
645
-
646
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type decfloat
647
- def decfloat(*args)
648
- puts_log '20'
649
- ibm_parse_column_attributes_args('decfloat', *args)
650
- self
651
- end
652
-
653
- def graphic(*args)
654
- puts_log '21'
655
- ibm_parse_column_attributes_args('graphic', *args)
656
- self
657
- end
658
-
659
- def vargraphic(*args)
660
- puts_log '22'
661
- ibm_parse_column_attributes_args('vargraphic', *args)
662
- self
663
- end
664
-
665
- def bigint(*args)
666
- puts_log '23'
667
- ibm_parse_column_attributes_args('bigint', *args)
668
- self
669
- end
670
-
671
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type char [character]
672
- def char(*args)
673
- puts_log '24'
674
- ibm_parse_column_attributes_args('char', *args)
675
- self
676
- end
677
- alias character char
678
- # end of class Table
679
- end
680
-
681
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
682
- include ColumnMethods
683
-
684
- def native
685
- puts_log '25'
686
- @base.native_database_types
687
- end
688
-
689
- # Method to parse the passed arguments and create the ColumnDefinition object of the specified type
690
- def ibm_parse_column_attributes_args(type, *args)
691
- puts_log '26'
692
- options = {}
693
- options = args.delete_at(args.length - 1) if args.last.is_a?(Hash)
694
- args.each do |name|
695
- column(name, type, options)
696
- end
697
- end
698
- private :ibm_parse_column_attributes_args
699
-
700
- # Method to support the new syntax of rails 2.0 migrations for columns of type xml
701
- def xml(*args)
702
- puts_log '27'
703
- ibm_parse_column_attributes_args('xml', *args)
704
- self
705
- end
706
-
707
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type double
708
- def double(*args)
709
- puts_log '28'
710
- ibm_parse_column_attributes_args('double', *args)
711
- self
712
- end
713
-
714
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type decfloat
715
- def decfloat(*args)
716
- puts_log '29'
717
- ibm_parse_column_attributes_args('decfloat', *args)
718
- self
719
- end
720
-
721
- def graphic(*args)
722
- puts_log '30'
723
- ibm_parse_column_attributes_args('graphic', *args)
724
- self
725
- end
726
-
727
- def vargraphic(*args)
728
- puts_log '31'
729
- ibm_parse_column_attributes_args('vargraphic', *args)
730
- self
731
- end
732
-
733
- def bigint(*args)
734
- puts_log '32'
735
- ibm_parse_column_attributes_args('bigint', *args)
736
- self
737
- end
738
-
739
- # Method to support the new syntax of rails 2.0 migrations (short-hand definitions) for columns of type char [character]
740
- def char(*args)
741
- puts_log '33'
742
- ibm_parse_column_attributes_args('char', *args)
743
- self
744
- end
745
- alias character char
746
-
747
- # Overrides the abstract adapter in order to handle
748
- # the DEFAULT option for the native XML datatype
749
- def column(name, type, index: nil, **options)
750
- puts_log '34 column'
751
- name = name.to_s
752
- type = type.to_sym if type
753
-
754
- if @columns_hash[name]
755
- unless @columns_hash[name].primary_key?
756
- raise ArgumentError, "you can't define an already defined column '#{name}'."
757
- end
758
-
759
- raise ArgumentError,
760
- "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
761
-
762
- end
763
-
764
- # construct a column definition where @base is adaptor instance
765
- column = new_column_definition(name, type, **options)
766
-
767
- # DB2 does not accept DEFAULT NULL option for XML
768
- # for table create, but does accept nullable option
769
- if type.to_s == 'xml'
770
- column.null = options[:null]
771
- # Override column object's (instance of ColumnDefinition structure)
772
- # to_s which is expected to return the create_table SQL fragment
773
- # and bypass DEFAULT NULL option while still appending NOT NULL
774
- def column.to_s
775
- sql = "#{base.quote_column_name(name)} #{type}"
776
- sql << ' NOT NULL' if !null.nil? && (null == false)
777
- sql
778
- end
779
- else
780
- column.null = options[:null]
781
- column.default = options[:default]
782
- end
783
-
784
- column.scale = options[:scale] if options[:scale]
785
- column.precision = options[:precision] if options[:precision]
786
- # append column's limit option and yield native limits
787
- if options[:limit]
788
- column.limit = options[:limit]
789
- elsif @base.native_database_types[type.to_sym]
790
- if @base.native_database_types[type.to_sym].has_key? :limit
791
- column.limit = @base.native_database_types[type.to_sym][:limit]
792
- end
793
- end
794
-
795
- @columns << column unless @columns.nil? or @columns.include? column
796
-
797
- @columns_hash[name] = column
798
-
799
- self
800
- end
801
-
802
- end # end of class TableDefinition
803
- end # end of module ColumnMethods
804
-
805
627
  # The IBM_DB Adapter requires the native Ruby driver (ibm_db)
806
628
  # for IBM data servers (ibm_db.so).
807
629
  # +config+ the hash passed as an initializer argument content:
@@ -842,6 +664,42 @@ module ActiveRecord
842
664
  'IBM_DB'
843
665
  end
844
666
 
667
+ include Savepoints
668
+
669
+ def create_savepoint(name = current_savepoint_name)
670
+ puts_log 'create_savepoint'
671
+ # Turns off auto-committing
672
+ auto_commit_off
673
+ # Create savepoint
674
+ internal_execute("SAVEPOINT #{name} ON ROLLBACK RETAIN CURSORS", 'TRANSACTION')
675
+ end
676
+
677
+ class Column < ActiveRecord::ConnectionAdapters::Column
678
+ attr_reader :rowid
679
+
680
+ def initialize(*, auto_increment: nil, rowid: false, generated_type: nil, **)
681
+ super
682
+ @auto_increment = auto_increment
683
+ @rowid = rowid
684
+ @generated_type = generated_type
685
+ end
686
+
687
+ def self.binary_to_string(value)
688
+ # Returns a string removing the eventual BLOB scalar function
689
+ value.to_s.gsub(/"SYSIBM"."BLOB"\('(.*)'\)/i, '\1')
690
+ end
691
+
692
+ # whether the column is auto-populated by the database using a sequence
693
+ def auto_increment?
694
+ @auto_increment
695
+ end
696
+
697
+ def auto_incremented_by_db?
698
+ auto_increment? || rowid
699
+ end
700
+ alias_method :auto_incremented_by_db?, :auto_increment?
701
+ end
702
+
845
703
  class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
846
704
  attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
847
705
  def initialize(td)
@@ -889,6 +747,11 @@ module ActiveRecord
889
747
  options = @conn.unique_constraint_options(name, column_name, options)
890
748
  UniqueConstraintDefinition.new(name, column_name, options)
891
749
  end
750
+
751
+ def references(*args, **options)
752
+ super(*args, type: :integer, **options)
753
+ end
754
+ alias :belongs_to :references
892
755
  end # end of class TableDefinition
893
756
 
894
757
  UniqueConstraintDefinition = Struct.new(:table_name, :column, :options) do
@@ -927,8 +790,9 @@ module ActiveRecord
927
790
  StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
928
791
  end
929
792
 
930
- def initialize(connection, ar3, logger, config, conn_options)
793
+ def initialize(args)
931
794
  # Caching database connection configuration (+connect+ or +reconnect+ support)\
795
+ connection, ar3, config, conn_options = ActiveRecord::Base.ibm_db_connection(args)
932
796
  @config = config
933
797
  @connection = connection
934
798
  @isAr3 = ar3
@@ -963,8 +827,7 @@ module ActiveRecord
963
827
  @handle_lobs_triggered = false
964
828
 
965
829
  # Calls the parent class +ConnectionAdapters+' initializer
966
- # which sets @connection, @logger, @runtime and @last_verification
967
- super(@connection, logger, @config)
830
+ super(@config)
968
831
 
969
832
  if @connection
970
833
  server_info = IBM_DB.server_info(@connection)
@@ -1143,6 +1006,15 @@ module ActiveRecord
1143
1006
  true
1144
1007
  end
1145
1008
 
1009
+ #IBM Db2 does not natively support skipping rows on insert when there's a duplicate key
1010
+ def supports_insert_on_duplicate_skip?
1011
+ false
1012
+ end
1013
+
1014
+ def supports_insert_on_duplicate_update?
1015
+ false
1016
+ end
1017
+
1146
1018
  # This adapter supports migrations.
1147
1019
  # Current limitations:
1148
1020
  # +rename_column+ is not currently supported by the IBM data servers
@@ -1262,6 +1134,7 @@ module ActiveRecord
1262
1134
  @set_quoted_literal_replacement)
1263
1135
  puts_log "Connection Established B = #{@connection}"
1264
1136
  end
1137
+ @raw_connection = @connection
1265
1138
  rescue StandardError => e
1266
1139
  warn "Connection to database #{@database} failed: #{e}"
1267
1140
  puts_log "Connection to database #{@database} failed: #{e}"
@@ -1318,6 +1191,7 @@ module ActiveRecord
1318
1191
  IBM_DB.close(@connection)
1319
1192
  puts_log "Connection closed #{Thread.current}"
1320
1193
  @connection = nil
1194
+ @raw_connection = nil
1321
1195
  rescue StandardError => e
1322
1196
  puts_log "Connection close failure #{e.message}, #{Thread.current}"
1323
1197
  end
@@ -1325,13 +1199,24 @@ module ActiveRecord
1325
1199
  end
1326
1200
  end
1327
1201
 
1202
+ # Check the connection back in to the connection pool
1203
+ def close
1204
+ pool.checkin self
1205
+ disconnect!
1206
+ end
1207
+
1208
+ def connected?
1209
+ puts_log "connected? #{@connection}"
1210
+ !(@connection.nil?)
1211
+ end
1212
+
1328
1213
  #==============================================
1329
1214
  # DATABASE STATEMENTS
1330
1215
  #==============================================
1331
1216
 
1332
1217
  def create_table(name, id: :primary_key, primary_key: nil, force: nil, **options)
1333
1218
  puts_log "create_table name=#{name}, id=#{id}, primary_key=#{primary_key}, force=#{force}"
1334
- puts_log "create_table Options = #{options}"
1219
+ puts_log "create_table Options 1 = #{options}"
1335
1220
  puts_log "primary_key_prefix_type = #{ActiveRecord::Base.primary_key_prefix_type}"
1336
1221
  puts_log caller
1337
1222
  @servertype.setup_for_lob_table
@@ -1356,6 +1241,7 @@ module ActiveRecord
1356
1241
  options[:auto_increment] = true if options[:auto_increment].nil? and %i[integer bigint].include?(id)
1357
1242
  end
1358
1243
 
1244
+ puts_log "create_table Options 2 = #{options}"
1359
1245
  super(name, id: id, primary_key: primary_key, force: force, **options)
1360
1246
  end
1361
1247
 
@@ -1381,8 +1267,8 @@ module ActiveRecord
1381
1267
  end
1382
1268
  end
1383
1269
 
1384
- def select(sql, name = nil, binds = [], prepare: false, async: false)
1385
- puts_log "select #{sql}"
1270
+ def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
1271
+ puts_log "select sql = #{sql}"
1386
1272
  puts_log "binds = #{binds}"
1387
1273
  puts_log "prepare = #{prepare}"
1388
1274
 
@@ -1414,20 +1300,21 @@ module ActiveRecord
1414
1300
  end
1415
1301
 
1416
1302
  results = []
1303
+ cols = []
1417
1304
 
1418
1305
  stmt = if binds.nil? || binds.empty?
1419
- internal_execute(sql, name)
1306
+ internal_execute(sql, name, allow_retry: allow_retry)
1420
1307
  else
1421
- exec_query_ret_stmt(sql, name, binds, prepare: prepare, async: async)
1308
+ exec_query_ret_stmt(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry)
1422
1309
  end
1423
1310
 
1424
- cols = IBM_DB.resultCols(stmt)
1425
-
1426
1311
  if stmt
1312
+ cols = IBM_DB.resultCols(stmt)
1427
1313
  results = fetch_data(stmt)
1428
- puts_log "Results = #{results}"
1429
1314
  end
1430
1315
 
1316
+ puts_log "select cols = #{cols}, results = #{results}"
1317
+
1431
1318
  if @isAr3
1432
1319
  results
1433
1320
  else
@@ -1437,11 +1324,13 @@ module ActiveRecord
1437
1324
  end
1438
1325
  end
1439
1326
 
1327
+ puts_log "select final results = #{results} #{caller}"
1440
1328
  results
1441
1329
  end
1442
1330
 
1443
1331
  def translate_exception(exception, message:, sql:, binds:)
1444
- puts_log "translate_exception - #{message}"
1332
+ puts_log "translate_exception - exception = #{exception}, message = #{message}"
1333
+ puts_log "translate_exception #{caller}"
1445
1334
  error_msg1 = /SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by .* constrains table .* from having duplicate values for the index key/
1446
1335
  error_msg2 = /SQL0204N .* is an undefined name/
1447
1336
  error_msg3 = /SQL0413N Overflow occurred during numeric data type conversion/
@@ -1471,8 +1360,13 @@ module ActiveRecord
1471
1360
  elsif exception.message.match?(/called on a closed database/i)
1472
1361
  puts_log 'ConnectionNotEstablished exception'
1473
1362
  ConnectionNotEstablished.new(exception, connection_pool: @pool)
1363
+ elsif message.strip.start_with?("FrozenError") or
1364
+ message.strip.start_with?("ActiveRecord::Encryption::Errors::Encoding:") or
1365
+ message.strip.start_with?("ActiveRecord::Encryption::Errors::Encryption") or
1366
+ message.strip.start_with?("ActiveRecord::ConnectionFailed")
1367
+ exception
1474
1368
  else
1475
- super
1369
+ super(message, message: exception, sql: sql, binds: binds)
1476
1370
  end
1477
1371
  end
1478
1372
 
@@ -1611,11 +1505,52 @@ module ActiveRecord
1611
1505
  " VALUES (#{val})"
1612
1506
  end
1613
1507
 
1508
+ def getTableIdentityColumn(table_name)
1509
+ query = "SELECT COLNAME FROM SYSCAT.COLUMNS WHERE TABNAME = #{quote(table_name.upcase)} AND IDENTITY = 'Y'"
1510
+ puts_log "getTableIdentityColumn table_name = #{table_name}, query = #{query}"
1511
+ rows = execute_without_logging(query).rows
1512
+ puts_log "getTableIdentityColumn rows = #{rows}"
1513
+ if rows.any?
1514
+ return rows.first
1515
+ end
1516
+ end
1517
+
1518
+ def return_insert (stmt, sql, binds, pk, id_value = nil, returning: nil)
1519
+ puts_log "return_insert sql = #{sql}, pk = #{pk}, returning = #{returning}"
1520
+ @sql << sql
1521
+
1522
+ table_name = sql[/\AINSERT\s+INTO\s+([^\s\(]+)/i, 1]
1523
+ rowID = getTableIdentityColumn(table_name)
1524
+ #Identity column exist.
1525
+ if Array(rowID).any?
1526
+ val = @servertype.last_generated_id(stmt)
1527
+ #returning required is just an ID, or nothing is expected to return
1528
+ only_returning_id = Array(returning).empty? ||
1529
+ (Array(returning).size == 1 && Array(rowID).first == Array(returning).first)
1530
+ unless only_returning_id
1531
+ cols = Array(returning).join(', ')
1532
+ query = "SELECT #{cols} FROM #{table_name} WHERE #{Array(rowID).first} = #{val}"
1533
+ puts_log "return_insert val = #{val}, cols = #{cols}, table_name = #{table_name}"
1534
+ puts_log "return_insert query = #{query}"
1535
+ rows = execute_without_logging(query).rows
1536
+ puts_log "return_insert rows = #{rows}"
1537
+ return rows.first
1538
+ end
1539
+ end
1540
+
1541
+ puts_log "return_insert id_value = #{id_value}, val = #{val}"
1542
+ if !returning.nil?
1543
+ [id_value || val]
1544
+ else
1545
+ id_value || val
1546
+ end
1547
+ end
1548
+
1614
1549
  # Perform an insert and returns the last ID generated.
1615
1550
  # This can be the ID passed to the method or the one auto-generated by the database,
1616
1551
  # and retrieved by the +last_generated_id+ method.
1617
- def insert_direct(sql, name = nil, _pk = nil, id_value = nil, _sequence_name = nil, returning: nil)
1618
- puts_log 'insert_direct'
1552
+ def insert_direct(sql, name = nil, _pk = nil, id_value = nil, returning: nil)
1553
+ puts_log "insert_direct sql = #{sql}, name = #{name}, _pk = #{_pk}, returning = #{returning}"
1619
1554
  if @handle_lobs_triggered # Ensure the array of sql is cleared if they have been handled in the callback
1620
1555
  @sql = []
1621
1556
  @handle_lobs_triggered = false
@@ -1624,9 +1559,7 @@ module ActiveRecord
1624
1559
  return unless stmt = execute(sql, name)
1625
1560
 
1626
1561
  begin
1627
- @sql << sql
1628
- return [@servertype.last_generated_id(stmt)] unless returning.nil?
1629
- id_value || @servertype.last_generated_id(stmt)
1562
+ return_insert(stmt, sql, nil, _pk, id_value, returning: returning)
1630
1563
  # Ensures to free the resources associated with the statement
1631
1564
  ensure
1632
1565
  IBM_DB.free_stmt(stmt) if stmt
@@ -1634,7 +1567,7 @@ module ActiveRecord
1634
1567
  end
1635
1568
 
1636
1569
  def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
1637
- puts_log "insert Binds P = #{binds}"
1570
+ puts_log "insert Binds P = #{binds}, name = #{name}, pk = #{pk}, id_value = #{id_value}, returning = #{returning}"
1638
1571
  puts_log caller
1639
1572
  if @arelVersion < 6
1640
1573
  sql = to_sql(arel)
@@ -1646,27 +1579,30 @@ module ActiveRecord
1646
1579
  puts_log "insert Binds A = #{binds}"
1647
1580
  puts_log "insert SQL = #{sql}"
1648
1581
  # unless IBM_DBAdapter.respond_to?(:exec_insert)
1649
- return insert_direct(sql, name, pk, id_value, sequence_name, returning: returning) if binds.nil? || binds.empty?
1582
+ return insert_direct(sql, name, pk, id_value, returning: returning) if binds.nil? || binds.empty?
1650
1583
 
1651
1584
  ActiveRecord::Base.clear_query_caches_for_current_thread
1652
1585
 
1653
1586
  return unless stmt = exec_insert_db2(sql, name, binds, pk, sequence_name, returning)
1654
1587
 
1655
1588
  begin
1656
- @sql << sql
1657
- return [@servertype.last_generated_id(stmt)] unless returning.nil?
1658
- id_value || @servertype.last_generated_id(stmt)
1589
+ return_insert(stmt, sql, binds, pk, id_value, returning: returning)
1659
1590
  ensure
1660
1591
  IBM_DB.free_stmt(stmt) if stmt
1661
1592
  end
1662
1593
  end
1663
1594
 
1664
1595
  def exec_insert_db2(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning = nil)
1665
- puts_log 'exec_insert_db2'
1596
+ puts_log "exec_insert_db2 sql = #{sql}, name = #{name}, binds = #{binds}, pk = #{pk}, returning = #{returning}"
1666
1597
  sql, binds = sql_for_insert(sql, pk, binds, returning)
1667
1598
  exec_query_ret_stmt(sql, name, binds, prepare: false)
1668
1599
  end
1669
1600
 
1601
+ def build_insert_sql(insert) # :nodoc:
1602
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
1603
+ sql
1604
+ end
1605
+
1670
1606
  def last_inserted_id(result)
1671
1607
  puts_log 'last_inserted_id'
1672
1608
  result
@@ -1745,7 +1681,7 @@ module ActiveRecord
1745
1681
  !READ_QUERY.match?(sql.b)
1746
1682
  end
1747
1683
 
1748
- def explain(arel, binds = [])
1684
+ def explain(arel, binds = [], options = [])
1749
1685
  sql = "EXPLAIN ALL SET QUERYNO = 1 FOR #{to_sql(arel, binds)}"
1750
1686
  stmt = execute(sql, 'EXPLAIN')
1751
1687
  result = select("select * from explain_statement where explain_level = 'P' and queryno = 1", 'EXPLAIN')
@@ -1755,15 +1691,55 @@ module ActiveRecord
1755
1691
  IBM_DB.free_stmt(stmt) if stmt
1756
1692
  end
1757
1693
 
1694
+ def execute_without_logging(sql, name = nil, binds = [], prepare: true, async: false)
1695
+ puts_log "execute_without_logging sql = #{sql}, name = #{name}, binds = #{binds}"
1696
+
1697
+ sql = transform_query(sql)
1698
+ check_if_write_query(sql)
1699
+ mark_transaction_written_if_write(sql)
1700
+ cols = nil
1701
+ results = nil
1702
+ begin
1703
+ param_array = type_casted_binds(binds)
1704
+ puts_log "execute_without_logging Param array = #{param_array}"
1705
+ puts_log "execute_without_logging #{caller}"
1706
+
1707
+ stmt = @servertype.prepare(sql, name)
1708
+ @statements[sql] = stmt if prepare
1709
+
1710
+ puts_log "execute_without_logging Statement = #{stmt}"
1711
+
1712
+ execute_prepared_stmt(stmt, param_array)
1713
+
1714
+ if stmt and sql.strip.upcase.start_with?("SELECT")
1715
+ cols = IBM_DB.resultCols(stmt)
1716
+ results = fetch_data(stmt) if stmt
1717
+
1718
+ puts_log "execute_without_logging columns = #{cols}"
1719
+ puts_log "execute_without_logging result = #{results}"
1720
+ end
1721
+ rescue => e
1722
+ raise translate_exception_class(e, sql, binds)
1723
+ ensure
1724
+ @offset = @limit = nil
1725
+ end
1726
+ if @isAr3
1727
+ results
1728
+ elsif results.nil?
1729
+ ActiveRecord::Result.empty
1730
+ else
1731
+ ActiveRecord::Result.new(cols, results)
1732
+ end
1733
+ end
1734
+
1758
1735
  # Executes +sql+ statement in the context of this connection using
1759
1736
  # +binds+ as the bind substitutes. +name+ is logged along with
1760
1737
  # the executed +sql+ statement.
1761
1738
  # Here prepare argument is not used, by default this method creates prepared statment and execute.
1762
- def exec_query_ret_stmt(sql, name = 'SQL', binds = [], prepare: false, async: false)
1739
+ def exec_query_ret_stmt(sql, name = 'SQL', binds = [], prepare: false, async: false, allow_retry: false)
1763
1740
  puts_log "exec_query_ret_stmt #{sql}"
1764
1741
  sql = transform_query(sql)
1765
1742
  check_if_write_query(sql)
1766
- #materialize_transactions
1767
1743
  mark_transaction_written_if_write(sql)
1768
1744
  begin
1769
1745
  puts_log "SQL = #{sql}"
@@ -1778,7 +1754,7 @@ module ActiveRecord
1778
1754
 
1779
1755
  puts_log "Statement = #{stmt}"
1780
1756
  log(sql, name, binds, param_array, async: async) do
1781
- with_raw_connection do |conn|
1757
+ with_raw_connection(allow_retry: allow_retry) do |conn|
1782
1758
  return false unless stmt
1783
1759
  return stmt if execute_prepared_stmt(stmt, param_array)
1784
1760
  end
@@ -1799,7 +1775,10 @@ module ActiveRecord
1799
1775
  puts_log "select_prepared sql before = #{sql}"
1800
1776
  puts_log "select_prepared Binds = #{binds}"
1801
1777
  stmt = exec_query_ret_stmt(sql, name, binds, prepare: prepare, async: async)
1802
- if !/^select .*/i.match(sql).nil?
1778
+ cols = nil
1779
+ results = nil
1780
+
1781
+ if stmt and sql.strip.upcase.start_with?("SELECT")
1803
1782
  cols = IBM_DB.resultCols(stmt)
1804
1783
 
1805
1784
  results = fetch_data(stmt) if stmt
@@ -1807,12 +1786,11 @@ module ActiveRecord
1807
1786
  puts_log "select_prepared columns = #{cols}"
1808
1787
  puts_log "select_prepared sql after = #{sql}"
1809
1788
  puts_log "select_prepared result = #{results}"
1810
- else
1811
- cols = nil
1812
- results = nil
1813
1789
  end
1814
1790
  if @isAr3
1815
1791
  results
1792
+ elsif results.nil?
1793
+ ActiveRecord::Result.empty
1816
1794
  else
1817
1795
  ActiveRecord::Result.new(cols, results)
1818
1796
  end
@@ -1829,19 +1807,36 @@ module ActiveRecord
1829
1807
  def execute(sql, name = nil, allow_retry: false)
1830
1808
  puts_log "execute #{sql}"
1831
1809
  ActiveRecord::Base.clear_query_caches_for_current_thread
1832
- internal_execute(sql, name, allow_retry: allow_retry)
1810
+ stmt = internal_execute(sql, name, allow_retry: allow_retry)
1811
+ cols = nil
1812
+ results = nil
1813
+ puts_log "raw_execute stmt = #{stmt}"
1814
+ if sql.strip.upcase.start_with?("SELECT") and stmt
1815
+ cols = IBM_DB.resultCols(stmt)
1816
+ results = fetch_data(stmt)
1817
+
1818
+ puts_log "execute columns = #{cols}"
1819
+ puts_log "execute result = #{results}"
1820
+ end
1821
+ if results.nil? || results.empty?
1822
+ stmt
1823
+ else
1824
+ formatted = cols.each_with_index.map { |col, i| { col => results[i].first } }
1825
+ puts_log "raw_execute formatted = #{formatted}"
1826
+ formatted.to_s
1827
+ end
1833
1828
  end
1834
1829
 
1835
1830
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
1836
1831
  # Logs and execute the sql instructions.
1837
1832
  # The +log+ method is defined in the parent class +AbstractAdapter+
1838
1833
  # sql='INSERT INTO ar_internal_metadata (key, value, created_at, updated_at) VALUES ('10', '10', '10', '10')
1839
- puts_log "raw_execute #{sql} #{Thread.current}"
1834
+ puts_log "raw_execute sql = #{sql} #{Thread.current}"
1840
1835
  log(sql, name, async: async) do
1841
1836
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
1842
1837
  verify!
1843
1838
  puts_log "raw_execute executes query #{Thread.current}"
1844
- result = @servertype.execute(sql, name)
1839
+ result= @servertype.execute(sql, name)
1845
1840
  puts_log "raw_execute result = #{result} #{Thread.current}"
1846
1841
  verified!
1847
1842
  result
@@ -2009,7 +2004,8 @@ module ActiveRecord
2009
2004
  end
2010
2005
 
2011
2006
  def default_sequence_name(table, column) # :nodoc:
2012
- puts_log '72'
2007
+ puts_log "default_sequence_name table = #{table}, column = #{column}"
2008
+ return nil if column.is_a?(Array)
2013
2009
  "#{table}_#{column}_seq"
2014
2010
  end
2015
2011
 
@@ -2078,6 +2074,7 @@ module ActiveRecord
2078
2074
 
2079
2075
  def quote_table_name(name)
2080
2076
  puts_log "quote_table_name #{name}"
2077
+ puts_log caller
2081
2078
  if name.start_with? '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
2082
2079
  name = "\"#{name}\""
2083
2080
  else
@@ -2089,7 +2086,7 @@ module ActiveRecord
2089
2086
  end
2090
2087
 
2091
2088
  def quote_column_name(name)
2092
- puts_log 'quote_column_name'
2089
+ puts_log "quote_column_name #{name}"
2093
2090
  @servertype.check_reserved_words(name).gsub('"', '').gsub("'", '')
2094
2091
  end
2095
2092
 
@@ -2106,7 +2103,7 @@ module ActiveRecord
2106
2103
  def native_database_types
2107
2104
  {
2108
2105
  primary_key: { name: @servertype.primary_key_definition(@start_id) },
2109
- string: { name: 'varchar', limit: 255 },
2106
+ string: { name: 'varchar', limit: 400 },
2110
2107
  text: { name: 'clob' },
2111
2108
  integer: { name: 'integer' },
2112
2109
  float: { name: 'float' },
@@ -2509,7 +2506,7 @@ module ActiveRecord
2509
2506
  next if is_composite
2510
2507
 
2511
2508
  sql = "select remarks from syscat.indexes where tabname = #{quote(table_name.upcase)} and indname = #{quote(index_stats[5])}"
2512
- comment = single_value_from_rows(select_prepared(sql, "SCHEMA").rows)
2509
+ comment = single_value_from_rows(execute_without_logging(sql, "SCHEMA").rows)
2513
2510
 
2514
2511
  indexes << IndexDefinition.new(table_name, index_name, index_unique, index_columns,
2515
2512
  comment: comment)
@@ -2669,22 +2666,34 @@ module ActiveRecord
2669
2666
  # sql = "select * from sysibm.sqlcolumns where table_name = #{quote(table_name.upcase)}"
2670
2667
  if @debug == true
2671
2668
  sql = "select * from syscat.columns where tabname = #{quote(table_name.upcase)}"
2672
- puts_log "SYSIBM.SQLCOLUMNS = #{select_prepared(sql).rows}"
2669
+ puts_log "SYSIBM.SQLCOLUMNS = #{execute_without_logging(sql).rows}"
2673
2670
  end
2674
2671
 
2672
+ pri_key = primary_key(table_name)
2673
+
2675
2674
  if stmt
2676
2675
  begin
2677
2676
  # Fetches all the columns and assigns them to col.
2678
2677
  # +col+ is an hash with keys/value pairs for a column
2679
2678
  while col = IBM_DB.fetch_assoc(stmt)
2680
- puts_log col
2679
+ rowid = false
2680
+ puts_log "def columns fecthed = #{col}"
2681
2681
  column_name = col['column_name'].downcase
2682
+ sql = "select 1 FROM syscat.columns where tabname = #{quote(table_name.upcase)} and generated = 'D' and colname = '#{col['column_name']}'"
2683
+ rows = execute_without_logging(sql).rows
2684
+ auto_increment = rows.dig(0, 0) == 1 ? true : nil
2685
+ puts_log "def columns auto_increment = #{rows}, #{auto_increment}"
2686
+
2682
2687
  # Assigns the column default value.
2683
2688
  column_default_value = col['column_def']
2684
2689
  default_value = extract_value_from_default(column_default_value)
2685
2690
  # Assigns the column type
2686
2691
  column_type = col['type_name'].downcase
2687
2692
 
2693
+ if Array(pri_key).include?(column_name) and column_type =~ /integer|bigint/i
2694
+ rowid = true
2695
+ puts_log "def columns rowid = true"
2696
+ end
2688
2697
  # Assigns the field length (size) for the column
2689
2698
 
2690
2699
  column_length = if column_type =~ /integer|bigint/i
@@ -2743,7 +2752,9 @@ module ActiveRecord
2743
2752
 
2744
2753
  column_type = 'boolean' if ruby_type.to_s == 'boolean'
2745
2754
 
2755
+ puts_log "Inside def columns() - default_value = #{default_value}, column_default_value = #{column_default_value}"
2746
2756
  default_function = extract_default_function(default_value, column_default_value)
2757
+ puts_log "Inside def columns() - default_function = #{default_function}"
2747
2758
 
2748
2759
  sqltype_metadata = SqlTypeMetadata.new(
2749
2760
  # sql_type: sql_type,
@@ -2755,7 +2766,7 @@ module ActiveRecord
2755
2766
  )
2756
2767
 
2757
2768
  columns << Column.new(column_name, default_value, sqltype_metadata, column_nullable, default_function,
2758
- comment: col['remarks'])
2769
+ comment: col['remarks'], auto_increment: auto_increment, rowid: rowid)
2759
2770
  end
2760
2771
  rescue StandardError => e # Handle driver fetch errors
2761
2772
  error_msg = IBM_DB.getErrormsg(stmt, IBM_DB::DB_STMT)
@@ -2789,6 +2800,7 @@ module ActiveRecord
2789
2800
 
2790
2801
  def has_default_function?(default_value, default)
2791
2802
  !default_value && /\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP/.match?(default)
2803
+ !default_value && /(\w+\(.*\)|CURRENT(?:[_\s]TIME|[_\s]DATE|[_\s]TIMESTAMP))/i.match?(default)
2792
2804
  end
2793
2805
 
2794
2806
  def foreign_keys(table_name)
@@ -2932,9 +2944,10 @@ module ActiveRecord
2932
2944
 
2933
2945
  # Adds comment for given table or drops it if +comment+ is a +nil+
2934
2946
  def change_table_comment(table_name, comment_or_changes) # :nodoc:
2935
- puts_log 'change_table_comment'
2947
+ puts_log "change_table_comment table_name = #{table_name}, comment_or_changes = #{comment_or_changes}"
2936
2948
  clear_cache!
2937
2949
  comment = extract_new_comment_value(comment_or_changes)
2950
+ puts_log "change_table_comment new_comment = #{comment}"
2938
2951
  if comment.nil?
2939
2952
  execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS ''"
2940
2953
  else
@@ -2963,9 +2976,9 @@ module ActiveRecord
2963
2976
  end
2964
2977
 
2965
2978
  def table_comment(table_name) # :nodoc:
2966
- puts_log 'table_comment'
2979
+ puts_log "table_comment table_name = #{table_name}"
2967
2980
  sql = "select remarks from syscat.tables where tabname = #{quote(table_name.upcase)}"
2968
- single_value_from_rows(select_prepared(sql).rows)
2981
+ single_value_from_rows(execute_without_logging(sql).rows)
2969
2982
  end
2970
2983
 
2971
2984
  def add_index(table_name, column_name, **options) # :nodoc:
@@ -3087,7 +3100,7 @@ module ActiveRecord
3087
3100
  # rename_table('octopuses', 'octopi')
3088
3101
  # Overriden to satisfy IBM data servers syntax
3089
3102
  def rename_table(name, new_name, **options)
3090
- puts_log 'rename_table'
3103
+ puts_log "rename_table name = #{name}, new_name = #{new_name}"
3091
3104
  validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
3092
3105
  clear_cache!
3093
3106
  schema_cache.clear_data_source_cache!(name.to_s)
@@ -3108,19 +3121,20 @@ module ActiveRecord
3108
3121
  end
3109
3122
 
3110
3123
  def add_reference(table_name, ref_name, **options) # :nodoc:
3124
+ puts_log "add_reference table_name = #{table_name}, ref_name = #{ref_name}"
3111
3125
  super(table_name, ref_name, type: :integer, **options)
3112
3126
  end
3113
3127
  alias :add_belongs_to :add_reference
3114
3128
 
3115
3129
  def drop_table_indexes(index_list)
3116
- puts_log 'drop_table_indexes'
3130
+ puts_log "drop_table_indexes index_list = #{index_list}"
3117
3131
  index_list.each do |indexs|
3118
3132
  remove_index(indexs.table, name: indexs.name)
3119
3133
  end
3120
3134
  end
3121
3135
 
3122
3136
  def create_table_indexes(index_list, new_table)
3123
- puts_log 'create_table_indexes'
3137
+ puts_log "create_table_indexes index_list = #{index_list}, new_table = #{new_table}"
3124
3138
  index_list.each do |indexs|
3125
3139
  generated_index_name = index_name(indexs.table, column: indexs.columns)
3126
3140
  custom_index_name = indexs.name
@@ -3412,6 +3426,7 @@ module ActiveRecord
3412
3426
  puts_log "remove_unique_constraint table_name = #{table_name}, column_name = #{column_name}, options = #{options}"
3413
3427
  unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
3414
3428
 
3429
+ puts_log "remove_unique_constraint unique_name_to_delete = #{unique_name_to_delete}"
3415
3430
  at = create_alter_table(table_name)
3416
3431
  at.drop_unique_constraint(unique_name_to_delete)
3417
3432
 
@@ -3419,7 +3434,7 @@ module ActiveRecord
3419
3434
  end
3420
3435
 
3421
3436
  def unique_constraint_name(table_name, **options)
3422
- puts_log "unique_constraint_name"
3437
+ puts_log "unique_constraint_name table_name = #{table_name}, options = #{options}"
3423
3438
  options.fetch(:name) do
3424
3439
  column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
3425
3440
  identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
@@ -3430,16 +3445,72 @@ module ActiveRecord
3430
3445
  end
3431
3446
 
3432
3447
  def unique_constraint_for(table_name, **options)
3433
- name = unique_constraint_name(table_name, **options) unless options.key?(:column)
3434
- unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
3448
+ puts_log "unique_constraint_for table_name = #{table_name}, options = #{options}"
3449
+ name = unique_constraint_name(table_name, **options)
3450
+ puts_log "unique_constraint_for name = #{name}"
3451
+ uq = unique_constraints(table_name)
3452
+ puts_log "unique_constraint_for unique_constraints = #{uq}"
3453
+ uq.detect { |unique_constraint| unique_constraint.defined_for?(name: name) }
3435
3454
  end
3436
3455
 
3437
3456
  def unique_constraint_for!(table_name, column: nil, **options)
3438
- puts_log "unique_constraint_for table_name = #{table_name}, column = #{column}, options = #{options}"
3457
+ puts_log "unique_constraint_for! table_name = #{table_name}, column = #{column}, options = #{options}"
3439
3458
  unique_constraint_for(table_name, column: column, **options) ||
3440
3459
  raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
3441
3460
  end
3442
3461
 
3462
+ def foreign_key_name(table_name, options)
3463
+ puts_log "foreign_key_name table_name = #{table_name}, options = #{options}"
3464
+ options.fetch(:name) do
3465
+ columns = Array(options.fetch(:column)).map(&:to_s)
3466
+ identifier = "#{table_name}_#{columns * '_and_'}_fk"
3467
+ hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10)
3468
+
3469
+ "fk_rails_#{hashed_identifier}"
3470
+ end
3471
+ end
3472
+
3473
+ def foreign_key_for(from_table, **options)
3474
+ puts_log "foreign_key_for from_table = #{from_table}, options = #{options}"
3475
+ return unless use_foreign_keys?
3476
+ fks = foreign_keys(from_table)
3477
+ puts_log "foreign_key_for fks = #{fks}"
3478
+ if options.key?(:column) && options.key?(:to_table) && options[:to_table] != nil
3479
+ name = foreign_key_name(from_table, options)
3480
+ puts_log "foreign_key_for name = #{options}"
3481
+ fks.detect { |fk| fk.defined_for?(name: name) }
3482
+ else
3483
+ fks.detect { |fk| fk.defined_for?(**options) }
3484
+ end
3485
+ end
3486
+
3487
+ def foreign_key_for!(from_table, to_table: nil, **options)
3488
+ puts_log "foreign_key_for! from_table = #{from_table}, to_table = #{to_table}, options = #{options}"
3489
+ foreign_key_for(from_table, to_table: to_table, **options) ||
3490
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
3491
+ end
3492
+
3493
+ def foreign_key_exists?(from_table, to_table = nil, **options)
3494
+ puts_log "foreign_key_exists? from_table = #{from_table}, to_table = #{to_table}, options = #{options}"
3495
+ foreign_key_for(from_table, to_table: to_table, **options).present?
3496
+ end
3497
+
3498
+ def remove_foreign_key(from_table, to_table = nil, **options)
3499
+ puts_log "remove_foreign_key from_table = #{from_table}, to_table = #{to_table}, options = #{options}"
3500
+ #to_table ||= options[:to_table]
3501
+ return unless use_foreign_keys?
3502
+ #return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
3503
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
3504
+
3505
+ fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
3506
+ puts_log "remove_foreign_key fk_name_to_delete = #{fk_name_to_delete}"
3507
+
3508
+ at = create_alter_table from_table
3509
+ at.drop_foreign_key fk_name_to_delete
3510
+
3511
+ execute schema_creation.accept(at)
3512
+ end
3513
+
3443
3514
  def create_table_definition(name, **options)
3444
3515
  puts_log "create_table_definition name = #{name}"
3445
3516
  puts_log caller