activerecord-ingres-adapter 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ require 'Ingres'
2
+ require 'test/unit'
3
+ require 'ext/tests/config.rb'
4
+
5
+ class TestIngresTypeDateFetch< Test::Unit::TestCase
6
+ def setup
7
+ @@ing = Ingres.new()
8
+ assert_kind_of(Ingres, @@ing.connect(@@database), "conn is not an Ingres object")
9
+ end
10
+
11
+ def teardown
12
+ @@ing.disconnect
13
+ end
14
+
15
+ def test_ingresdate_fetch
16
+ sql = "select rt_depart_at, rt_arrive_at from route where rt_depart_from = 'VLL' and rt_arrive_to='MAD' and rt_id=1305"
17
+ data = @@ing.execute(sql).flatten
18
+ assert_equal "11-oct-2006 19:00:00", data[0]
19
+ assert_equal "12-oct-2006 05:27:00", data[1]
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ require 'Ingres'
2
+ require 'test/unit'
3
+ require 'ext/tests/config.rb'
4
+
5
+ class TestIngresTypeLOB < Test::Unit::TestCase
6
+ def setup
7
+ @@ing = Ingres.new()
8
+ assert_kind_of(Ingres, @@ing.connect(@@database), "conn is not an Ingres object")
9
+ end
10
+
11
+ def teardown
12
+ @@ing.disconnect
13
+ end
14
+
15
+ def test_blob_fetch
16
+ sql = "select up_image from user_profile where up_id = 1"
17
+ data = @@ing.execute(sql)
18
+ end
19
+
20
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2009 Ingres Corporation
2
+ #
3
+ # Ruby unit tests for the Ingres Ruby driver using the Ruby Test::Unit
4
+ # Framework see,
5
+ # (http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html)
6
+ # for more information
7
+ #
8
+ # Add all new test suites and test cases (if they are not part of a test suite)
9
+ # to this file
10
+ #
11
+ # To execute the tests:
12
+ # ruby tests/ts_all_rb
13
+ # To execute an individual suite:
14
+ # ruby tests/ts_connect.rb
15
+ # Or to execute an individual test case:
16
+ # ruby tests/tc_connect.rb
17
+ #
18
+ # Naming conventions for test files
19
+ # test suites to be prefixed with ts_
20
+ # test cases to be prefixed with tc_
21
+
22
+ # Test case for local connection
23
+ #
24
+ require 'ext/tests/ts_connect.rb'
25
+ require 'ext/tests/ts_execute.rb'
26
+ require 'ext/tests/ts_types.rb'
27
+ require 'ext/tests/ts_transactions.rb'
28
+ require 'ext/tests/ts_environment.rb'
@@ -0,0 +1,5 @@
1
+ require 'ext/tests/tc_connect.rb'
2
+ require 'ext/tests/tc_connect_with_login_only.rb'
3
+ require 'ext/tests/tc_connect_with_login_password.rb'
4
+ require 'ext/tests/tc_dual_connections.rb'
5
+ require 'ext/tests/tc_connect_hash.rb'
@@ -0,0 +1 @@
1
+ require 'ext/tests/tc_env_date_format.rb'
@@ -0,0 +1 @@
1
+ require 'ext/tests/tc_query_simple.rb'
@@ -0,0 +1,3 @@
1
+ #require 'ext/tests/tc_transactions_commit.rb'
2
+ #require 'ext/tests/tc_transactions_rollback.rb'
3
+ require 'ext/tests/tc_transactions_savepoint.rb'
@@ -0,0 +1,2 @@
1
+ require 'ext/tests/tc_type_lob_fetch.rb'
2
+ require 'ext/tests/tc_type_date_fetch.rb'
@@ -0,0 +1,786 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_record/connection_adapters/statement_pool'
4
+ require 'arel/visitors/bind_visitor'
5
+
6
+ module ActiveRecord
7
+ class Base
8
+ # Establishes a connection to the database that's used by all Active Record objects
9
+ def self.ingres_connection(config) # :nodoc:
10
+ require 'Ingres' unless self.class.const_defined?(:Ingres)
11
+
12
+ config = config.symbolize_keys
13
+ host = config[:host]
14
+ port = config[:port] || 21064
15
+ username = config[:username].to_s if config[:username]
16
+ password = config[:password].to_s if config[:password]
17
+
18
+ if config.key?(:database)
19
+ database = config[:database]
20
+ else
21
+ raise ArgumentError, "No database specified. Missing argument: database."
22
+ end
23
+
24
+ #return the connection adapter
25
+ ConnectionAdapters::IngresAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
26
+ end
27
+ end
28
+
29
+ module ConnectionAdapters
30
+ class IngresColumn < Column #:nodoc:
31
+ def initialize(name, default, sql_type = nil, null = true, default_function = nil)
32
+ super(name, default, sql_type, null)
33
+
34
+ @default_function = default_function
35
+ end
36
+
37
+ private
38
+
39
+ def extract_limit(sql_type)
40
+ case sql_type
41
+ when /^bigint/i; 8
42
+ when /^smallint/i; 2
43
+ else super
44
+ end
45
+ end
46
+
47
+ def simplified_type(field_type)
48
+ case field_type
49
+ when /INTEGER/
50
+ :integer
51
+ when /FLOAT/
52
+ :float
53
+ when /DECIMAL/
54
+ extract_scale(field_type) == 0 ? :integer : :decimal
55
+ when /MONEY/
56
+ :decimal
57
+ when /BOOLEAN/
58
+ :boolean
59
+ when /VARCHAR/
60
+ :string
61
+ when /CHAR/
62
+ :string
63
+ when /ANSIDATE/
64
+ :date
65
+ when /DATE|INGRESDATE/
66
+ :datetime
67
+ when /TIMESTAMP/
68
+ :timestamp
69
+ when /TIME/
70
+ :time
71
+ when /INTERVAL/ # No equivalent in Rails
72
+ :string
73
+ else
74
+ super
75
+ end
76
+ end
77
+ end
78
+
79
+
80
+ # This Ingres adapter works with the Ruby/Ingres driver written
81
+ # by Jared Richardson
82
+ #
83
+ # Options:
84
+ #
85
+ # * <tt>:username</tt> - Optional-Defaults to nothing
86
+ # * <tt>:password</tt> - Optional-Defaults to nothing
87
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
88
+ #
89
+ # Author: jared@jaredrichardson.net
90
+ # Maintainer: bruce.lunsford@ingres.com
91
+ # Maintainer: grant.croker@ingres.com
92
+ #
93
+ class IngresAdapter < AbstractAdapter
94
+
95
+ ADAPTER_NAME = 'Ingres'.freeze
96
+
97
+ NATIVE_DATABASE_TYPES = {
98
+ :primary_key => "integer NOT NULL PRIMARY KEY",
99
+ :string => { :name => "varchar", :limit => 255 },
100
+ :text => { :name => "varchar", :limit => 32000 },
101
+ :integer => { :name => "integer" },
102
+ :float => { :name => "float" },
103
+ :datetime => { :name => "ingresdate" },
104
+ :timestamp => { :name => "timestamp" },
105
+ :time => { :name => "time" },
106
+ :date => { :name => "ansidate" }, #"date" pre-ansidate
107
+ :binary => { :name => "blob" },
108
+ :boolean => { :name => "tinyint" },
109
+ :decimal => { :name => "decimal" }
110
+ }.freeze
111
+
112
+ PARAMETERS_TYPES = {
113
+ "byte" => "b",
114
+ "long_byte" => "B",
115
+ "char" => "c",
116
+ "date" => "d",
117
+ "decimal" => "D",
118
+ "float" => "f",
119
+ "integer" => "i",
120
+ "nchar" => "n",
121
+ "nvarchar" => "N",
122
+ "text" => "t",
123
+ "long_text" => "T",
124
+ "varchar" => "v",
125
+ "long_varchar" => "V",
126
+ }.freeze
127
+
128
+ class StatementPool < ConnectionAdapters::StatementPool
129
+ def initialize(connection, max = 1000)
130
+ super
131
+ @cache = Hash.new { |h,pid| h[pid] = {} }
132
+ end
133
+
134
+ def each(&block); cache.each(&block); end
135
+ def key?(key); cache.key?(key); end
136
+ def [](key); cache[key]; end
137
+ def length; cache.length; end
138
+ def delete(key); cache.delete(key); end
139
+
140
+ def []=(sql, key)
141
+ while @max < cache.size
142
+ cache.shift.last[:stmt].close
143
+ end
144
+ cache[sql] = key
145
+ end
146
+
147
+ def clear
148
+ cache.values.each do |hash|
149
+ hash[:stmt].close
150
+ end
151
+ cache.clear
152
+ end
153
+
154
+ private
155
+ def cache
156
+ @cache[$$]
157
+ end
158
+ end
159
+
160
+ class BindSubstitution < Arel::Visitors::Ingres # :nodoc:
161
+ include Arel::Visitors::BindVisitor
162
+ end
163
+
164
+ def initialize(connection, logger, connection_parameters, config)
165
+ super(connection, logger)
166
+
167
+ if config.fetch(:prepared_statements) { true }
168
+ @visitor = Arel::Visitors::Ingres.new self
169
+ else
170
+ @visitor = BindSubstitution.new self
171
+ end
172
+
173
+ @connection_parameters, @config = connection_parameters, config
174
+
175
+ connect
176
+ @statements = StatementPool.new(@connection,
177
+ config.fetch(:statement_limit) { 1000 })
178
+ end
179
+
180
+ def adapter_name
181
+ ADAPTER_NAME
182
+ end
183
+
184
+ # Can this adapter determine the primary key for tables not attached
185
+ # to an ActiveRecord class, such as join tables? Backend specific, as
186
+ # the abstract adapter always returns +false+.
187
+ def supports_primary_key?
188
+ true
189
+ end
190
+
191
+ # Does this adapter support DDL rollbacks in transactions? That is, would
192
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction?
193
+ def supports_ddl_transactions?
194
+ true
195
+ end
196
+
197
+ # Does this adapter support savepoints?
198
+ # We do but there is no delete/release savepoint feature which
199
+ # is needed by ActiveRecord
200
+ def supports_savepoints?
201
+ false
202
+ end
203
+
204
+ def supports_migrations?
205
+ true
206
+ end
207
+
208
+ # Should primary key values be selected from their corresponding
209
+ # sequence before the insert statement? If true, next_sequence_value
210
+ # is called before each insert to set the record's primary key.
211
+ def prefetch_primary_key?(table_name = nil)
212
+ true
213
+ end
214
+
215
+ # CONNECTION MANAGEMENT ====================================
216
+
217
+ def active?
218
+ @connection.exec 'SELECT 1'
219
+ true
220
+ rescue Exception
221
+ false
222
+ end
223
+
224
+ def reconnect!
225
+ disconnect!
226
+ clear_cache!
227
+ connect
228
+ end
229
+
230
+ def reset!
231
+ clear_cache!
232
+ super
233
+ end
234
+
235
+ def disconnect!
236
+ @connection.disconnect rescue nil
237
+ end
238
+
239
+ def native_database_types
240
+ NATIVE_DATABASE_TYPES
241
+ end
242
+
243
+ # HELPER METHODS ===========================================
244
+
245
+ def new_column(column_name, default, type)
246
+ IngresColumn.new(column_name, default, type)
247
+ end
248
+
249
+ # QUOTING ==================================================
250
+
251
+ def force_numeric?(column)
252
+ (column.nil? || [:integer, :float, :decimal].include?(column.type))
253
+ end
254
+
255
+ def quote(value, column = nil)
256
+ case value
257
+ when String
258
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
259
+ "'#{@connection.insert_binary(value)}'"
260
+ else
261
+ "'#{quote_string(value)}'"
262
+ end
263
+ when TrueClass then '1'
264
+ when FalseClass then '0'
265
+ when Fixnum, Bignum then force_numeric?(column) ? value.to_s : "'#{value.to_s}'"
266
+ when NilClass then "NULL"
267
+ when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
268
+ else super
269
+ end
270
+
271
+ end
272
+
273
+ # Quotes a string, escaping any ' (single quote) and \ (backslash)
274
+ # characters.
275
+ def quote_string(s)
276
+ s.gsub(/'/, "''") # ' (for ruby-mode)
277
+ end
278
+
279
+ # Quotes column names for use in SQL queries.
280
+ def quote_column_name(name) #:nodoc:
281
+ %("#{name}")
282
+ end
283
+
284
+ # DATABASE STATEMENTS ======================================
285
+
286
+ def clear_cache!
287
+ @statements.clear
288
+ end
289
+
290
+ def exec_query(sql, name = 'SQL', binds = [])
291
+ log(sql, name, binds) do
292
+ #TODO Aiming to do prepared statements but we'll have to do changes to the C API
293
+ #result = binds.empty? ? exec_no_cache(sql, binds) :
294
+ # exec_cache(sql, binds)
295
+ result = binds.empty? ? @connection.execute(sql) :
296
+ @connection.execute(sql, *binds.map { |bind| [PARAMETERS_TYPES[bind[0].sql_type.downcase], bind[1]] }.flatten)
297
+
298
+ if @connection.rows_affected
299
+ ActiveRecord::Result.new(@connection.column_list_of_names, result)
300
+ else
301
+ ActiveRecord::Result.new([], [])
302
+ end
303
+ end
304
+ end
305
+
306
+ def exec_delete(sql, name = 'SQL', binds = [])
307
+ exec_query(sql, name, binds)
308
+ @connection.rows_affected
309
+ end
310
+ alias :exec_update :exec_delete
311
+
312
+ # Get the last generate identity/auto_increment/sequence number generated
313
+ # for a given table
314
+ def last_inserted_id(table)
315
+ r = exec_query("SELECT max(#{primary_key(table)}) FROM #{table}")
316
+ Integer(r.first.first)
317
+ end
318
+
319
+ def execute(sql, name = nil)
320
+ log(sql, name) do
321
+ @connection.execute(sql)
322
+ end
323
+ end
324
+
325
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
326
+ super
327
+ id_value
328
+ end
329
+ alias :create :insert_sql
330
+
331
+ def select_rows(sql, name = nil)
332
+ execute(sql, name).to_a
333
+ end
334
+
335
+ def get_data_size(id)
336
+ column_names = @connection.column_list_of_names
337
+ index = column_names.index(id)
338
+
339
+ if(index) then
340
+ data_sizes = @connection.data_sizes
341
+ res = data_sizes.at(index)
342
+ else
343
+ res = 0
344
+ end
345
+ res
346
+ end
347
+
348
+ def next_sequence_value(sequence_name)
349
+
350
+ ary = sequence_name.split(' ')
351
+
352
+ if use_table_sequence? then
353
+ next_value = next_identity_for_table(ary[0])
354
+ else
355
+ if (!ary[1]) then
356
+ ary[0] =~ /(\w+)_nonstd_seq/
357
+ ary[0] = $1
358
+ if( ary[1]== nil) then
359
+ last_identity = last_identity_for_table($1)
360
+ end
361
+ else
362
+ last_identity = last_identity_for_table(ary[0])
363
+ end
364
+ if last_identity == "NULL"
365
+ next_value = 1
366
+ else
367
+ next_value = last_identity + 1
368
+ end
369
+ end
370
+ next_value
371
+ end
372
+
373
+ # Should we use sequence defined in the default value
374
+ def use_table_sequence?
375
+ if @config.has_key? :use_sequence_for_identity then
376
+ return @config[:use_sequence_for_identity]
377
+ else
378
+ return FALSE
379
+ end
380
+ end
381
+
382
+ def execute_without_adding_ordering(sql, name=nil)
383
+ # TODO: clean up this hack
384
+ sql.gsub!(" = NULL", " is NULL")
385
+ sql.gsub!(" IN (NULL)", " is NULL")
386
+
387
+ begin
388
+ rs = @connection.execute(sql)
389
+ rescue
390
+ puts "\nAn error occurred!\n"
391
+ end
392
+ col_names = @connection.column_list_of_names
393
+ data_type = @connection.data_types
394
+
395
+ rows=[]
396
+ rs.each do |row|
397
+ index = 0
398
+ this_column = Hash.new
399
+ row.each do |item |
400
+
401
+ col_name = col_names[index].rstrip
402
+ col_val = item.to_s.rstrip
403
+
404
+ if (col_val=="NULL") then
405
+ col_val = nil
406
+ end
407
+ this_column[ col_name] = col_val
408
+ index += 1
409
+ end
410
+ rows << this_column
411
+ index = 0
412
+ end
413
+ if(@offset) then
414
+ rows = apply_limit_and_offset!(rows)
415
+ end
416
+
417
+ return rows
418
+ end
419
+
420
+ def begin_db_transaction #:nodoc:
421
+ execute "START TRANSACTION"
422
+ rescue Exception
423
+ # Transactions aren't supported
424
+ end
425
+
426
+ def commit_db_transaction #:nodoc:
427
+ execute "COMMIT"
428
+ rescue Exception
429
+ # Transactions aren't supported
430
+ end
431
+
432
+ def rollback_db_transaction #:nodoc:
433
+ execute "ROLLBACK"
434
+ rescue Exception
435
+ # Transactions aren't supported
436
+ end
437
+
438
+ # SCHEMA STATEMENTS ========================================
439
+
440
+ # Create an Ingres database
441
+ def create_database(name, options = {})
442
+ #`createdb #{name} -i -fnofeclients`
443
+ end
444
+
445
+ # Drops an Ingres database
446
+ def drop_database(name) #:nodoc:
447
+ #`destroydb #{name}`
448
+ end
449
+
450
+ # Return the list of all tables in the schema search path.
451
+ def tables(name = nil) #:nodoc:
452
+ tables = @connection.tables.flatten
453
+ tables
454
+ end
455
+
456
+ def indexes(table_name, name = nil)#:nodoc:
457
+ @primary_key = nil
458
+ sql = "SELECT base_name, index_name, unique_rule "
459
+ sql << "FROM "
460
+ sql << "iiindexes "
461
+ sql << "WHERE "
462
+ sql << "base_name = '#{table_name}' "
463
+
464
+ indexes = []
465
+ result_set = execute_without_adding_ordering(sql, name)
466
+
467
+ if(result_set) then
468
+ result_set.each do | column |
469
+ base_name = column.values_at("base_name")[0]
470
+ index_name = column.values_at("index_name")[0]
471
+ unique_rule =column.values_at("unique_rule")[0]=='U'
472
+ column_name = column.values_at("column_name")[0]
473
+
474
+ # get a copy of the primary index
475
+ # Ingres uses the first 5 characters of a table name for the primary key index
476
+ if table_name.length < 5 then
477
+ short_name = table_name
478
+ else
479
+ short_name = table_name[0..4]
480
+ end
481
+ if (index_name.starts_with?("$#{short_name}")) then
482
+ # we have the name of the pointer to the index
483
+ # now let's fetch the actual name of the primary key
484
+ sql2="select column_name from iiindex_columns where index_name='#{index_name}'"
485
+ result_set_2 = execute_without_adding_ordering(sql2)
486
+ @primary_key = result_set_2[0].fetch('column_name')
487
+ end
488
+
489
+ # ignore any index with a $pk or ii prefix. It's a system index
490
+ prefix1 = "$#{short_name}"
491
+ prefix2 = "ii"
492
+ if ( (index_name.starts_with?(prefix1)) || (index_name.starts_with?(prefix2)) ) then
493
+ #puts "\nSkipping #{index_name}\n"
494
+ else
495
+ #puts "\nAdding >>#{index_name}<<\n"
496
+
497
+ # now fetch the column names and store them in an Array
498
+ column_names = []
499
+ sql = "SELECT column_name from iiindex_columns "
500
+ sql << " WHERE index_name = '#{index_name}' "
501
+ rs2 = execute_without_adding_ordering(sql, name)
502
+ rs2.each do | col2 |
503
+ this_col_name = col2.values_at("column_name").first
504
+ column_names << this_col_name
505
+ end
506
+ # class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
507
+ indexes << IndexDefinition.new( table_name, index_name, unique_rule, column_names)
508
+ end
509
+ end
510
+ end
511
+ indexes
512
+ end
513
+
514
+ def columns(table_name, name = nil) #:nodoc:
515
+ column_definitions(table_name).collect do |row|
516
+ new_column(row["column_name"], row["column_default_val"], row["column_datatype"])
517
+ end
518
+ end
519
+
520
+ # Returns just a table's primary key
521
+ def primary_key(table)
522
+ row = @connection.execute(<<-end_sql).first
523
+ SELECT column_name
524
+ FROM iicolumns, iiconstraints
525
+ WHERE iiconstraints.table_name = '#{table}'
526
+ AND iiconstraints.constraint_name = iicolumns.table_name
527
+ AND iiconstraints.constraint_type = 'P'
528
+ AND iicolumns.column_name != 'tidp'
529
+ ORDER BY iicolumns.column_sequence
530
+ end_sql
531
+
532
+ row && row.first
533
+ end
534
+
535
+ def remove_index!(table_name, index_name)
536
+ execute "DROP INDEX #{quote_table_name(index_name)}"
537
+ end
538
+
539
+ # Ingres 9.1.x and earlier require 'COLNAME TYPE DEFAULT NULL WITH NULL' as part of the column definition
540
+ # for CREATE TABLE
541
+ # TODO : add a server version check so as to only do this for Ingres 9.1.x and earlier.
542
+ def add_column_options!(sql, options) #:nodoc:
543
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
544
+ # must explcitly check for :null to allow change_column to work on migrations
545
+ if options.has_key? :null
546
+ if options[:null] == false
547
+ sql << " NOT NULL"
548
+ else
549
+ sql << " WITH NULL"
550
+ end
551
+ end
552
+ end
553
+
554
+ def add_column(table_name, column_name, type, options = {})
555
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type)}"
556
+ if( (type.to_s=="string") && (options[:limit]!=nil) ) then
557
+ add_column_sql.gsub!("varchar(255)", "varchar(#{options[:limit]})")
558
+ # puts ("\ntype.to_s.class is #{type.to_s.class}")
559
+ end
560
+ execute(add_column_sql)
561
+ end
562
+
563
+ # Ingres does not support ALTER TABLE RENAME COL so we have to do it another way
564
+ #
565
+ # !!!!Note!!!!
566
+ # This method only handles tables and primary keys as generated by ActiveRecord
567
+ # If someone has modified the table structure or added additional indexes these
568
+ # will be lost.
569
+ # TODO - handle secondary indexes and alternate base table structures
570
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
571
+ table_columns = columns(table_name)
572
+ #Strip the leading :
573
+ old_column_name = "#{column_name}"
574
+ no_table_columns = table_columns.size
575
+ count = 0
576
+ column_sql = ""
577
+ table_columns.each do |col|
578
+ count = count + 1
579
+ if col.name == old_column_name then
580
+ column_sql << " #{col.name} as #{new_column_name}"
581
+ else
582
+ column_sql << " #{col.name}"
583
+ end
584
+ if (count < no_table_columns) then
585
+ column_sql << ", "
586
+ end
587
+ end
588
+ pk_col = primary_key(table_name)
589
+ # Backup the current table renaming the chosen column
590
+ execute( <<-SQL_COPY, nil)
591
+ DECLARE GLOBAL TEMPORARY TABLE session.table_copy AS
592
+ SELECT #{column_sql}
593
+ FROM #{table_name}
594
+ ON COMMIT PRESERVE ROWS
595
+ WITH NORECOVERY
596
+ SQL_COPY
597
+
598
+ #Drop table_name
599
+ execute( <<-SQL_COPY, nil)
600
+ DROP TABLE #{table_name}
601
+ SQL_COPY
602
+
603
+ #Re-create table_name based on session.table_copy
604
+ execute( <<-SQL_COPY, nil)
605
+ CREATE TABLE #{table_name} AS
606
+ SELECT * FROM session.table_copy
607
+ SQL_COPY
608
+
609
+ #Add the Primary key back
610
+ execute( <<-SQL_COPY, nil)
611
+ ALTER TABLE #{table_name} ADD CONSTRAINT #{table_name[0..4]}_#{pk_col}_pk PRIMARY KEY (#{pk_col})
612
+ SQL_COPY
613
+
614
+ #Drop the GTT session.table_copy
615
+ execute( <<-SQL_COPY, nil)
616
+ DROP TABLE session.table_copy
617
+ SQL_COPY
618
+ end
619
+
620
+ # Ingres supports a different SQL syntax for column type definitions
621
+ # For example with other vendor DBMS engines it's possible to specify the
622
+ # number of bytes in an integer column - e.g. INTEGER(3)
623
+ #
624
+ # In addition it would appear that the following syntax is not valid for Ingres
625
+ # colname DECIMAL DEFAULT xx.yy
626
+ # where as
627
+ # colname DECIMAL
628
+ # is valid. It would appear we need to supply the precision and scale when providing
629
+ # a default value.
630
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
631
+ case type.to_sym
632
+ when :integer;
633
+ case limit
634
+ when 1..2; return 'smallint'
635
+ when 3..4, nil; return 'integer'
636
+ when 5..8; return 'bigint'
637
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
638
+ end
639
+ when :boolean; return 'tinyint'
640
+ when :decimal;
641
+ if precision.nil? then
642
+ # Ingres requires the precision/scale be defined
643
+ return 'decimal (39,15)'
644
+ else
645
+ return "decimal (#{precision},#{scale})"
646
+ end
647
+ else
648
+ return super
649
+ end
650
+ end
651
+
652
+ private
653
+
654
+ #def exec_no_cache(sql, binds)
655
+ # @connection.execute(sql)
656
+ #end
657
+
658
+ #def exec_cache(sql, binds)
659
+ # begin
660
+ # stmt_key = prepare_statement sql
661
+ # end
662
+ #end
663
+
664
+ def connect
665
+ @connection = Ingres.new
666
+
667
+ @connection.connect({
668
+ :host => @connection_parameters[0],
669
+ :port => @connection_parameters[1],
670
+ :database => @connection_parameters[4],
671
+ :username => @connection_parameters[5],
672
+ :password => @connection_parameters[6],
673
+ :date_format => Ingres::DATE_FORMAT_FINLAND
674
+ })
675
+
676
+ configure_connection
677
+ end
678
+
679
+ def configure_connection
680
+ # Dummy function to execute further configuration on connection
681
+ end
682
+
683
+ def select(sql, name = nil, binds = [])
684
+ exec_query(sql, name, binds).to_a
685
+ end
686
+
687
+ def column_definitions(table_name)
688
+ exec_query(<<-end_sql, 'SCHEMA')
689
+ SELECT column_name, column_datatype, column_default_val
690
+ FROM iicolumns
691
+ WHERE table_name='#{table_name}'
692
+ ORDER BY column_sequence
693
+ end_sql
694
+ end
695
+
696
+ def default_value(value)
697
+ # Empty strings should be set to null
698
+ return nil if value == nil
699
+ return nil if value.empty?
700
+
701
+ # Boolean type values
702
+ return true if value =~ /true/
703
+ return false if value =~ /false/
704
+
705
+ # Date / Time magic values
706
+ # return Time.now.to_s if value =~ /^now\(\)/i
707
+
708
+ # Otherwise return what we got from the database
709
+ # and hope for the best..
710
+ return value
711
+ end
712
+
713
+ def next_identity_for_table(table)
714
+ if table != nil then
715
+ identity_col = primary_key(table)
716
+ if identity_col != nil then
717
+ sequence_name = table_sequence_name(table,identity_col)
718
+ if sequence_name != nil
719
+ sql = "SELECT #{sequence_name}.nextval"
720
+ next_identity = @connection.execute(sql)[0][0]
721
+ # Test for a value which is <= the max value already there
722
+ # to avoid possible duplicate key values
723
+ sql = "SELECT max(#{identity_col}) from #{table}"
724
+ max_id = @connection.execute(sql)[0][0]
725
+ max_id = 0 if max_id == "NULL"
726
+ until next_identity > max_id
727
+ sql = "SELECT #{sequence_name}.nextval"
728
+ next_identity = @connection.execute(sql)[0][0]
729
+ end
730
+ @identity_value = next_identity
731
+ else
732
+ next_identity = last_identity_for_table(table) + 1
733
+ end
734
+ else
735
+ next_identity = 1
736
+ end
737
+ else
738
+ next_identity = 1
739
+ end
740
+ next_identity
741
+ end
742
+
743
+ # Get the last generate identity/auto_increment/sequence number generated
744
+ # for a given insert statement
745
+ def last_identity_for_insert(sql)
746
+ puts "In last_identity_for_insert()"
747
+ sql =~ /INSERT INTO "(\w+)" .*/m
748
+ last_identity_for_table($1)
749
+ end
750
+
751
+ # Get the sequence name used in the table
752
+ def table_sequence_name(table, column)
753
+ sql = "SELECT column_default_val "
754
+ sql << "FROM iicolumns "
755
+ sql << "WHERE table_name = '#{table}' "
756
+ sql << " AND column_name = '#{column}'"
757
+ default = @connection.execute(sql)[0][0]
758
+ default =~ /next value for "(\w+)"\."(\w+)"/m
759
+ sequence_name = $2
760
+ end
761
+
762
+ def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
763
+ # TODO: is this okay? :(
764
+
765
+ if(1) then return end
766
+ puts "\n In update_nulls_after_insert\n"
767
+
768
+ sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
769
+ table = $1
770
+ cols = $2
771
+ values = $3
772
+ cols = cols.split(',')
773
+ values.gsub!(/'[^']*'/,"''")
774
+ values.gsub!(/"[^"]*"/,"\"\"")
775
+ values = values.split(',')
776
+ update_cols = []
777
+ values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
778
+ update_sql = "UPDATE #{table} SET"
779
+ update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
780
+ update_sql.chop!()
781
+ update_sql << " WHERE #{pk}=#{quote(id_value)}"
782
+ execute(update_sql, name + " NULL Correction") if update_cols.size > 0
783
+ end
784
+ end
785
+ end
786
+ end