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 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: []