activerecord-advantage-adapter 0.1.0

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