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

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