activerecord-sqlanywhere-jdbc-in4systems-adapter 1.0.12-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 754ffa261c7b9d3bfc68476c8463161b11fb7fbc
4
+ data.tar.gz: ba39ec75ea5143074d62966c70a608255bb89205
5
+ SHA512:
6
+ metadata.gz: 3cbf49e0b0bc3657d7ca89e9fc378551c693c0b6104946650799245af164a05bbf3d623e813bd9cfe475fa6a052345882df555c399034ab25e8f0212a26f135a
7
+ data.tar.gz: 6cca0835a18ce4e7cbbbfde53ea4bfac99b322bb8a4d83295f1a4da6e842a1374d922ebb5d13931d92b35167dbd29dde2aeb67bf9b2a16d72aff7de74bb8f277
@@ -0,0 +1,33 @@
1
+ =CHANGE LOG
2
+
3
+ =====0.2.0 -- 2010/12/02
4
+ - Added support for Rails 3.0.3
5
+ - Added support for Arel 2
6
+ - Removed test instructions for ActiveRecord 2.2.2
7
+ - Updated license to 2010
8
+
9
+ =====0.1.3 -- 2010/02/01
10
+ - Added :encoding option to connection string
11
+ - Fixed bug associated with dangling connections in development mode (http://groups.google.com/group/sql-anywhere-web-development/browse_thread/thread/79fa81bdfcf84c13/e29074e5b8b7ad6a?lnk=gst&q=activerecord#e29074e5b8b7ad6a)
12
+
13
+ =====0.1.2 -- 2008/12/30
14
+ - Fixed bug in ActiveRecord::ConnectionAdapters::SQLAnywhereAdapter#table_structure SQL (Paul Smith)
15
+ - Added options for :commlinks and :connection_name to database.yml configuration (Paul Smith)
16
+ - Fixed ActiveRecord::ConnectionAdapters::SQLAnywhereColumn.string_to_binary and binary_to_string (Paul Smith)
17
+ - Added :time as a native datatype (Paul Smith)
18
+ - Override SQLAnywhereAdapter#active? to prevent stale connections (Paul Smith)
19
+ - 'Fixed' coding style to match Rails standards (Paul Smith)
20
+ - Added temporary option for timestamp_format
21
+ - Fixed bug to let migrations drop columns with indexes
22
+ - Formatted code
23
+ - Fixed bug to raise proper exceptions when a query with a bad column in executed
24
+
25
+ =====0.1.1 -- 2008/11/06
26
+ - Changed file permissions on archives
27
+ - Changed archives to be specific to platform (.zip on windows, .tar.gz
28
+ otherwise)
29
+ - Removed the default rake task
30
+
31
+ =====0.1.0 -- 2008/10/15
32
+ - Initial Release
33
+
data/LICENSE ADDED
@@ -0,0 +1,23 @@
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
+ * While not a requirement of the license, if you do modify this file, we
20
+ * would appreciate hearing about it. Please email sqlany_interfaces@sybase.com
21
+ *
22
+ *
23
+ *====================================================*/
data/README ADDED
@@ -0,0 +1,56 @@
1
+ =SQL Anywhere ActiveRecord Driver
2
+
3
+ This is a SQL Anywhere driver for Ruby ActiveRecord. This driver requires the
4
+ native SQL Anywhere Ruby driver. To get the native driver, use:
5
+
6
+ gem install sqlanywhere
7
+
8
+ This driver is designed for use with ActiveRecord 3.0.3 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 => 'sqlanywhere',
19
+ :database => 'arunit', #equivalent to the "DatabaseName" parameter
20
+ :server => 'arunit', #equivalent to the "ServerName" parameter
21
+ :username => 'dba', #equivalent to the "UserID" parameter
22
+ :password => 'sql', #equivalent to the "Password" parameter
23
+ :encoding => 'Windows-1252', #equivalent to the "CharSet" parameter
24
+ :commlinks => 'TCPIP()', #equivalent to the "Commlinks" parameter
25
+ :connection_name => 'Rails' #equivalent to the "ConnectionName" parameter
26
+ }
27
+
28
+ ==Running the ActiveRecord Unit Test Suite
29
+
30
+ 1. Open <tt><ACTIVERECORD_INSTALL_DIR>/rakefile</tt> and modify the line:
31
+
32
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
33
+
34
+ to include <tt>sqlanywhere</tt>. It should now look like:
35
+
36
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase sqlanywhere )
37
+
38
+ 2. Create directory to hold the connection definition:
39
+
40
+ mkdir <ACTIVERECORD_INSTALL_DIR>/test/connections/native_sqlanywhere
41
+
42
+ 3. Copy <tt>test/connection.rb</tt> into the newly created directory.
43
+
44
+ 4. Create the two test databases. These can be created in any directory.
45
+
46
+ dbinit -c arunit
47
+ dbinit -c arunit2
48
+ dbsrv11 arunit arunit2
49
+
50
+ <b>If the commands cannot be found, make sure you have set up the SQL Anywhere environment variables correctly.</b> For more information review the online documentation here[http://dcx.sybase.com/index.php#http%3A%2F%2Fdcx.sybase.com%2F1100en%2Fdbadmin_en11%2Fda-envvar-sect1-3672410.html].
51
+
52
+ 6. Run the unit test suite from the ActiveRecord install directory:
53
+
54
+ rake test_sqlanywhere
55
+
56
+ <b>If the migration tests fail, make sure you have set up the SQL Anywhere environment variables correctly.</b> For more information review the online documentation here[http://dcx.sybase.com/index.php#http%3A%2F%2Fdcx.sybase.com%2F1100en%2Fdbadmin_en11%2Fda-envvar-sect1-3672410.html].
@@ -0,0 +1,492 @@
1
+ #encoding: utf-8
2
+ #====================================================
3
+ #
4
+ # Copyright 2008-2010 iAnywhere Solutions, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ #
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # While not a requirement of the license, if you do modify this file, we
21
+ # would appreciate hearing about it. Please email sqlany_interfaces@sybase.com
22
+ #
23
+ #
24
+ #====================================================
25
+ #require 'active_record'
26
+ require 'activerecord-jdbc-adapter'
27
+
28
+ require 'active_record/connection_adapters/abstract_adapter'
29
+ require 'arel/visitors/sqlanywhere.rb'
30
+ require 'pathname'
31
+
32
+ module ActiveRecord
33
+ class Base
34
+ DEFAULT_CONFIG = { :username => 'dba', :password => 'sql' }
35
+ # Main connection function to SQL Anywhere
36
+ # Connection Adapter takes four parameters:
37
+ # * :database (required, no default). Corresponds to "DatabaseName=" in connection string
38
+ # * :server (optional, defaults to :databse). Corresponds to "ServerName=" in connection string
39
+ # * :username (optional, default to 'dba')
40
+ # * :password (optional, deafult to 'sql')
41
+ # * :encoding (optional, defaults to charset of OS)
42
+ # * :commlinks (optional). Corresponds to "CommLinks=" in connection string
43
+ # * :connection_name (optional). Corresponds to "ConnectionName=" in connection string
44
+
45
+ def self.sqlanywhere_jdbc_in4systems_connection(config)
46
+
47
+ if config[:connection_string]
48
+ connection_string = config[:connection_string]
49
+ else
50
+ config = DEFAULT_CONFIG.merge(config)
51
+
52
+ raise ArgumentError, "No database name was given. Please add a :database option." unless config.has_key?(:database)
53
+
54
+ connection_string = "ServerName=#{(config[:server] || config[:database])};DatabaseName=#{config[:database]};UserID=#{config[:username]};Password=#{config[:password]};"
55
+ connection_string += "CommLinks=#{config[:commlinks]};" unless config[:commlinks].nil?
56
+ connection_string += "ConnectionName=#{config[:connection_name]};" unless config[:connection_name].nil?
57
+ connection_string += "CharSet=#{config[:encoding]};" unless config[:encoding].nil?
58
+ connection_string += "Idle=0" # Prevent the server from disconnecting us if we're idle for >240mins (by default)
59
+ end
60
+
61
+ url = 'jdbc:sqlanywhere:' + connection_string
62
+
63
+ if ENV['SQLANY16']
64
+ $CLASSPATH << 'sajdbc4.jar'
65
+ $CLASSPATH << Pathname.new(ENV['SQLANY16']).join('java').join('sajdbc4.jar').to_s
66
+ driver = 'sybase.jdbc4.sqlanywhere.IDriver'
67
+ elsif ENV['SQLANY12']
68
+ $CLASSPATH << 'sajdbc4.jar'
69
+ $CLASSPATH << Pathname.new(ENV['SQLANY12']).join('java').join('sajdbc4.jar').to_s
70
+ driver = 'sybase.jdbc4.sqlanywhere.IDriver'
71
+ elsif ENV['SQLANY11']
72
+ $CLASSPATH << 'sajdbc.jar'
73
+ $CLASSPATH << Pathname.new(ENV['SQLANY11']).join('java').join('sajdbc.jar').to_s
74
+ driver = 'sybase.jdbc.sqlanywhere.IDriver'
75
+ else
76
+ raise "Cannot find SqlAnywhere installation directory"
77
+ end
78
+
79
+ conn = ActiveRecord::Base.jdbc_connection({adapter: 'jdbc', driver: driver, url: url})
80
+
81
+ ConnectionAdapters::SQLAnywhereJdbcIn4systemsAdapter.new( conn, logger, connection_string)
82
+ end
83
+ end
84
+
85
+ module ConnectionAdapters
86
+ class JdbcTypeConverter
87
+ AR_TO_JDBC_TYPES[:text] << lambda {|r| r['type_name'] =~ /^long varchar$/i}
88
+ end
89
+
90
+ class SQLAnywhereException < StandardError
91
+ attr_reader :errno
92
+ attr_reader :sql
93
+
94
+ def initialize(message, errno, sql)
95
+ super(message)
96
+ @errno = errno
97
+ @sql = sql
98
+ end
99
+ end
100
+
101
+ class SQLAnywhereColumn < Column
102
+ private
103
+ # Overridden to handle SQL Anywhere integer, varchar, binary, and timestamp types
104
+ def simplified_type(field_type)
105
+ return :boolean if field_type =~ /tinyint/i
106
+ return :boolean if field_type =~ /bit/i
107
+ return :text if field_type =~ /long varchar/i
108
+ return :string if field_type =~ /varchar/i
109
+ return :binary if field_type =~ /long binary/i
110
+ return :datetime if field_type =~ /timestamp/i
111
+ return :integer if field_type =~ /smallint|bigint/i
112
+ return :text if field_type =~ /xml/i
113
+ return :integer if field_type =~ /uniqueidentifier/i
114
+ super
115
+ end
116
+
117
+ def extract_limit(sql_type)
118
+ case sql_type
119
+ when /^tinyint/i
120
+ 1
121
+ when /^smallint/i
122
+ 2
123
+ when /^integer/i
124
+ 4
125
+ when /^bigint/i
126
+ 8
127
+ else super
128
+ end
129
+ end
130
+
131
+ protected
132
+ # Handles the encoding of a binary object into SQL Anywhere
133
+ # SQL Anywhere requires that binary values be encoded as \xHH, where HH is a hexadecimal number
134
+ # This function encodes the binary string in this format
135
+ def self.string_to_binary(value)
136
+ "\\x" + value.unpack("H*")[0].scan(/../).join("\\x")
137
+ end
138
+
139
+ def self.binary_to_string(value)
140
+ value.gsub(/\\x[0-9]{2}/) { |byte| byte[2..3].hex }
141
+ end
142
+
143
+ # Should override the time column values.
144
+ # Sybase doesn't like the time zones.
145
+
146
+ end
147
+
148
+ class SQLAnywhereJdbcIn4systemsAdapter < AbstractAdapter
149
+ delegate :select, :select_rows, :execute, to: :conn
150
+
151
+ attr_reader :conn
152
+ def initialize( conn, logger, connection_string = "") #:nodoc:
153
+ super
154
+ @visitor = Arel::Visitors::SQLAnywhere.new self
155
+ @conn = conn
156
+ end
157
+
158
+ def self.visitor_for(pool)
159
+ config = pool.spec.config
160
+
161
+ if config.fetch(:prepared_statements) {true}
162
+ Arel::Visitors::SQLAnywhere.new pool
163
+ else
164
+ BindSubstitution.new pool
165
+ end
166
+ end
167
+
168
+ def adapter_name #:nodoc:
169
+ 'SQLAnywhere'
170
+ end
171
+
172
+ def supports_migrations? #:nodoc:
173
+ true
174
+ end
175
+
176
+ def requires_reloading?
177
+ true
178
+ end
179
+
180
+ def active?
181
+ # The liveness variable is used a low-cost "no-op" to test liveness
182
+ SA.instance.api.sqlany_execute_immediate(@connection, "SET liveness = 1") == 1
183
+ rescue
184
+ false
185
+ end
186
+
187
+ def supports_count_distinct? #:nodoc:
188
+ true
189
+ end
190
+
191
+ def supports_autoincrement? #:nodoc:
192
+ true
193
+ end
194
+
195
+ # Maps native ActiveRecord/Ruby types into SQLAnywhere types
196
+ # TINYINTs are treated as the default boolean value
197
+ # ActiveRecord allows NULLs in boolean columns, and the SQL Anywhere BIT type does not
198
+ # As a result, TINYINT must be used. All TINYINT columns will be assumed to be boolean and
199
+ # should not be used as single-byte integer columns. This restriction is similar to other ActiveRecord database drivers
200
+ def native_database_types #:nodoc:
201
+ {
202
+ :primary_key => 'INTEGER PRIMARY KEY DEFAULT AUTOINCREMENT NOT NULL',
203
+ :string => { :name => "varchar", :limit => 255 },
204
+ :text => { :name => "long varchar" },
205
+ :integer => { :name => "integer", :limit => 4 },
206
+ :float => { :name => "float" },
207
+ :decimal => { :name => "decimal" },
208
+ :datetime => { :name => "datetime" },
209
+ :timestamp => { :name => "datetime" },
210
+ :time => { :name => "time" },
211
+ :date => { :name => "date" },
212
+ :binary => { :name => "binary" },
213
+ :boolean => { :name => "tinyint", :limit => 1}
214
+ }
215
+ end
216
+
217
+ # QUOTING ==================================================
218
+
219
+ # Applies quotations around column names in generated queries
220
+ def quote_column_name(name) #:nodoc:
221
+ %Q("#{name}")
222
+ end
223
+
224
+ def quote_table_name(name)
225
+ owner, table = name.to_s.split('.', 2)
226
+ if table == nil
227
+ table = owner
228
+ owner = :dba
229
+ end
230
+ "#{quote_column_name(owner)}.#{quote_column_name(table)}"
231
+ end
232
+
233
+ def quote_table_alias_name(name)
234
+ quote_column_name name
235
+ end
236
+
237
+
238
+ # Handles special quoting of binary columns. Binary columns will be treated as strings inside of ActiveRecord.
239
+ # ActiveRecord requires that any strings it inserts into databases must escape the backslash (\).
240
+ # Since in the binary case, the (\x) is significant to SQL Anywhere, it cannot be escaped.
241
+ def quote(value, column = nil)
242
+ case value
243
+ when String, ActiveSupport::Multibyte::Chars
244
+ value_S = value.to_s
245
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
246
+ "'#{column.class.string_to_binary(value_S)}'"
247
+ else
248
+ super(value, column)
249
+ end
250
+ else
251
+ super(value, column)
252
+ end
253
+ end
254
+
255
+ def quoted_true
256
+ '1'
257
+ end
258
+
259
+ def quoted_false
260
+ '0'
261
+ end
262
+
263
+ # SQL Anywhere does not support sizing of integers based on the sytax INTEGER(size). Integer sizes
264
+ # must be captured when generating the SQL and replaced with the appropriate size.
265
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
266
+ type = type.to_sym
267
+ if native = native_database_types[type]
268
+ if type == :integer
269
+ case limit
270
+ when 1
271
+ column_type_sql = 'tinyint'
272
+ when 2
273
+ column_type_sql = 'smallint'
274
+ when 3..4
275
+ column_type_sql = 'integer'
276
+ when 5..8
277
+ column_type_sql = 'bigint'
278
+ else
279
+ column_type_sql = 'integer'
280
+ end
281
+ column_type_sql
282
+ elsif type == :string and !limit.nil?
283
+ "varchar (#{limit})"
284
+ elsif type == :boolean
285
+ column_type_sql = 'tinyint'
286
+ else
287
+ super(type, limit, precision, scale)
288
+ end
289
+ else
290
+ super(type, limit, precision, scale)
291
+ end
292
+ end
293
+
294
+ def viewed_tables(name = nil)
295
+ list_of_tables(['view'], name)
296
+ end
297
+
298
+ def base_tables(name = nil)
299
+ list_of_tables(['base'], name)
300
+ end
301
+
302
+ # Do not return SYS-owned or DBO-owned tables or RS_systabgroup-owned
303
+ def tables(name = nil) #:nodoc:
304
+ list_of_tables(['base', 'view'])
305
+ end
306
+
307
+ def columns(table_name, name = nil) #:nodoc:
308
+ table_structure(table_name).map do |field|
309
+ default = field['default']
310
+ if default == nil # Nil is the usual case
311
+ elsif default.starts_with?("'") # If string, remove first and last quotes and the last \n character
312
+ default = default[1..-2]
313
+ elsif default =~ /^-?\d+(\.\d+)?$/ # If a number string, leave as it is
314
+ else # Otherwise, it's probably something (LAST USER, CURRENT TIMESTAMP, etc) that wouldn't work in Rails, so return nil
315
+ default = nil
316
+ end
317
+ SQLAnywhereColumn.new(field['name'], default, field['domain'], (field['nulls'] == 1))
318
+ end
319
+ end
320
+
321
+ def indexes(table_name, name = nil) #:nodoc:
322
+ sql = "SELECT DISTINCT index_name, \"unique\" FROM SYS.SYSTABLE INNER JOIN SYS.SYSIDXCOL ON SYS.SYSTABLE.table_id = SYS.SYSIDXCOL.table_id INNER JOIN SYS.SYSIDX ON SYS.SYSTABLE.table_id = SYS.SYSIDX.table_id AND SYS.SYSIDXCOL.index_id = SYS.SYSIDX.index_id WHERE table_name = '#{table_name}' AND index_category > 2"
323
+ select(sql, name).map do |row|
324
+ index = IndexDefinition.new(table_name, row['index_name'])
325
+ index.unique = row['unique'] == 1
326
+ sql = "SELECT column_name FROM SYS.SYSIDX INNER JOIN SYS.SYSIDXCOL ON SYS.SYSIDXCOL.table_id = SYS.SYSIDX.table_id AND SYS.SYSIDXCOL.index_id = SYS.SYSIDX.index_id INNER JOIN SYS.SYSCOLUMN ON SYS.SYSCOLUMN.table_id = SYS.SYSIDXCOL.table_id AND SYS.SYSCOLUMN.column_id = SYS.SYSIDXCOL.column_id WHERE index_name = '#{row['index_name']}'"
327
+ index.columns = select(sql).map { |col| col['column_name'] }
328
+ index
329
+ end
330
+ end
331
+
332
+ def primary_key(table_name) #:nodoc:
333
+ sql = "SELECT cname from SYS.SYSCOLUMNS where tname = '#{table_name}' and in_primary_key = 'Y'"
334
+ rs = exec_query(sql)
335
+ if !rs.nil? and !rs.first.nil?
336
+ rs.first['cname']
337
+ else
338
+ nil
339
+ end
340
+ end
341
+
342
+ def remove_index(table_name, options={}) #:nodoc:
343
+ execute "DROP INDEX #{quote_table_name(table_name)}.#{quote_column_name(index_name(table_name, options))}"
344
+ end
345
+
346
+ def rename_table(name, new_name)
347
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME #{quote_table_name(new_name)}"
348
+ end
349
+
350
+ def change_column_default(table_name, column_name, default) #:nodoc:
351
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
352
+ end
353
+
354
+ def change_column_null(table_name, column_name, null, default = nil)
355
+ unless null || default.nil?
356
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
357
+ end
358
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? '' : 'NOT'} NULL")
359
+ end
360
+
361
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
362
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
363
+ add_column_options!(add_column_sql, options)
364
+ add_column_sql << ' NULL' if options[:null]
365
+ execute(add_column_sql)
366
+ end
367
+
368
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
369
+ if column_name.downcase == new_column_name.downcase
370
+ whine = "if_the_only_change_is_case_sqlanywhere_doesnt_rename_the_column"
371
+ rename_column table_name, column_name, "#{new_column_name}#{whine}"
372
+ rename_column table_name, "#{new_column_name}#{whine}", new_column_name
373
+ else
374
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
375
+ end
376
+ end
377
+
378
+ def remove_column(table_name, *column_names)
379
+ column_names = column_names.flatten
380
+ column_names.zip(columns_for_remove(table_name, *column_names)).each do |unquoted_column_name, column_name|
381
+ sql = <<-SQL
382
+ SELECT "index_name" FROM SYS.SYSTAB join SYS.SYSTABCOL join SYS.SYSIDXCOL join SYS.SYSIDX
383
+ WHERE "column_name" = '#{unquoted_column_name}' AND "table_name" = '#{table_name}'
384
+ SQL
385
+ select(sql, nil).each do |row|
386
+ execute "DROP INDEX \"#{table_name}\".\"#{row['index_name']}\""
387
+ end
388
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}"
389
+ end
390
+ end
391
+
392
+ def exec_query(sql, name = 'SQL', binds = [])
393
+ binds.map! do |column, value|
394
+ type = column.type
395
+ if value != nil
396
+ case type
397
+ when :boolean
398
+ [column, value ? 1 : 0]
399
+ else
400
+ [column, value]
401
+ end
402
+ else
403
+ [column, value]
404
+ end
405
+ end
406
+ conn.exec_query(sql, name, binds)
407
+ end
408
+
409
+ def last_inserted_id(result)
410
+ select_value('SELECT @@identity')
411
+ end
412
+
413
+ def select_user(cache=true)
414
+ @user_id = (cache && @user_id) || select_value('SELECT USER')
415
+ end
416
+
417
+ # Set the database user to be user_id.
418
+ # If a block is given, then after running the block, the previous user_id is restored.
419
+ def set_user(user_id)
420
+ previous_user_id = select_user(true)
421
+ if previous_user_id != user_id
422
+ execute("SETUSER #{quote_column_name(user_id)}")
423
+ @user_id = user_id
424
+ end
425
+ if block_given?
426
+ begin
427
+ yield
428
+ ensure
429
+ set_user(previous_user_id)
430
+ end
431
+ end
432
+ end
433
+
434
+ protected
435
+
436
+ def list_of_tables(types, name = nil)
437
+ sql = "SELECT table_name FROM SYS.SYSTABLE WHERE table_type in (#{types.map{|t| quote(t)}.join(', ')}) and creator NOT IN (0,3,5)"
438
+ select(sql, name).map { |row| row["table_name"] }
439
+ end
440
+
441
+ # Queries the structure of a table including the columns names, defaults, type, and nullability
442
+ # ActiveRecord uses the type to parse scale and precision information out of the types. As a result,
443
+ # chars, varchars, binary, nchars, nvarchars must all be returned in the form <i>type</i>(<i>width</i>)
444
+ # numeric and decimal must be returned in the form <i>type</i>(<i>width</i>, <i>scale</i>)
445
+ # Nullability is returned as 0 (no nulls allowed) or 1 (nulls allowed)
446
+ # Alos, ActiveRecord expects an autoincrement column to have default value of NULL
447
+
448
+ def table_structure(table_name)
449
+ sql = <<-SQL
450
+ SELECT SYS.SYSCOLUMN.column_name AS name,
451
+ "default" AS "default",
452
+ IF SYS.SYSCOLUMN.domain_id IN (7,8,9,11,33,34,35,3,27) THEN
453
+ IF SYS.SYSCOLUMN.domain_id IN (3,27) THEN
454
+ SYS.SYSDOMAIN.domain_name || '(' || SYS.SYSCOLUMN.width || ',' || SYS.SYSCOLUMN.scale || ')'
455
+ ELSE
456
+ SYS.SYSDOMAIN.domain_name || '(' || SYS.SYSCOLUMN.width || ')'
457
+ ENDIF
458
+ ELSE
459
+ SYS.SYSDOMAIN.domain_name
460
+ ENDIF AS domain,
461
+ IF SYS.SYSCOLUMN.nulls = 'Y' THEN 1 ELSE 0 ENDIF AS nulls
462
+ FROM
463
+ SYS.SYSCOLUMN
464
+ INNER JOIN SYS.SYSTABLE ON SYS.SYSCOLUMN.table_id = SYS.SYSTABLE.table_id
465
+ INNER JOIN SYS.SYSDOMAIN ON SYS.SYSCOLUMN.domain_id = SYS.SYSDOMAIN.domain_id
466
+ INNER JOIN sys.sysUser ON sys.systable.creator = sys.sysuser.user_id AND sys.sysuser.user_name = 'dba'
467
+ WHERE
468
+ table_name = '#{table_name}'
469
+ SQL
470
+ structure = exec_query(sql, :skip_logging)
471
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure == false
472
+ structure
473
+ end
474
+
475
+ # Required to prevent DEFAULT NULL being added to primary keys
476
+ def options_include_default?(options)
477
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
478
+ end
479
+
480
+ private
481
+
482
+ def set_connection_options
483
+ SA.instance.api.sqlany_execute_immediate(@connection, "SET TEMPORARY OPTION non_keywords = 'LOGIN'") rescue nil
484
+ SA.instance.api.sqlany_execute_immediate(@connection, "SET TEMPORARY OPTION timestamp_format = 'YYYY-MM-DD HH:NN:SS'") rescue nil
485
+ #SA.instance.api.sqlany_execute_immediate(@connection, "SET OPTION reserved_keywords = 'LIMIT'") rescue nil
486
+ # The liveness variable is used a low-cost "no-op" to test liveness
487
+ SA.instance.api.sqlany_execute_immediate(@connection, "CREATE VARIABLE liveness INT") rescue nil
488
+ end
489
+ end
490
+ end
491
+ end
492
+
@@ -0,0 +1,159 @@
1
+ module Arel
2
+ module Visitors
3
+ class SQLAnywhere < Arel::Visitors::ToSql
4
+ def initialize connection
5
+ super
6
+ @quoted_table_aliases = {}
7
+ end
8
+
9
+ private
10
+ def visit_Arel_Nodes_SelectStatement o
11
+ o = order_hacks(o)
12
+
13
+ is_distinct = using_distinct?(o)
14
+
15
+ o.limit = 1000000 if (o.offset && !o.limit)
16
+ o.limit = o.limit.expr if(o.limit.is_a?(Arel::Nodes::Limit))
17
+ o.limit = o.limit if(o.limit.is_a?(Fixnum))
18
+
19
+ [
20
+ "SELECT",
21
+ ("DISTINCT" if is_distinct),
22
+ ("TOP #{o.limit}" if o.limit),
23
+ (visit_Arel_Nodes_Offset(o.offset) if o.offset),
24
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
25
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
26
+ #("LIMIT #{o.limit}" if o.limit),
27
+ #(visit(o.offset) if o.offset),
28
+ (visit(o.lock) if o.lock),
29
+ ].compact.join ' '
30
+ end
31
+
32
+ def visit_Arel_Nodes_SelectCore o
33
+ [
34
+ "#{o.projections.map { |x| visit x }.join ', '}",
35
+ ("FROM #{visit o.source}" if o.source), # Joins
36
+ ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
37
+ ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
38
+ (visit(o.having) if o.having),
39
+ ].compact.join ' '
40
+ end
41
+
42
+ def visit_Arel_Nodes_Group o
43
+ expr = o.expr.clone
44
+ if expr.class == Arel::Nodes::NamedFunction
45
+ expr.alias = nil
46
+ end
47
+ visit expr
48
+ end
49
+
50
+ def visit_Arel_Nodes_Offset o
51
+ "START AT #{visit(o.expr) + 1}"
52
+ end
53
+
54
+ def visit_Arel_Nodes_True o
55
+ "1=1"
56
+ end
57
+
58
+ def visit_Arel_Nodes_False o
59
+ "1=0"
60
+ end
61
+
62
+ def visit_Arel_Nodes_Matches o
63
+ # The version in arel cannot like integer columns
64
+ left = visit o.left # This method sets last column
65
+ # If last column was left, visit o.right would return 0
66
+ self.last_column = nil
67
+ "#{left} LIKE #{visit o.right}"
68
+ end
69
+
70
+ def visit_Arel_Nodes_TableAlias o
71
+ "#{visit o.relation} #{quote_table_alias_name o.name}"
72
+ end
73
+
74
+ def visit_Arel_Table o, a=nil
75
+ if o.table_alias
76
+ "#{quote_table_name o.name} #{quote_table_alias_name o.table_alias}"
77
+ else
78
+ quote_table_name o.name
79
+ end
80
+ end
81
+
82
+ def visit_Arel_Attributes_Attribute o, a=nil
83
+ if o.relation.table_alias
84
+ join_name = o.relation.table_alias
85
+ "#{quote_table_alias_name join_name}.#{quote_column_name o.name}"
86
+ else
87
+ join_name = o.relation.name
88
+ "#{quote_table_name join_name}.#{quote_column_name o.name}"
89
+ end
90
+ end
91
+
92
+ def quote_table_alias_name name
93
+ return name if Arel::Nodes::SqlLiteral === name
94
+ @quoted_table_aliases[name] ||= @connection.quote_table_alias_name(name)
95
+ end
96
+
97
+
98
+
99
+
100
+
101
+ def using_distinct?(o)
102
+ o.cores.any? do |core|
103
+ core.set_quantifier.class == Arel::Nodes::Distinct
104
+ end
105
+ end
106
+
107
+ # The functions (order_hacks, split_order_string) are based on the Oracle Enhacned ActiveRecord driver maintained by Raimonds Simanovskis (2010)
108
+ # (https://github.com/rsim/oracle-enhanced)
109
+
110
+ ###
111
+ # Hacks for the order clauses
112
+ def order_hacks o
113
+ return o if o.orders.empty?
114
+ return o unless o.cores.any? do |core|
115
+ core.projections.any? do |projection|
116
+ /DISTINCT.*FIRST_VALUE/ === projection
117
+ end
118
+ end
119
+ # Previous version with join and split broke ORDER BY clause
120
+ # if it contained functions with several arguments (separated by ',').
121
+ #
122
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
123
+ orders = o.orders.map do |x|
124
+ string = visit x
125
+ if string.include?(',')
126
+ split_order_string(string)
127
+ else
128
+ string
129
+ end
130
+ end.flatten
131
+ o.orders = []
132
+ orders.each_with_index do |order, i|
133
+ o.orders <<
134
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}")
135
+ end
136
+ o
137
+ end
138
+
139
+ # Split string by commas but count opening and closing brackets
140
+ # and ignore commas inside brackets.
141
+ def split_order_string(string)
142
+ array = []
143
+ i = 0
144
+ string.split(',').each do |part|
145
+ if array[i]
146
+ array[i] << ',' << part
147
+ else
148
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
149
+ array[i] = '' << part
150
+ end
151
+ i += 1 if array[i].count('(') == array[i].count(')')
152
+ end
153
+ array
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ Arel::Visitors::VISITORS['sqlanywhere'] = Arel::Visitors::SQLAnywhere
@@ -0,0 +1,25 @@
1
+ print "Using native SQLAnywhere 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 => 'sqlanywhere',
10
+ :database => 'arunit',
11
+ :server => 'arunit',
12
+ :username => 'dba',
13
+ :password => 'sql'
14
+ },
15
+ 'arunit2' => {
16
+ :adapter => 'sqlanywhere',
17
+ :database => 'arunit2',
18
+ :server => 'arunit',
19
+ :username => 'dba',
20
+ :password => 'sql'
21
+ }
22
+ }
23
+
24
+ ActiveRecord::Base.establish_connection 'arunit'
25
+ Course.establish_connection 'arunit2'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-sqlanywhere-jdbc-in4systems-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.12
5
+ platform: java
6
+ authors:
7
+ - Eric Farar
8
+ - Sri Kalai
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord-jdbc-adapter
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ requirement: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - '>='
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ prerelease: false
27
+ type: :runtime
28
+ - !ruby/object:Gem::Dependency
29
+ name: activerecord
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: 3.0.3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: 3.0.3
40
+ prerelease: false
41
+ type: :runtime
42
+ description: ActiveRecord JDBC driver for SQL Anywhere customized for in4systems
43
+ email: chris.couzens@in4systems.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG
49
+ - LICENSE
50
+ - README
51
+ - lib/active_record/connection_adapters/sqlanywhere_jdbc_in4systems_adapter.rb
52
+ - lib/arel/visitors/sqlanywhere.rb
53
+ - test/connection.rb
54
+ homepage: https://github.com/in4systems/activerecord-sqlanywhere-adapter
55
+ licenses:
56
+ - Apache License Version 2.0
57
+ metadata: {}
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 2.2.2
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: ActiveRecord driver for SQL Anywhere
78
+ test_files: []