activerecord-advantage-adapter 0.1.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.
- data/LICENSE +20 -0
- data/README +66 -0
- data/lib/active_record/connection_adapters/advantage_adapter.rb +531 -0
- data/lib/arel/visitors/advantage.rb +39 -0
- data/test/connection.rb +23 -0
- metadata +80 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
/*====================================================
|
2
|
+
*
|
3
|
+
* Copyright 2008-2010 iAnywhere Solutions, Inc.
|
4
|
+
*
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
* you may not use this file except in compliance with the License.
|
7
|
+
* You may obtain a copy of the License at
|
8
|
+
*
|
9
|
+
*
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
*
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
*
|
16
|
+
* See the License for the specific language governing permissions and
|
17
|
+
* limitations under the License.
|
18
|
+
*
|
19
|
+
*
|
20
|
+
*====================================================*/
|
data/README
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
=Advantage ActiveRecord Driver
|
2
|
+
|
3
|
+
This is a Advantage driver for Ruby ActiveRecord. This driver requires the
|
4
|
+
native Advantage Ruby driver. To get the native driver, use:
|
5
|
+
|
6
|
+
gem install advantage
|
7
|
+
|
8
|
+
This driver is designed for use with ActiveRecord 3.2.0 and greater.
|
9
|
+
|
10
|
+
This driver is licensed under the Apache License, Version 2.
|
11
|
+
|
12
|
+
==Making a Connection
|
13
|
+
|
14
|
+
The following code is a sample database configuration object.
|
15
|
+
|
16
|
+
ActiveRecord::Base.configurations = {
|
17
|
+
'arunit' => {
|
18
|
+
:adapter => 'advantage',
|
19
|
+
:database => 'c:\test\arunit.add', #equivalent to the "Data Source" parameter
|
20
|
+
:username => 'adssys', #equivalent to the "UserID" parameter
|
21
|
+
:password => '' #equivalent to the "Password" parameter
|
22
|
+
}
|
23
|
+
|
24
|
+
==Creating a new project. The following is based on the tutorial at http://edgeguides.rubyonrails.org/getting_started.html
|
25
|
+
|
26
|
+
1. Create the application:
|
27
|
+
rails new blog
|
28
|
+
|
29
|
+
2. Switch into the new application folder
|
30
|
+
cd blog
|
31
|
+
|
32
|
+
3. Create three databases. This can be done via ARC using SQL
|
33
|
+
CREATE DATABASE "c:\blog\dbprod\blog_production.add";
|
34
|
+
CREATE DATABASE "c:\blog\dbtest\blog_test.add";
|
35
|
+
CREATE DATABASE "c:\blog\dbdev\blog_dev.add";
|
36
|
+
|
37
|
+
4. Edit the file GemFile and add the activerecord-advantage-adapter
|
38
|
+
gem 'activerecord-advantage-adapter'
|
39
|
+
|
40
|
+
5. Edit the config/database.yml file to match the following
|
41
|
+
|
42
|
+
development:
|
43
|
+
adapter: advantage
|
44
|
+
database: c:/blog/dbdev/blog_dev.add
|
45
|
+
username: adssys
|
46
|
+
password:
|
47
|
+
|
48
|
+
# Warning: The database defined as "test" will be erased and
|
49
|
+
# re-generated from your development database when you run "rake".
|
50
|
+
# Do not set this db to the same as development or production.
|
51
|
+
test:
|
52
|
+
adapter: advantage
|
53
|
+
database: c:/blog/dbtest/blog_test.add
|
54
|
+
username: adssys
|
55
|
+
password:
|
56
|
+
|
57
|
+
production:
|
58
|
+
adapter: advantage
|
59
|
+
database: c:/blog/dbprod/blog_production.add
|
60
|
+
username: adssys
|
61
|
+
password:
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
|
@@ -0,0 +1,531 @@
|
|
1
|
+
#====================================================
|
2
|
+
#
|
3
|
+
# Copyright 2008-2010 iAnywhere Solutions, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
#
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
#
|
21
|
+
#====================================================
|
22
|
+
|
23
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
24
|
+
require 'arel/visitors/advantage.rb'
|
25
|
+
|
26
|
+
# Singleton class to hold a valid instance of the AdvantageInterface across all connections
|
27
|
+
class ADS
|
28
|
+
include Singleton
|
29
|
+
attr_accessor :api
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
require 'advantage' unless defined? Advantage
|
33
|
+
@api = Advantage::AdvantageInterface.new()
|
34
|
+
raise LoadError, "Could not load ACE library" if Advantage::API.ads_initialize_interface(@api) == 0
|
35
|
+
raise LoadError, "Could not initialize ACE library" if @api.ads_init() == 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ActiveRecord
|
40
|
+
class Base
|
41
|
+
DEFAULT_CONFIG = { :username => 'adssys', :password => nil }
|
42
|
+
# Main connection function to Advantage
|
43
|
+
# Connection Adapter takes four parameters:
|
44
|
+
# * :database (required, no default). Corresponds to "Data Source=" in connection string
|
45
|
+
# * :username (optional, default to 'adssys'). Correspons to "User ID=" in connection string
|
46
|
+
# * :password (optional, deafult to '')
|
47
|
+
# * :options (optional, defaults to ''). Corresponds to any additional options in connection string
|
48
|
+
|
49
|
+
def self.advantage_connection(config)
|
50
|
+
|
51
|
+
config = DEFAULT_CONFIG.merge(config)
|
52
|
+
|
53
|
+
raise ArgumentError, "No data source was given. Please add a :database option." unless config.has_key?(:database)
|
54
|
+
|
55
|
+
connection_string = "data source=#{config[:database]};User ID=#{config[:username]};"
|
56
|
+
connection_string += "Password=#{config[:password]};" unless config[:options].nil?
|
57
|
+
connection_string += "#{config[:options]};" unless config[:options].nil?
|
58
|
+
|
59
|
+
db = ADS.instance.api.ads_new_connection()
|
60
|
+
|
61
|
+
ConnectionAdapters::AdvantageAdapter.new(db, logger, connection_string)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module ConnectionAdapters
|
66
|
+
class AdvantageException < StandardError
|
67
|
+
attr_reader :errno
|
68
|
+
attr_reader :sql
|
69
|
+
|
70
|
+
def initialize(message, errno, sql)
|
71
|
+
super(message)
|
72
|
+
@errno = errno
|
73
|
+
@sql = sql
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class AdvantageColumn < Column
|
78
|
+
private
|
79
|
+
# Overridden to handle Advantage integer, varchar, binary, and timestamp types
|
80
|
+
def simplified_type(field_type)
|
81
|
+
return :boolean if field_type =~ /logical/i
|
82
|
+
return :string if field_type =~ /varchar/i
|
83
|
+
return :binary if field_type =~ /long binary/i
|
84
|
+
return :datetime if field_type =~ /timestamp/i
|
85
|
+
return :integer if field_type =~ /short|integer/i
|
86
|
+
return :integer if field_type =~ /autoinc/i
|
87
|
+
super
|
88
|
+
end
|
89
|
+
|
90
|
+
#EJS Need?
|
91
|
+
=begin
|
92
|
+
def extract_limit(sql_type)
|
93
|
+
case sql_type
|
94
|
+
when /^tinyint/i
|
95
|
+
1
|
96
|
+
when /^smallint/i
|
97
|
+
2
|
98
|
+
when /^integer/i
|
99
|
+
4
|
100
|
+
when /^bigint/i
|
101
|
+
8
|
102
|
+
else super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
=end
|
106
|
+
|
107
|
+
protected
|
108
|
+
end
|
109
|
+
|
110
|
+
class AdvantageAdapter < AbstractAdapter
|
111
|
+
def initialize( connection, logger, connection_string = "") #:nodoc:
|
112
|
+
super(connection, logger)
|
113
|
+
@auto_commit = true
|
114
|
+
@affected_rows = 0
|
115
|
+
@connection_string = connection_string
|
116
|
+
@visitor = Arel::Visitors::Advantage.new self
|
117
|
+
connect!
|
118
|
+
end
|
119
|
+
|
120
|
+
def adapter_name #:nodoc:
|
121
|
+
'Advantage'
|
122
|
+
end
|
123
|
+
|
124
|
+
def supports_migrations? #:nodoc:
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
def requires_reloading? #:nodoc:
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def active? #:nodoc:
|
133
|
+
ADS.instance.api.ads_execute_immediate(@connection, "SELECT 1 FROM SYSTEM.IOTA") == 1
|
134
|
+
rescue
|
135
|
+
false
|
136
|
+
end
|
137
|
+
|
138
|
+
def disconnect! #:nodoc:
|
139
|
+
result = ADS.instance.api.ads_disconnect( @connection )
|
140
|
+
super
|
141
|
+
end
|
142
|
+
|
143
|
+
def reconnect! #:nodoc:
|
144
|
+
disconnect!
|
145
|
+
connect!
|
146
|
+
end
|
147
|
+
|
148
|
+
def supports_count_distinct? #:nodoc:
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
def supports_autoincrement? #:nodoc:
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
# Used from StackOverflow question 1000688
|
157
|
+
# Stip alone will return NIL if the string is not altered. In that case,
|
158
|
+
# still return the string.
|
159
|
+
def strip_or_self(str) #:nodoc:
|
160
|
+
str.strip! || str if str
|
161
|
+
end
|
162
|
+
|
163
|
+
# Maps native ActiveRecord/Ruby types into ADS types
|
164
|
+
def native_database_types #:nodoc:
|
165
|
+
{
|
166
|
+
:primary_key => 'AUTOINC PRIMARY KEY CONSTRAINT NOT NULL',
|
167
|
+
:string => { :name => "varchar", :limit => 255 },
|
168
|
+
:text => { :name => "memo" },
|
169
|
+
:integer => { :name => "integer" },
|
170
|
+
:float => { :name => "float" },
|
171
|
+
:decimal => { :name => "numeric" },
|
172
|
+
:datetime => { :name => "timestamp" },
|
173
|
+
:timestamp => { :name => "timestamp" },
|
174
|
+
:time => { :name => "time" },
|
175
|
+
:date => { :name => "date" },
|
176
|
+
:binary => { :name => "blob" },
|
177
|
+
:boolean => { :name => "logical"}
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
# Applies quotations around column names in generated queries
|
182
|
+
def quote_column_name(name) #:nodoc:
|
183
|
+
%Q("#{name}")
|
184
|
+
end
|
185
|
+
|
186
|
+
def quote(value, column = nil) #:nodoc:
|
187
|
+
super(value, column)
|
188
|
+
end
|
189
|
+
|
190
|
+
def quoted_true #:nodoc:
|
191
|
+
'1'
|
192
|
+
end
|
193
|
+
|
194
|
+
def quoted_false #:nodoc:
|
195
|
+
'0'
|
196
|
+
end
|
197
|
+
|
198
|
+
# The database execution function
|
199
|
+
def execute(sql, name = nil, binds = []) #:nodoc:
|
200
|
+
if name == :skip_logging
|
201
|
+
query(sql, binds)
|
202
|
+
else
|
203
|
+
log(sql, name, binds) { query(sql, binds) }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Translate the exception if possible
|
208
|
+
def translate_exception(exception, message) #:nodoc:
|
209
|
+
return super unless exception.respond_to?(:errno)
|
210
|
+
case exception.errno
|
211
|
+
when 2121
|
212
|
+
if exception.sql !~ /^SELECT/i then
|
213
|
+
raise ActiveRecord::ActiveRecordError.new(message)
|
214
|
+
else
|
215
|
+
super
|
216
|
+
end
|
217
|
+
when 7076
|
218
|
+
raise InvalidForeignKey.new(message, exception)
|
219
|
+
when 7057
|
220
|
+
raise RecordNotUnique.new(message, exception)
|
221
|
+
else
|
222
|
+
super
|
223
|
+
end
|
224
|
+
super
|
225
|
+
end
|
226
|
+
|
227
|
+
# The database update function.
|
228
|
+
def update_sql(sql, name = nil) #:nodoc:
|
229
|
+
execute( sql, name )
|
230
|
+
return @affected_rows
|
231
|
+
end
|
232
|
+
|
233
|
+
# The database delete function.
|
234
|
+
def delete_sql(sql, name = nil) #:nodoc:
|
235
|
+
execute( sql, name )
|
236
|
+
return @affected_rows
|
237
|
+
end
|
238
|
+
|
239
|
+
# The database insert function.
|
240
|
+
# ActiveRecord requires that insert_sql returns the primary key of the row just inserted. In most cases, this can be accomplished
|
241
|
+
# by immediatly querying the @@identity property. If the @@identity property is 0, then passed id_value is used
|
242
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
243
|
+
execute(sql, name)
|
244
|
+
identity = last_inserted_id(nil)
|
245
|
+
retval = id_value if retval == 0
|
246
|
+
return retval
|
247
|
+
end
|
248
|
+
|
249
|
+
# The Database insert function as part of the rails changes
|
250
|
+
def exec_insert(sql, name = nil, binds = []) #:nodoc:
|
251
|
+
log(sql, "insert", binds) { query(sql, binds) }
|
252
|
+
end
|
253
|
+
|
254
|
+
# The Database update function as part of the rails changes
|
255
|
+
def exec_update(sql, name = nil, binds = []) #:nodoc:
|
256
|
+
log(sql, "update", binds) { query(sql, binds) }
|
257
|
+
end
|
258
|
+
|
259
|
+
# The Database delete function as part of the rails changes
|
260
|
+
def exec_delete(sql, name = nil, binds = []) #:nodoc:
|
261
|
+
log(sql, "delete", binds) { query(sql, binds) }
|
262
|
+
end
|
263
|
+
|
264
|
+
# Retrieve the last AutoInc ID
|
265
|
+
def last_inserted_id(result) #:nodoc:
|
266
|
+
rs = ADS.instance.api.ads_execute_direct(@connection, 'SELECT LASTAUTOINC( CONNECTION ) FROM SYSTEM.IOTA')
|
267
|
+
raise ActiveRecord::StatementInvalid.new("#{ADS.instance.api.ads_error(@connection)}:#{sql}") if rs.nil?
|
268
|
+
ADS.instance.api.ads_fetch_next(rs)
|
269
|
+
retval, identity = ADS.instance.api.ads_get_column(rs, 0)
|
270
|
+
ADS.instance.api.ads_free_stmt(rs)
|
271
|
+
identity
|
272
|
+
end
|
273
|
+
|
274
|
+
# Returns a query as an array of arrays
|
275
|
+
def select_rows(sql, name = nil)
|
276
|
+
rs = ADS.instance.api.ads_execute_direct(@connection, sql)
|
277
|
+
raise ActiveRecord::StatementInvalid.new("#{ADS.instance.api.ads_error(@connection)}:#{sql}") if rs.nil?
|
278
|
+
record = []
|
279
|
+
while ADS.instance.api.ads_fetch_next(rs) == 1
|
280
|
+
max_cols = ADS.instance.api.ads_num_cols(rs)
|
281
|
+
result = Array.new(max_cols)
|
282
|
+
max_cols.times do |cols|
|
283
|
+
result[cols] = ADS.instance.api.ads_get_column(rs, cols)[1]
|
284
|
+
end
|
285
|
+
record << result
|
286
|
+
end
|
287
|
+
ADS.instance.api.ads_free_stmt(rs)
|
288
|
+
return record
|
289
|
+
end
|
290
|
+
|
291
|
+
# Begin a transaction
|
292
|
+
def begin_db_transaction #:nodoc:
|
293
|
+
ADS.instance.api.AdsBeginTransaction(@connection)
|
294
|
+
@auto_commit = false;
|
295
|
+
end
|
296
|
+
|
297
|
+
# Commit the transaction
|
298
|
+
def commit_db_transaction #:nodoc:
|
299
|
+
ADS.instance.api.ads_commit(@connection)
|
300
|
+
@auto_commit = true;
|
301
|
+
end
|
302
|
+
|
303
|
+
# Rollback the transaction
|
304
|
+
def rollback_db_transaction #:nodoc:
|
305
|
+
ADS.instance.api.ads_rollback(@connection)
|
306
|
+
@auto_commit = true;
|
307
|
+
end
|
308
|
+
|
309
|
+
def add_lock!(sql, options) #:nodoc:
|
310
|
+
sql
|
311
|
+
end
|
312
|
+
|
313
|
+
# Advantage does not support sizing of integers based on the sytax INTEGER(size).
|
314
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
315
|
+
if native = native_database_types[type]
|
316
|
+
if type == :integer
|
317
|
+
column_type_sql = 'integer'
|
318
|
+
elsif type == :string and !limit.nil?
|
319
|
+
"varchar (#{limit})"
|
320
|
+
else
|
321
|
+
super(type, limit, precision, scale)
|
322
|
+
end
|
323
|
+
else
|
324
|
+
super(type, limit, precision, scale)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Retrieve a list of Tables
|
329
|
+
def tables(name = nil) #:nodoc:
|
330
|
+
sql = "EXECUTE PROCEDURE sp_GetTables( NULL, NULL, NULL, 'TABLE' );"
|
331
|
+
select(sql, name).map { |row| strip_or_self(row["TABLE_NAME"]) }
|
332
|
+
end
|
333
|
+
|
334
|
+
# Return a list of columns
|
335
|
+
def columns(table_name, name = nil) #:nodoc:
|
336
|
+
table_structure(table_name).map do |field|
|
337
|
+
AdvantageColumn.new(strip_or_self(field['COLUMN_NAME']), field['COLUMN_DEF'], strip_or_self(field['TYPE_NAME']), field['NULLABLE'])
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# Return a list of indexes
|
342
|
+
# EJS - Is there a way to get info without DD?
|
343
|
+
def indexes(table_name, name = nil) #:nodoc:
|
344
|
+
sql = "SELECT name, INDEX_OPTIONS & 1 AS [unique], index_expression FROM SYSTEM.INDEXES WHERE parent = '#{table_name}'"
|
345
|
+
select(sql, name).map do |row|
|
346
|
+
index = IndexDefinition.new(table_name, row['name'])
|
347
|
+
index.unique = row['unique'] == 1
|
348
|
+
index.columns = row['index_expression']
|
349
|
+
index
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Return the primary key
|
354
|
+
def primary_key(table_name) #:nodoc:
|
355
|
+
sql = "SELECT COLUMN_NAME FROM (EXECUTE PROCEDURE sp_GetBestRowIdentifier( NULL, NULL, '#{table_name}', NULL, FALSE)) as gbri"
|
356
|
+
rs = select(sql)
|
357
|
+
if !rs.nil? and !rs[0].nil?
|
358
|
+
strip_or_self(rs[0]['COLUMN_NAME'])
|
359
|
+
else
|
360
|
+
nil
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Drop an index
|
365
|
+
def remove_index(table_name, options={}) #:nodoc:
|
366
|
+
execute "DROP INDEX #{quote_table_name(table_name)}.#{quote_column_name(index_name(table_name, options))}"
|
367
|
+
end
|
368
|
+
|
369
|
+
# Rename a table
|
370
|
+
#EJS - can be done without dd?
|
371
|
+
def rename_table(name, new_name) #:nodoc:
|
372
|
+
execute "EXECUTE PROCEDURE sp_RenameDDObject(#{quote_table_name(name)} , #{quote_table_name(new_name)}, 1 /* ADS_DD_TABLE_OBJECT */, 0 /* Rename File */)"
|
373
|
+
end
|
374
|
+
|
375
|
+
# Helper function to retrieve the columns current type
|
376
|
+
def get_column_type(table_name, column_name) #:nodoc:
|
377
|
+
sql = <<-SQL
|
378
|
+
SELECT
|
379
|
+
CASE
|
380
|
+
WHEN type_name = 'VARCHAR' or type_name = 'CHAR' or type_name = 'CICHAR' or
|
381
|
+
type_name = 'NVARCHAR' or type_name = 'NCHAR' or type_name = 'VARBINARY'
|
382
|
+
THEN CAST(TRIM(type_name) + '(' + TRIM(CAST(column_size AS SQL_CHAR)) + ')' AS SQL_CHAR)
|
383
|
+
WHEN type_name = 'NUMERIC' or type_name = 'DOUBLE' or type_name = 'CURDOUBLE'
|
384
|
+
THEN CAST(TRIM(type_name) + '(' + TRIM(CAST(column_size AS SQL_CHAR)) + ',' + TRIM(CAST(decimal_digits AS SQL_CHAR)) + ')' AS SQL_CHAR)
|
385
|
+
ELSE
|
386
|
+
TRIM(type_name COLLATE ads_default_cs)
|
387
|
+
END AS "domain"
|
388
|
+
from (EXECUTE PROCEDURE sp_GetColumns( NULL, NULL, '#{table_name}', NULL)) as spgc
|
389
|
+
WHERE COLUMN_NAME = '#{column_name}'
|
390
|
+
SQL
|
391
|
+
rs = select(sql)
|
392
|
+
if !rs.nil? and !rs[0].nil?
|
393
|
+
rs[0]['domain']
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Change a columns defaults.
|
398
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
399
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{get_column_type(table_name, column_name)} DEFAULT #{quote(default)}"
|
400
|
+
end
|
401
|
+
|
402
|
+
# Change a columns nullability
|
403
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
404
|
+
unless null || default.nil?
|
405
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
406
|
+
end
|
407
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{get_column_type(table_name, column_name)} CONSTRAINT #{null ? '' : 'NOT'} NULL")
|
408
|
+
end
|
409
|
+
|
410
|
+
# Alter a column
|
411
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
412
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, type_options[:limit], type_options[:precision], type_options[:scale])}"
|
413
|
+
add_column_options!(add_column_sql, options)
|
414
|
+
execute(add_column_sql)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Add column options
|
418
|
+
def add_column_options!(sql, options) #:nodoc:
|
419
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
|
420
|
+
# must explicitly check for :null to allow change_column to work on migrations
|
421
|
+
if options[:null] == false
|
422
|
+
sql << " CONSTRAINT NOT NULL"
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Rename a column
|
427
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
428
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{type_to_sql(type, type_options[:limit], type_options[:precision], type_options[:scale])}"
|
429
|
+
end
|
430
|
+
|
431
|
+
# Drop a column from a table
|
432
|
+
def remove_column(table_name, column_name) #:nodoc:
|
433
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
|
434
|
+
end
|
435
|
+
|
436
|
+
protected
|
437
|
+
|
438
|
+
# Execute a query
|
439
|
+
def select(sql, name = nil, binds = []) #:nodoc:
|
440
|
+
return execute(sql, name, binds)
|
441
|
+
end
|
442
|
+
|
443
|
+
# Queries the structure of a table including the columns names, defaults, type, and nullability
|
444
|
+
# ActiveRecord uses the type to parse scale and precision information out of the types. As a result,
|
445
|
+
# chars, varchars, binary, nchars, nvarchars must all be returned in the form <i>type</i>(<i>width</i>)
|
446
|
+
# numeric and decimal must be returned in the form <i>type</i>(<i>width</i>, <i>scale</i>)
|
447
|
+
# Nullability is returned as 0 (no nulls allowed) or 1 (nulls allowed)
|
448
|
+
# Alos, ActiveRecord expects an autoincrement column to have default value of NULL
|
449
|
+
def table_structure(table_name)
|
450
|
+
sql = "SELECT COLUMN_NAME, IIF(COLUMN_DEF = 'NULL', null, COLUMN_DEF) as COLUMN_DEF, TYPE_NAME, NULLABLE from (EXECUTE PROCEDURE sp_GetColumns( NULL, NULL, '#{table_name}', NULL )) spgc;"
|
451
|
+
structure = execute(sql, :skip_logging)
|
452
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure == false
|
453
|
+
structure
|
454
|
+
end
|
455
|
+
|
456
|
+
# Required to prevent DEFAULT NULL being added to primary keys
|
457
|
+
def options_include_default?(options)
|
458
|
+
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
459
|
+
end
|
460
|
+
|
461
|
+
private
|
462
|
+
|
463
|
+
# Connect
|
464
|
+
def connect! #:nodoc:
|
465
|
+
result = ADS.instance.api.ads_connect(@connection, @connection_string)
|
466
|
+
if result != 1 then
|
467
|
+
error = ADS.instance.api.ads_error(@connection)
|
468
|
+
raise ActiveRecord::ActiveRecordError.new("#{error}: Cannot Establish Connection")
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
# Execute a query
|
473
|
+
def query(sql, binds = []) #:nodoc:
|
474
|
+
return if sql.nil?
|
475
|
+
|
476
|
+
if binds.empty?
|
477
|
+
rs = ADS.instance.api.ads_execute_direct(@connection, sql)
|
478
|
+
else
|
479
|
+
stmt = ADS.instance.api.ads_prepare(@connection, sql)
|
480
|
+
# bind each of the parameters
|
481
|
+
# col: Parameter array. Col[0] -> Parameter info, Col[1] -> Parameter value
|
482
|
+
binds.each_with_index { |col, index|
|
483
|
+
result, param = ADS.instance.api.ads_describe_bind_param(stmt, index)
|
484
|
+
if result == 1
|
485
|
+
# For date/time/timestamp fix up the format to remove the timzone
|
486
|
+
if (col[0].type === :datetime or col[0].type === :timestamp or col[0] === :time) and !col[1].nil?
|
487
|
+
param.set_value(col[1].to_s(:db))
|
488
|
+
else
|
489
|
+
param.set_value(col[1])
|
490
|
+
end
|
491
|
+
ADS.instance.api.ads_bind_param(stmt, index, param)
|
492
|
+
else
|
493
|
+
result, errstr = ADS.instance.api.ads_error(@connection)
|
494
|
+
raise AdvantageException.new(errstr, result, sql)
|
495
|
+
end
|
496
|
+
} #binds.each_with_index
|
497
|
+
result = ADS.instance.api.ads_execute(stmt)
|
498
|
+
if result == 1
|
499
|
+
rs = stmt
|
500
|
+
else
|
501
|
+
result, errstr = ADS.instance.api.ads_error(@connection)
|
502
|
+
raise AdvantageException.new(errstr, result, sql)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
if rs.nil?
|
506
|
+
result, errstr = ADS.instance.api.ads_error(@connection)
|
507
|
+
raise AdvantageException.new(errstr, result, sql)
|
508
|
+
end
|
509
|
+
|
510
|
+
record = []
|
511
|
+
if( ADS.instance.api.ads_num_cols(rs) > 0 )
|
512
|
+
while ADS.instance.api.ads_fetch_next(rs) == 1
|
513
|
+
max_cols = ADS.instance.api.ads_num_cols(rs)
|
514
|
+
result = Hash.new()
|
515
|
+
max_cols.times do |cols|
|
516
|
+
result[ADS.instance.api.ads_get_column_info(rs, cols)[2]] = ADS.instance.api.ads_get_column(rs, cols)[1]
|
517
|
+
end
|
518
|
+
record << result
|
519
|
+
end
|
520
|
+
@affected_rows = 0
|
521
|
+
else
|
522
|
+
@affected_rows = ADS.instance.api.ads_affected_rows(rs)
|
523
|
+
end
|
524
|
+
ADS.instance.api.ads_free_stmt(rs)
|
525
|
+
|
526
|
+
return record
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Arel
|
2
|
+
module Visitors
|
3
|
+
class Advantage < Arel::Visitors::ToSql
|
4
|
+
private
|
5
|
+
|
6
|
+
def visit_Arel_Nodes_Offset o
|
7
|
+
"START AT #{visit(o.expr) + 1}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_Arel_Nodes_Limit(o)
|
11
|
+
"TOP #{visit o.expr}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_Arel_Nodes_SelectStatement(o)
|
15
|
+
[
|
16
|
+
"SELECT",
|
17
|
+
(visit(o.limit) if o.limit),
|
18
|
+
(visit(o.offset) if o.offset),
|
19
|
+
o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
|
20
|
+
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
|
21
|
+
].compact.join ' '
|
22
|
+
end
|
23
|
+
|
24
|
+
def visit_Arel_Nodes_SelectCore o
|
25
|
+
[
|
26
|
+
"#{o.projections.map { |x| visit x }.join ', '}",
|
27
|
+
("FROM #{visit o.froms}" if o.froms),
|
28
|
+
("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
|
29
|
+
("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
|
30
|
+
(visit(o.having) if o.having),
|
31
|
+
].compact.join ' '
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Arel::Visitors::VISITORS['advantage'] = Arel::Visitors::Advantage
|
38
|
+
|
39
|
+
|
data/test/connection.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
print "Using native Advantage Interface\n"
|
2
|
+
require_dependency 'models/course'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = {
|
8
|
+
'arunit' => {
|
9
|
+
:adapter => 'advantage',
|
10
|
+
:database => 'c:/test/arunit.add',
|
11
|
+
:username => 'adssys',
|
12
|
+
:password =>
|
13
|
+
},
|
14
|
+
'arunit2' => {
|
15
|
+
:adapter => 'advantage',
|
16
|
+
:database => 'c:/test/arunit2.add',
|
17
|
+
:username => 'adssys',
|
18
|
+
:password =>
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection 'arunit'
|
23
|
+
Course.establish_connection 'arunit2'
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-advantage-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Edgar Sherman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: advantage
|
16
|
+
requirement: &22592844 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *22592844
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activerecord
|
27
|
+
requirement: &22592400 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.2.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *22592400
|
36
|
+
description: ! ' ActiveRecord driver for Advantage
|
37
|
+
|
38
|
+
'
|
39
|
+
email: advantage@sybase.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files:
|
43
|
+
- README
|
44
|
+
- LICENSE
|
45
|
+
files:
|
46
|
+
- lib/active_record/connection_adapters/advantage_adapter.rb
|
47
|
+
- lib/arel/visitors/advantage.rb
|
48
|
+
- test/connection.rb
|
49
|
+
- README
|
50
|
+
- LICENSE
|
51
|
+
homepage: http://devzone.advantagedatabase.com
|
52
|
+
licenses: []
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --title
|
56
|
+
- ActiveRecord Driver for Advantage
|
57
|
+
- --main
|
58
|
+
- README
|
59
|
+
- --line-numbers
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.9.2
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.16
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: ActiveRecord driver for Advantage
|
80
|
+
test_files: []
|