akitaonrails-activerecord-sqlserver-adapter 1.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.
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
require 'bigdecimal/util'
|
|
5
|
+
|
|
6
|
+
# sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
|
|
7
|
+
#
|
|
8
|
+
# Author: Joey Gibson <joey@joeygibson.com>
|
|
9
|
+
# Date: 10/14/2004
|
|
10
|
+
#
|
|
11
|
+
# Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
|
|
12
|
+
# Date: 3/22/2005
|
|
13
|
+
#
|
|
14
|
+
# Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
|
|
15
|
+
# Date: 6/26/2005
|
|
16
|
+
|
|
17
|
+
# Modifications (Migrations): Tom Ward <tom@popdog.net>
|
|
18
|
+
# Date: 27/10/2005
|
|
19
|
+
#
|
|
20
|
+
# Modifications (Numerous fixes as maintainer): Ryan Tomayko <rtomayko@gmail.com>
|
|
21
|
+
# Date: Up to July 2006
|
|
22
|
+
|
|
23
|
+
# Current maintainer: Tom Ward <tom@popdog.net>
|
|
24
|
+
|
|
25
|
+
module ActiveRecord
|
|
26
|
+
class Base
|
|
27
|
+
def self.sqlserver_connection(config) #:nodoc:
|
|
28
|
+
require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
|
|
29
|
+
|
|
30
|
+
config = config.symbolize_keys
|
|
31
|
+
|
|
32
|
+
mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
|
|
33
|
+
username = config[:username] ? config[:username].to_s : 'sa'
|
|
34
|
+
password = config[:password] ? config[:password].to_s : ''
|
|
35
|
+
autocommit = config.key?(:autocommit) ? config[:autocommit] : true
|
|
36
|
+
if mode == "ODBC"
|
|
37
|
+
raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
|
|
38
|
+
dsn = config[:dsn]
|
|
39
|
+
driver_url = "DBI:ODBC:#{dsn}"
|
|
40
|
+
else
|
|
41
|
+
raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
|
|
42
|
+
database = config[:database]
|
|
43
|
+
host = config[:host] ? config[:host].to_s : 'localhost'
|
|
44
|
+
unless config[:trusted_connection]
|
|
45
|
+
driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
|
|
46
|
+
else
|
|
47
|
+
driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};Trusted_Connection=Yes;"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
conn = DBI.connect(driver_url, username, password)
|
|
51
|
+
conn["AutoCommit"] = autocommit
|
|
52
|
+
ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Overridden to include support for SQL server's lack of = operator on
|
|
56
|
+
# text/ntext/image columns LIKE operator is used instead
|
|
57
|
+
def self.sanitize_sql_hash(attrs)
|
|
58
|
+
conditions = attrs.map do |attr, value|
|
|
59
|
+
col = self.columns.find {|c| c.name == attr}
|
|
60
|
+
if col && col.respond_to?("is_special") && col.is_special
|
|
61
|
+
"#{table_name}.#{connection.quote_column_name(attr)} LIKE ?"
|
|
62
|
+
else
|
|
63
|
+
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
|
|
64
|
+
end
|
|
65
|
+
end.join(' AND ')
|
|
66
|
+
replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# In the case of SQL server, the lock value must follow the FROM clause
|
|
70
|
+
def self.construct_finder_sql(options)
|
|
71
|
+
scope = scope(:find)
|
|
72
|
+
sql = "SELECT #{(scope && scope[:select]) || options[:select] || '*'} "
|
|
73
|
+
sql << "FROM #{(scope && scope[:from]) || options[:from] || table_name} "
|
|
74
|
+
|
|
75
|
+
if ActiveRecord::Base.connection.adapter_name == "SQLServer" && !options[:lock].blank? # SQLServer
|
|
76
|
+
add_lock!(sql, options, scope)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
add_joins!(sql, options, scope)
|
|
80
|
+
add_conditions!(sql, options[:conditions], scope)
|
|
81
|
+
|
|
82
|
+
sql << " GROUP BY #{options[:group]} " if options[:group]
|
|
83
|
+
|
|
84
|
+
add_order!(sql, options[:order], scope)
|
|
85
|
+
add_limit!(sql, options, scope)
|
|
86
|
+
add_lock!(sql, options, scope) unless ActiveRecord::Base.connection.adapter_name == "SQLServer" # SQLServer
|
|
87
|
+
# $log.debug "database_helper: construct_finder_sql: sql at end: #{sql.inspect}"
|
|
88
|
+
sql
|
|
89
|
+
end
|
|
90
|
+
end # class Base
|
|
91
|
+
|
|
92
|
+
module ConnectionAdapters
|
|
93
|
+
class SQLServerColumn < Column# :nodoc:
|
|
94
|
+
attr_reader :identity, :is_special
|
|
95
|
+
|
|
96
|
+
def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
|
|
97
|
+
super(name, default, sql_type, null)
|
|
98
|
+
@identity = identity
|
|
99
|
+
@is_special = sql_type =~ /text|ntext|image/i
|
|
100
|
+
# TODO: check ok to remove @scale = scale_value
|
|
101
|
+
# SQL Server only supports limits on *char and float types
|
|
102
|
+
@limit = nil unless @type == :string
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def simplified_type(field_type)
|
|
106
|
+
case field_type
|
|
107
|
+
when /real/i then :float
|
|
108
|
+
when /money/i then :decimal
|
|
109
|
+
when /image/i then :binary
|
|
110
|
+
when /bit/i then :boolean
|
|
111
|
+
when /uniqueidentifier/i then :string
|
|
112
|
+
else super
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def type_cast(value)
|
|
117
|
+
return nil if value.nil?
|
|
118
|
+
case type
|
|
119
|
+
when :datetime then cast_to_datetime(value)
|
|
120
|
+
when :timestamp then cast_to_time(value)
|
|
121
|
+
when :time then cast_to_time(value)
|
|
122
|
+
when :date then cast_to_datetime(value)
|
|
123
|
+
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
|
124
|
+
else super
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def cast_to_time(value)
|
|
129
|
+
return value if value.is_a?(Time)
|
|
130
|
+
time_array = ParseDate.parsedate(value)
|
|
131
|
+
Time.send(Base.default_timezone, *time_array) rescue nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def cast_to_datetime(value)
|
|
135
|
+
return value.to_time if value.is_a?(DBI::Timestamp)
|
|
136
|
+
|
|
137
|
+
if value.is_a?(Time)
|
|
138
|
+
if value.year != 0 and value.month != 0 and value.day != 0
|
|
139
|
+
return value
|
|
140
|
+
else
|
|
141
|
+
return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if value.is_a?(DateTime)
|
|
146
|
+
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
|
|
150
|
+
value
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# TODO: Find less hack way to convert DateTime objects into Times
|
|
154
|
+
|
|
155
|
+
def self.string_to_time(value)
|
|
156
|
+
if value.is_a?(DateTime)
|
|
157
|
+
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
|
158
|
+
else
|
|
159
|
+
super
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
|
164
|
+
# because of a SQL Server statement length policy.
|
|
165
|
+
# def self.string_to_binary(value)
|
|
166
|
+
# value.gsub(/(\r|\n|\0|\x1a)/) do
|
|
167
|
+
# case $1
|
|
168
|
+
# when "\r" then "%00"
|
|
169
|
+
# when "\n" then "%01"
|
|
170
|
+
# when "\0" then "%02"
|
|
171
|
+
# when "\x1a" then "%03"
|
|
172
|
+
# end
|
|
173
|
+
# end
|
|
174
|
+
# end
|
|
175
|
+
#
|
|
176
|
+
# def self.binary_to_string(value)
|
|
177
|
+
# value.gsub(/(%00|%01|%02|%03)/) do
|
|
178
|
+
# case $1
|
|
179
|
+
# when "%00" then "\r"
|
|
180
|
+
# when "%01" then "\n"
|
|
181
|
+
# when "%02\0" then "\0"
|
|
182
|
+
# when "%03" then "\x1a"
|
|
183
|
+
# end
|
|
184
|
+
# end
|
|
185
|
+
# end
|
|
186
|
+
# end
|
|
187
|
+
|
|
188
|
+
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
|
189
|
+
# because of a SQL Server statement length policy.
|
|
190
|
+
# Convert strings to hex before storing in the database
|
|
191
|
+
def self.string_to_binary(value)
|
|
192
|
+
"0x#{value.unpack("H*")[0]}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def self.binary_to_string(value)
|
|
196
|
+
# TODO: Need to remove conditional pack (should always have to pack hex characters into blob)
|
|
197
|
+
# Assigning a value to a binary column causes the string_to_binary to hexify it
|
|
198
|
+
# This hex value is stored in the DB but the original value is retained in the
|
|
199
|
+
# cache. By forcing reload, the value coming into binary_to_string will always
|
|
200
|
+
# be hex. Need to force reload or update the cached column's value to match what is sent to the DB.
|
|
201
|
+
value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# In ADO mode, this adapter will ONLY work on Windows systems,
|
|
206
|
+
# since it relies on Win32OLE, which, to my knowledge, is only
|
|
207
|
+
# available on Windows.
|
|
208
|
+
#
|
|
209
|
+
# This mode also relies on the ADO support in the DBI module. If you are using the
|
|
210
|
+
# one-click installer of Ruby, then you already have DBI installed, but
|
|
211
|
+
# the ADO module is *NOT* installed. You will need to get the latest
|
|
212
|
+
# source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
|
|
213
|
+
# unzip it, and copy the file
|
|
214
|
+
# <tt>src/lib/dbd_ado/ADO.rb</tt>
|
|
215
|
+
# to
|
|
216
|
+
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
|
|
217
|
+
# (you will more than likely need to create the ADO directory).
|
|
218
|
+
# Once you've installed that file, you are ready to go.
|
|
219
|
+
#
|
|
220
|
+
# In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
|
|
221
|
+
# the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
|
|
222
|
+
# and it is available at http://www.ch-werner.de/rubyodbc/
|
|
223
|
+
#
|
|
224
|
+
# Options:
|
|
225
|
+
#
|
|
226
|
+
# * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
|
|
227
|
+
# * <tt>:username</tt> -- Defaults to sa.
|
|
228
|
+
# * <tt>:password</tt> -- Defaults to empty string.
|
|
229
|
+
# * <tt>:windows_auth</tt> -- Defaults to "User ID=#{username};Password=#{password}"
|
|
230
|
+
#
|
|
231
|
+
# ADO specific options:
|
|
232
|
+
#
|
|
233
|
+
# * <tt>:host</tt> -- Defaults to localhost.
|
|
234
|
+
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
|
235
|
+
# * <tt>:windows_auth</tt> -- Use windows authentication instead of username/password.
|
|
236
|
+
#
|
|
237
|
+
# ODBC specific options:
|
|
238
|
+
#
|
|
239
|
+
# * <tt>:dsn</tt> -- Defaults to nothing.
|
|
240
|
+
#
|
|
241
|
+
# ADO code tested on Windows 2000 and higher systems,
|
|
242
|
+
# running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
|
|
243
|
+
#
|
|
244
|
+
# ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
|
|
245
|
+
# unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
|
|
246
|
+
# [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
|
|
247
|
+
class SQLServerAdapter < AbstractAdapter
|
|
248
|
+
|
|
249
|
+
# add synchronization to adapter to prevent 'invalid cursor state' error
|
|
250
|
+
require 'sync'
|
|
251
|
+
def initialize(connection, logger, connection_options=nil)
|
|
252
|
+
super(connection, logger)
|
|
253
|
+
@connection_options = connection_options
|
|
254
|
+
@sql_connection_lock = Sync.new
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def native_database_types
|
|
258
|
+
{
|
|
259
|
+
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
|
|
260
|
+
:string => { :name => "varchar", :limit => 255 },
|
|
261
|
+
:text => { :name => "varchar(max)" },
|
|
262
|
+
:integer => { :name => "int" },
|
|
263
|
+
:float => { :name => "float", :limit => 8 },
|
|
264
|
+
:decimal => { :name => "decimal" },
|
|
265
|
+
:datetime => { :name => "datetime" },
|
|
266
|
+
:timestamp => { :name => "datetime" },
|
|
267
|
+
:time => { :name => "datetime" },
|
|
268
|
+
:date => { :name => "datetime" },
|
|
269
|
+
:binary => { :name => "varbinary(max)"},
|
|
270
|
+
:boolean => { :name => "bit"}
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def adapter_name
|
|
275
|
+
'SQLServer'
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def supports_migrations? #:nodoc:
|
|
279
|
+
true
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
|
283
|
+
return super unless type.to_s == 'integer'
|
|
284
|
+
|
|
285
|
+
if limit.nil? || limit == 4
|
|
286
|
+
'integer'
|
|
287
|
+
elsif limit < 4
|
|
288
|
+
'smallint'
|
|
289
|
+
else
|
|
290
|
+
'bigint'
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Returns a table's primary key and belonging sequence (not applicable to SQL server).
|
|
295
|
+
def pk_and_sequence_for(table_name)
|
|
296
|
+
@connection["AutoCommit"] = false
|
|
297
|
+
keys = []
|
|
298
|
+
execute("EXEC sp_helpindex '#{table_name}'") do |handle|
|
|
299
|
+
if handle.column_info.any?
|
|
300
|
+
pk_index = handle.detect {|index| index[1] =~ /primary key/ }
|
|
301
|
+
keys << pk_index[2] if pk_index
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
keys.length == 1 ? [keys.first, nil] : nil
|
|
305
|
+
ensure
|
|
306
|
+
@connection["AutoCommit"] = true
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# allows to set owner in table name with dot notation
|
|
310
|
+
def quote_table_name(name)
|
|
311
|
+
names = name.split('.')
|
|
312
|
+
names.size == 1 ? "[#{names[0]}]" : "[#{names[0]}].[#{names[1]}]"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# CONNECTION MANAGEMENT ====================================#
|
|
316
|
+
|
|
317
|
+
# Returns true if the connection is active.
|
|
318
|
+
def active?
|
|
319
|
+
@connection.execute("SELECT 1").finish
|
|
320
|
+
true
|
|
321
|
+
rescue DBI::DatabaseError, DBI::InterfaceError
|
|
322
|
+
false
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Reconnects to the database, returns false if no connection could be made.
|
|
326
|
+
def reconnect!
|
|
327
|
+
disconnect!
|
|
328
|
+
@connection = DBI.connect(*@connection_options)
|
|
329
|
+
rescue DBI::DatabaseError => e
|
|
330
|
+
@logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
|
|
331
|
+
false
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Disconnects from the database
|
|
335
|
+
|
|
336
|
+
def disconnect!
|
|
337
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
338
|
+
begin
|
|
339
|
+
@connection.disconnect
|
|
340
|
+
rescue nil
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Add synchronization for the db connection to ensure no one else is using this one
|
|
346
|
+
# prevents 'invalid cursor state' error
|
|
347
|
+
def select_rows(sql, name = nil)
|
|
348
|
+
rows = []
|
|
349
|
+
repair_special_columns(sql)
|
|
350
|
+
log(sql, name) do
|
|
351
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
352
|
+
@connection.select_all(sql) do |row|
|
|
353
|
+
record = []
|
|
354
|
+
row.each do |col|
|
|
355
|
+
if col.is_a? DBI::Timestamp
|
|
356
|
+
record << col.to_time
|
|
357
|
+
else
|
|
358
|
+
record << col
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
rows << record
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
rows
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def columns(table_name, name = nil)
|
|
369
|
+
return [] if table_name.blank?
|
|
370
|
+
table_name = table_name.to_s if table_name.is_a?(Symbol)
|
|
371
|
+
table_name = table_name.split('.')[-1] unless table_name.nil?
|
|
372
|
+
table_name = table_name.gsub(/[\[\]]/, '')
|
|
373
|
+
sql = %Q{
|
|
374
|
+
SELECT
|
|
375
|
+
clmns.name AS ColName,
|
|
376
|
+
object_definition(clmns.default_object_id) as DefaultValue,
|
|
377
|
+
CAST(clmns.scale AS int) AS numeric_scale,
|
|
378
|
+
CAST(clmns.precision AS int) AS numeric_precision,
|
|
379
|
+
usrt.name AS ColType,
|
|
380
|
+
case clmns.is_nullable when 0 then 'NO' else 'YES' end AS IsNullable,
|
|
381
|
+
CAST(CASE WHEN baset.name IN (N'nchar', N'nvarchar') AND clmns.max_length <> -1 THEN
|
|
382
|
+
clmns.max_length/2 ELSE clmns.max_length END AS int) AS Length,
|
|
383
|
+
clmns.is_identity as IsIdentity
|
|
384
|
+
FROM
|
|
385
|
+
sys.tables AS tbl
|
|
386
|
+
INNER JOIN sys.all_columns AS clmns ON clmns.object_id=tbl.object_id
|
|
387
|
+
LEFT OUTER JOIN sys.types AS usrt ON usrt.user_type_id = clmns.user_type_id
|
|
388
|
+
LEFT OUTER JOIN sys.types AS baset ON baset.user_type_id = clmns.system_type_id and
|
|
389
|
+
baset.user_type_id = baset.system_type_id
|
|
390
|
+
WHERE
|
|
391
|
+
(tbl.name=N'#{table_name}' )
|
|
392
|
+
ORDER BY
|
|
393
|
+
clmns.column_id ASC
|
|
394
|
+
}
|
|
395
|
+
# Comment out if you want to have the Columns select statment logged.
|
|
396
|
+
# Personally, I think it adds unnecessary bloat to the log.
|
|
397
|
+
# If you do comment it out, make sure to un-comment the "result" line that follows
|
|
398
|
+
result = log(sql, name) do
|
|
399
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
400
|
+
@connection.select_all(sql)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
#result = @connection.select_all(sql)
|
|
404
|
+
columns = []
|
|
405
|
+
result.each do |field|
|
|
406
|
+
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null|NULL/ ? nil : field[:DefaultValue]
|
|
407
|
+
if field[:ColType] =~ /numeric|decimal/i
|
|
408
|
+
type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
|
|
409
|
+
else
|
|
410
|
+
type = "#{field[:ColType]}(#{field[:Length]})"
|
|
411
|
+
end
|
|
412
|
+
is_identity = field[:IsIdentity] == 1
|
|
413
|
+
is_nullable = field[:IsNullable] == 'YES'
|
|
414
|
+
columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
|
|
415
|
+
end
|
|
416
|
+
columns
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
420
|
+
execute(sql, name)
|
|
421
|
+
id_value || select_one("SELECT scope_identity() AS Ident")["Ident"]
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def update(sql, name = nil)
|
|
425
|
+
execute(sql, name) do |handle|
|
|
426
|
+
handle.rows
|
|
427
|
+
end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
alias_method :delete, :update
|
|
431
|
+
|
|
432
|
+
# override execute to synchronize the connection
|
|
433
|
+
def execute(sql, name = nil)
|
|
434
|
+
if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
|
|
435
|
+
log(sql, name) do
|
|
436
|
+
with_identity_insert_enabled(table_name) do
|
|
437
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
438
|
+
@connection.execute(sql) do |handle|
|
|
439
|
+
yield(handle) if block_given?
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
else
|
|
445
|
+
log(sql, name) do
|
|
446
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
447
|
+
@connection.execute(sql) do |handle|
|
|
448
|
+
yield(handle) if block_given?
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Add synchronization for the db connection to ensure no one else is using this one
|
|
456
|
+
# prevents 'Could not change transaction status' error
|
|
457
|
+
def begin_db_transaction
|
|
458
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
459
|
+
begin
|
|
460
|
+
@connection["AutoCommit"] = false
|
|
461
|
+
rescue Exception => e
|
|
462
|
+
@connection["AutoCommit"] = true
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
def commit_db_transaction
|
|
467
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
468
|
+
begin
|
|
469
|
+
@connection.commit
|
|
470
|
+
ensure
|
|
471
|
+
@connection["AutoCommit"] = true
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def rollback_db_transaction
|
|
477
|
+
@sql_connection_lock.synchronize(:EX) do
|
|
478
|
+
begin
|
|
479
|
+
@connection.rollback
|
|
480
|
+
ensure
|
|
481
|
+
@connection["AutoCommit"] = true
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def quote(value, column = nil)
|
|
487
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
|
488
|
+
|
|
489
|
+
case value
|
|
490
|
+
when TrueClass then '1'
|
|
491
|
+
when FalseClass then '0'
|
|
492
|
+
else
|
|
493
|
+
if value.acts_like?(:time)
|
|
494
|
+
"'#{value.strftime("%Y%m%d %H:%M:%S")}'"
|
|
495
|
+
elsif value.acts_like?(:date)
|
|
496
|
+
"'#{value.strftime("%Y%m%d")}'"
|
|
497
|
+
else
|
|
498
|
+
super
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def quote_string(string)
|
|
504
|
+
string.gsub(/\'/, "''")
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def quoted_true
|
|
508
|
+
"1"
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def quoted_false
|
|
512
|
+
"0"
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def quote_column_name(name)
|
|
516
|
+
"[#{name}]"
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def add_limit_offset!(sql, options)
|
|
520
|
+
if options[:limit] and options[:offset]
|
|
521
|
+
total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i
|
|
522
|
+
if (options[:limit] + options[:offset]) >= total_rows
|
|
523
|
+
options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
|
|
524
|
+
end
|
|
525
|
+
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
|
|
526
|
+
sql << ") AS tmp1"
|
|
527
|
+
if options[:order]
|
|
528
|
+
options[:order] = options[:order].split(',').map do |field|
|
|
529
|
+
parts = field.split(" ")
|
|
530
|
+
tc = parts[0]
|
|
531
|
+
if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
|
|
532
|
+
tc.gsub!(/\./, '\\.\\[')
|
|
533
|
+
tc << '\\]'
|
|
534
|
+
end
|
|
535
|
+
if sql =~ /#{tc} AS (t\d_r\d\d?)/
|
|
536
|
+
parts[0] = $1
|
|
537
|
+
elsif parts[0] =~ /\w+\.(\w+)/
|
|
538
|
+
parts[0] = $1
|
|
539
|
+
end
|
|
540
|
+
parts.join(' ')
|
|
541
|
+
end.join(', ')
|
|
542
|
+
sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
|
|
543
|
+
else
|
|
544
|
+
sql << " ) AS tmp2"
|
|
545
|
+
end
|
|
546
|
+
elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
|
|
547
|
+
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
|
|
548
|
+
"SELECT#{$1} TOP #{options[:limit]}"
|
|
549
|
+
end unless options[:limit].nil?
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def recreate_database(name)
|
|
554
|
+
drop_database(name)
|
|
555
|
+
create_database(name)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def drop_database(name)
|
|
559
|
+
execute "DROP DATABASE #{name}"
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def create_database(name)
|
|
563
|
+
execute "CREATE DATABASE #{name}"
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def current_database
|
|
567
|
+
@connection.select_one("select DB_NAME()")[0]
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def tables(name = nil)
|
|
571
|
+
execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
|
|
572
|
+
sth.inject([]) do |tables, field|
|
|
573
|
+
table_name = field[0]
|
|
574
|
+
tables << table_name unless table_name == 'dtproperties'
|
|
575
|
+
tables
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def indexes(table_name, name = nil)
|
|
581
|
+
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false
|
|
582
|
+
indexes = []
|
|
583
|
+
execute("EXEC sp_helpindex '#{table_name}'", name) do |sth|
|
|
584
|
+
sth.each do |index|
|
|
585
|
+
unique = index[1] =~ /unique/
|
|
586
|
+
primary = index[1] =~ /primary key/
|
|
587
|
+
if !primary
|
|
588
|
+
indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
indexes
|
|
593
|
+
ensure
|
|
594
|
+
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def add_order_by_for_association_limiting!(sql, options)
|
|
598
|
+
# Just skip ORDER BY clause. I dont know better solution for DISTINCT plus ORDER BY.
|
|
599
|
+
# And this doesnt cause to much problem..
|
|
600
|
+
return sql
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def rename_table(name, new_name)
|
|
604
|
+
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Adds a new column to the named table.
|
|
608
|
+
# See TableDefinition#column for details of the options you can use.
|
|
609
|
+
def add_column(table_name, column_name, type, options = {})
|
|
610
|
+
add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
611
|
+
add_column_options!(add_column_sql, options)
|
|
612
|
+
# TODO: Add support to mimic date columns, using constraints to mark them as such in the database
|
|
613
|
+
# add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
|
|
614
|
+
execute(add_column_sql)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def rename_column(table, column, new_column_name)
|
|
618
|
+
execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
# database_statements line 108 Set the SQL specific rowlocking
|
|
622
|
+
# was previously generating invalid syntax for SQL server
|
|
623
|
+
def add_lock!(sql, options)
|
|
624
|
+
case lock = options[:lock]
|
|
625
|
+
when true then sql << "WITH(HOLDLOCK, ROWLOCK) "
|
|
626
|
+
when String then sql << "#{lock} "
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# Delete the default options if it's nil. Adapter was adding default NULL contraints
|
|
631
|
+
# to all columns which caused problems when trying to alter the column
|
|
632
|
+
def add_column_options!(sql, options) #:nodoc:
|
|
633
|
+
options.delete(:default) if options[:default].nil?
|
|
634
|
+
super
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# calculate column size to fix issue
|
|
638
|
+
# size XXXXX given to the column 'data' exceeds the maximum allowed for any data type (8000)
|
|
639
|
+
def column_total_size(table_name)
|
|
640
|
+
return nil if table_name.blank?
|
|
641
|
+
table_name = table_name.to_s if table_name.is_a?(Symbol)
|
|
642
|
+
table_name = table_name.split('.')[-1] unless table_name.nil?
|
|
643
|
+
table_name = table_name.gsub(/[\[\]]/, '')
|
|
644
|
+
sql = %Q{
|
|
645
|
+
SELECT SUM(COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME)) as Length
|
|
646
|
+
FROM INFORMATION_SCHEMA.COLUMNS cols
|
|
647
|
+
WHERE cols.TABLE_NAME = '#{table_name}'
|
|
648
|
+
}
|
|
649
|
+
# Comment out if you want to have the Columns select statment logged.
|
|
650
|
+
# Personally, I think it adds unnecessary bloat to the log. If you do
|
|
651
|
+
# comment it out, make sure to un-comment the "result" line that follows
|
|
652
|
+
result = log(sql, name) do
|
|
653
|
+
@sql_connection_lock.synchronize(:EX) { @connection.select_all(sql) }
|
|
654
|
+
end
|
|
655
|
+
field[:Length].to_i
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# if binary, calculate te the remaining amount for size
|
|
659
|
+
# issue: size XXXXX given to the column 'data' exceeds the maximum allowed for any data type (8000)
|
|
660
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
661
|
+
# $log.debug "change_column"
|
|
662
|
+
sql_commands = []
|
|
663
|
+
|
|
664
|
+
# Handle conversion of text columns to binary columns by first
|
|
665
|
+
# converting to varchar. We determine the amount of space left for the
|
|
666
|
+
# columns so we can get the most out of the conversion.
|
|
667
|
+
if type == :binary
|
|
668
|
+
col = self.columns(table_name, column_name)
|
|
669
|
+
sql_commands << "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(:string, 8000 - column_total_size(table_name))}" if col && col.type == :text
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
sql_commands << "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
673
|
+
if options_include_default?(options)
|
|
674
|
+
remove_default_constraint(table_name, column_name)
|
|
675
|
+
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
|
|
676
|
+
end
|
|
677
|
+
sql_commands.each {|c|
|
|
678
|
+
execute(c)
|
|
679
|
+
}
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def remove_column(table_name, column_name)
|
|
683
|
+
remove_check_constraints(table_name, column_name)
|
|
684
|
+
remove_default_constraint(table_name, column_name)
|
|
685
|
+
execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
def remove_default_constraint(table_name, column_name)
|
|
689
|
+
constraints = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
|
|
690
|
+
|
|
691
|
+
constraints.each do |constraint|
|
|
692
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def remove_check_constraints(table_name, column_name)
|
|
697
|
+
# TODO remove all constraints in single method
|
|
698
|
+
constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
|
|
699
|
+
constraints.each do |constraint|
|
|
700
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def remove_index(table_name, options = {})
|
|
705
|
+
execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
private
|
|
709
|
+
def select(sql, name = nil)
|
|
710
|
+
repair_special_columns(sql)
|
|
711
|
+
if match = query_has_limit_and_offset?(sql)
|
|
712
|
+
matched, limit, offset = *match
|
|
713
|
+
execute(sql)
|
|
714
|
+
# SET ROWCOUNT n causes all statements to only affect n rows, which we use
|
|
715
|
+
# to delete offset rows from the temporary table
|
|
716
|
+
execute("SET ROWCOUNT #{offset}")
|
|
717
|
+
execute("DELETE from #limit_offset_temp")
|
|
718
|
+
execute("SET ROWCOUNT 0")
|
|
719
|
+
result = execute_select("SELECT * FROM #limit_offset_temp")
|
|
720
|
+
execute("DROP TABLE #limit_offset_temp")
|
|
721
|
+
result
|
|
722
|
+
else
|
|
723
|
+
execute_select(sql)
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def execute_select(sql)
|
|
728
|
+
result = []
|
|
729
|
+
execute(sql) do |handle|
|
|
730
|
+
handle.each do |row|
|
|
731
|
+
row_hash = {}
|
|
732
|
+
row.each_with_index do |value, i|
|
|
733
|
+
if value.is_a? DBI::Timestamp
|
|
734
|
+
value = DateTime.new(value.year, value.month, value.day, value.hour, value.minute, value.sec)
|
|
735
|
+
end
|
|
736
|
+
row_hash[handle.column_names[i]] = value
|
|
737
|
+
end
|
|
738
|
+
result << row_hash
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
result
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
def query_has_limit_and_offset?(sql)
|
|
745
|
+
match = sql.match(/#limit_offset_temp -- limit => (\d+) offset => (\d+)/)
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# Turns IDENTITY_INSERT ON for table during execution of the block
|
|
749
|
+
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
|
750
|
+
# block has been executed without regard to its previous state
|
|
751
|
+
|
|
752
|
+
def with_identity_insert_enabled(table_name, &block)
|
|
753
|
+
set_identity_insert(table_name, true)
|
|
754
|
+
yield
|
|
755
|
+
ensure
|
|
756
|
+
set_identity_insert(table_name, false)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def set_identity_insert(table_name, enable = true)
|
|
760
|
+
execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
|
761
|
+
rescue Exception => e
|
|
762
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def get_table_name(sql)
|
|
766
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
|
767
|
+
$1
|
|
768
|
+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
|
769
|
+
$1
|
|
770
|
+
else
|
|
771
|
+
nil
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def identity_column(table_name)
|
|
776
|
+
@table_columns = {} unless @table_columns
|
|
777
|
+
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
|
|
778
|
+
@table_columns[table_name].each do |col|
|
|
779
|
+
return col.name if col.identity
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
return nil
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
def query_requires_identity_insert?(sql)
|
|
786
|
+
table_name = get_table_name(sql)
|
|
787
|
+
id_column = identity_column(table_name)
|
|
788
|
+
sql =~ /\[#{id_column}\]/ ? table_name : nil
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
def change_order_direction(order)
|
|
792
|
+
order.split(",").collect {|fragment|
|
|
793
|
+
case fragment
|
|
794
|
+
when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
|
|
795
|
+
when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
|
|
796
|
+
else String.new(fragment).split(',').join(' DESC,') + ' DESC'
|
|
797
|
+
end
|
|
798
|
+
}.join(",")
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def get_special_columns(table_name)
|
|
802
|
+
special = []
|
|
803
|
+
@table_columns ||= {}
|
|
804
|
+
@table_columns[table_name] ||= columns(table_name)
|
|
805
|
+
@table_columns[table_name].each do |col|
|
|
806
|
+
special << col.name if col.is_special
|
|
807
|
+
end
|
|
808
|
+
special
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def repair_special_columns(sql)
|
|
812
|
+
special_cols = get_special_columns(get_table_name(sql))
|
|
813
|
+
for col in special_cols.to_a
|
|
814
|
+
sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
|
|
815
|
+
sql.gsub!(/ORDER BY #{col.to_s}/i, '')
|
|
816
|
+
end
|
|
817
|
+
sql
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
end #class SQLServerAdapter < AbstractAdapter
|
|
821
|
+
|
|
822
|
+
# If value is a string and destination column is binary, don't quote the string for MS SQL
|
|
823
|
+
module Quoting
|
|
824
|
+
# Quotes the column value to help prevent
|
|
825
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
|
826
|
+
def quote(value, column = nil)
|
|
827
|
+
# records are quoted as their primary key
|
|
828
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
|
829
|
+
# puts "Type: #{column.type} Name: #{column.name}" if column
|
|
830
|
+
case value
|
|
831
|
+
when String, ActiveSupport::Multibyte::Chars
|
|
832
|
+
value = value.to_s
|
|
833
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
|
834
|
+
column.class.string_to_binary(value)
|
|
835
|
+
elsif column && [:integer, :float].include?(column.type)
|
|
836
|
+
value = column.type == :integer ? value.to_i : value.to_f
|
|
837
|
+
value.to_s
|
|
838
|
+
else
|
|
839
|
+
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
|
840
|
+
end
|
|
841
|
+
when NilClass then "NULL"
|
|
842
|
+
when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
|
|
843
|
+
when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
|
|
844
|
+
when Float, Fixnum, Bignum then value.to_s
|
|
845
|
+
# BigDecimals need to be output in a non-normalized form and quoted.
|
|
846
|
+
when BigDecimal then value.to_s('F')
|
|
847
|
+
when Date then "'#{value.to_s}'"
|
|
848
|
+
when Time, DateTime then "'#{quoted_date(value)}'"
|
|
849
|
+
else "'#{quote_string(value.to_yaml)}'"
|
|
850
|
+
end
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
end #module ConnectionAdapters
|
|
855
|
+
end #module ActiveRecord
|
metadata
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: akitaonrails-activerecord-sqlserver-adapter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tom Ward
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-08-13 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: activerecord
|
|
17
|
+
version_requirement:
|
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
19
|
+
requirements:
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 1.15.5.7843
|
|
23
|
+
version:
|
|
24
|
+
description:
|
|
25
|
+
email: tom@popdog.net
|
|
26
|
+
executables: []
|
|
27
|
+
|
|
28
|
+
extensions: []
|
|
29
|
+
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
|
|
32
|
+
files:
|
|
33
|
+
- lib/active_record/connection_adapters/sqlserver_adapter.rb
|
|
34
|
+
has_rdoc: false
|
|
35
|
+
homepage: http://wiki.rubyonrails.org/rails/pages/SQL+Server
|
|
36
|
+
post_install_message:
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: "0"
|
|
46
|
+
version:
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: "0"
|
|
52
|
+
version:
|
|
53
|
+
requirements: []
|
|
54
|
+
|
|
55
|
+
rubyforge_project: activerecord
|
|
56
|
+
rubygems_version: 1.2.0
|
|
57
|
+
signing_key:
|
|
58
|
+
specification_version: 2
|
|
59
|
+
summary: SQL Server adapter for Active Record
|
|
60
|
+
test_files: []
|
|
61
|
+
|