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.
- checksums.yaml +7 -0
- data/CHANGELOG +69 -0
- data/Gemfile +7 -0
- data/LICENSE +258 -0
- data/README.md +69 -0
- data/Rakefile +0 -0
- data/activerecord_ingres_adapter.gemspec +19 -0
- data/ext/Ingres.c +3889 -0
- data/ext/Ingres.h +318 -0
- data/ext/Unicode.c +219 -0
- data/ext/Unicode.h +56 -0
- data/ext/createMake.bat +1 -0
- data/ext/extconf.rb +85 -0
- data/ext/tests/config.rb +11 -0
- data/ext/tests/tc_connect.rb +12 -0
- data/ext/tests/tc_connect_hash.rb +31 -0
- data/ext/tests/tc_connect_with_login_only.rb +14 -0
- data/ext/tests/tc_connect_with_login_password.rb +12 -0
- data/ext/tests/tc_dual_connections.rb +14 -0
- data/ext/tests/tc_env_date_format.rb +47 -0
- data/ext/tests/tc_query_simple.rb +29 -0
- data/ext/tests/tc_query_username.rb +22 -0
- data/ext/tests/tc_transactions_savepoint.rb +109 -0
- data/ext/tests/tc_type_date_fetch.rb +22 -0
- data/ext/tests/tc_type_lob_fetch.rb +20 -0
- data/ext/tests/ts_all.rb +28 -0
- data/ext/tests/ts_connect.rb +5 -0
- data/ext/tests/ts_environment.rb +1 -0
- data/ext/tests/ts_execute.rb +1 -0
- data/ext/tests/ts_transactions.rb +3 -0
- data/ext/tests/ts_types.rb +2 -0
- data/lib/active_record/connection_adapters/ingres_adapter.rb +786 -0
- data/lib/activerecord-ingres-adapter.rb +2 -0
- data/lib/arel/visitors/ingres.rb +30 -0
- metadata +80 -0
@@ -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
|
data/ext/tests/ts_all.rb
ADDED
@@ -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 @@
|
|
1
|
+
require 'ext/tests/tc_env_date_format.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'ext/tests/tc_query_simple.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
|