activerecord-ingres-adapter 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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