1 |
1 |
require 'active_record/connection_adapters/abstract_adapter'
|
2 |
2 |
|
3 |
|
require 'base64'
|
4 |
3 |
require 'bigdecimal'
|
5 |
4 |
require 'bigdecimal/util'
|
6 |
5 |
|
... | ... | |
42 |
41 |
raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
|
43 |
42 |
database = config[:database]
|
44 |
43 |
host = config[:host] ? config[:host].to_s : 'localhost'
|
45 |
|
driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User ID=#{username};Password=#{password};"
|
|
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
|
46 |
49 |
end
|
47 |
50 |
conn = DBI.connect(driver_url, username, password)
|
48 |
51 |
conn["AutoCommit"] = autocommit
|
... | ... | |
60 |
63 |
@is_special = sql_type =~ /text|ntext|image/i
|
61 |
64 |
# TODO: check ok to remove @scale = scale_value
|
62 |
65 |
# SQL Server only supports limits on *char and float types
|
63 |
|
@limit = nil unless @type == :float or @type == :string
|
|
66 |
@limit = nil unless @type == :string
|
64 |
67 |
end
|
65 |
68 |
|
66 |
69 |
def simplified_type(field_type)
|
67 |
70 |
case field_type
|
68 |
|
when /real/i then :float
|
69 |
71 |
when /money/i then :decimal
|
70 |
72 |
when /image/i then :binary
|
71 |
73 |
when /bit/i then :boolean
|
... | ... | |
79 |
81 |
case type
|
80 |
82 |
when :datetime then cast_to_datetime(value)
|
81 |
83 |
when :timestamp then cast_to_time(value)
|
82 |
|
when :time then cast_to_time(value)
|
83 |
|
when :date then cast_to_datetime(value)
|
84 |
84 |
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
85 |
85 |
else super
|
86 |
86 |
end
|
87 |
87 |
end
|
88 |
|
|
|
88 |
|
89 |
89 |
def cast_to_time(value)
|
90 |
90 |
return value if value.is_a?(Time)
|
91 |
91 |
time_array = ParseDate.parsedate(value)
|
... | ... | |
105 |
105 |
|
106 |
106 |
if value.is_a?(DateTime)
|
107 |
107 |
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
108 |
|
#return DateTime.new(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
109 |
108 |
end
|
110 |
109 |
|
111 |
110 |
return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
|
... | ... | |
114 |
113 |
|
115 |
114 |
# TODO: Find less hack way to convert DateTime objects into Times
|
116 |
115 |
|
117 |
|
def self.string_to_time(value)
|
118 |
|
if value.is_a?(DateTime)
|
119 |
|
return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
120 |
|
else
|
121 |
|
super
|
|
116 |
# def self.string_to_time(value)
|
|
117 |
# if value.is_a?(DateTime)
|
|
118 |
# return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
|
|
119 |
# else
|
|
120 |
# super
|
|
121 |
# end
|
|
122 |
# end
|
|
123 |
|
|
124 |
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
|
125 |
# because of a SQL Server statement length policy.
|
|
126 |
def self.string_to_binary(value)
|
|
127 |
value.gsub(/(\r|\n|\0|\x1a)/) do
|
|
128 |
case $1
|
|
129 |
when "\r" then "%00"
|
|
130 |
when "\n" then "%01"
|
|
131 |
when "\0" then "%02"
|
|
132 |
when "\x1a" then "%03"
|
|
133 |
end
|
122 |
134 |
end
|
123 |
135 |
end
|
124 |
136 |
|
125 |
|
# These methods will only allow the adapter to insert binary data with a length of 7K or less
|
126 |
|
# because of a SQL Server statement length policy.
|
127 |
|
def self.string_to_binary(value)
|
128 |
|
Base64.encode64(value)
|
129 |
|
end
|
130 |
|
|
131 |
|
def self.binary_to_string(value)
|
132 |
|
Base64.decode64(value)
|
133 |
|
end
|
|
137 |
def self.binary_to_string(value)
|
|
138 |
value.gsub(/(%00|%01|%02|%03)/) do
|
|
139 |
case $1
|
|
140 |
when "%00" then "\r"
|
|
141 |
when "%01" then "\n"
|
|
142 |
when "%02\0" then "\0"
|
|
143 |
when "%03" then "\x1a"
|
|
144 |
end
|
|
145 |
end
|
|
146 |
end
|
134 |
147 |
end
|
135 |
148 |
|
136 |
149 |
# In ADO mode, this adapter will ONLY work on Windows systems,
|
... | ... | |
157 |
170 |
# * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
|
158 |
171 |
# * <tt>:username</tt> -- Defaults to sa.
|
159 |
172 |
# * <tt>:password</tt> -- Defaults to empty string.
|
160 |
|
# * <tt>:windows_auth</tt> -- Defaults to "User ID=#{username};Password=#{password}"
|
161 |
173 |
#
|
162 |
174 |
# ADO specific options:
|
163 |
175 |
#
|
164 |
176 |
# * <tt>:host</tt> -- Defaults to localhost.
|
165 |
177 |
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
166 |
|
# * <tt>:windows_auth</tt> -- Use windows authentication instead of username/password.
|
167 |
178 |
#
|
168 |
179 |
# ODBC specific options:
|
169 |
180 |
#
|
... | ... | |
186 |
197 |
{
|
187 |
198 |
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
|
188 |
199 |
:string => { :name => "varchar", :limit => 255 },
|
189 |
|
:text => { :name => "text" },
|
|
200 |
:text => { :name => "varchar", :limit => "MAX"},
|
190 |
201 |
:integer => { :name => "int" },
|
191 |
|
:float => { :name => "float", :limit => 8 },
|
|
202 |
:float => { :name => "float" },
|
192 |
203 |
:decimal => { :name => "decimal" },
|
193 |
204 |
:datetime => { :name => "datetime" },
|
194 |
205 |
:timestamp => { :name => "datetime" },
|
195 |
|
:time => { :name => "datetime" },
|
196 |
|
:date => { :name => "datetime" },
|
197 |
|
:binary => { :name => "image"},
|
|
206 |
:time => { :name => "time" },
|
|
207 |
:date => { :name => "date" },
|
|
208 |
:binary => { :name => "varchar", :limit => "MAX"},
|
198 |
209 |
:boolean => { :name => "bit"}
|
199 |
210 |
}
|
200 |
211 |
end
|
... | ... | |
244 |
255 |
@connection.disconnect rescue nil
|
245 |
256 |
end
|
246 |
257 |
|
247 |
|
def select_rows(sql, name = nil)
|
|
258 |
def select_rows(sql, name = nil)
|
248 |
259 |
rows = []
|
249 |
260 |
repair_special_columns(sql)
|
250 |
261 |
log(sql, name) do
|
... | ... | |
261 |
272 |
end
|
262 |
273 |
end
|
263 |
274 |
rows
|
264 |
|
end
|
265 |
|
|
266 |
|
def columns(table_name, name = nil)
|
|
275 |
end
|
|
276 |
|
|
277 |
def columns(table_name, name = nil)
|
267 |
278 |
return [] if table_name.blank?
|
268 |
279 |
table_name = table_name.to_s if table_name.is_a?(Symbol)
|
269 |
280 |
table_name = table_name.split('.')[-1] unless table_name.nil?
|
270 |
281 |
table_name = table_name.gsub(/[\[\]]/, '')
|
271 |
282 |
sql = %Q{
|
272 |
|
SELECT
|
273 |
|
cols.COLUMN_NAME as ColName,
|
274 |
|
cols.COLUMN_DEFAULT as DefaultValue,
|
275 |
|
cols.NUMERIC_SCALE as numeric_scale,
|
276 |
|
cols.NUMERIC_PRECISION as numeric_precision,
|
277 |
|
cols.DATA_TYPE as ColType,
|
278 |
|
cols.IS_NULLABLE As IsNullable,
|
279 |
|
COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
|
280 |
|
COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
|
281 |
|
cols.NUMERIC_SCALE as Scale
|
282 |
|
FROM INFORMATION_SCHEMA.COLUMNS cols
|
283 |
|
WHERE cols.TABLE_NAME = '#{table_name}'
|
|
283 |
SELECT
|
|
284 |
clmns.name AS ColName,
|
|
285 |
object_definition(clmns.default_object_id) as DefaultValue,
|
|
286 |
CAST(clmns.scale AS int) AS numeric_scale,
|
|
287 |
CAST(clmns.precision AS int) AS numeric_precision,
|
|
288 |
usrt.name AS ColType,
|
|
289 |
case clmns.is_nullable when 0 then 'NO' else 'YES' end AS IsNullable,
|
|
290 |
CAST(CASE WHEN baset.name IN (N'nchar', N'nvarchar') AND clmns.max_length <> -1 THEN
|
|
291 |
clmns.max_length/2 ELSE clmns.max_length END AS int) AS Length,
|
|
292 |
clmns.is_identity as IsIdentity
|
|
293 |
FROM
|
|
294 |
sys.tables AS tbl
|
|
295 |
INNER JOIN sys.all_columns AS clmns ON clmns.object_id=tbl.object_id
|
|
296 |
LEFT OUTER JOIN sys.types AS usrt ON usrt.user_type_id = clmns.user_type_id
|
|
297 |
LEFT OUTER JOIN sys.types AS baset ON baset.user_type_id = clmns.system_type_id and
|
|
298 |
baset.user_type_id = baset.system_type_id
|
|
299 |
WHERE
|
|
300 |
(tbl.name=N'#{table_name}' )
|
|
301 |
ORDER BY
|
|
302 |
clmns.column_id ASC
|
284 |
303 |
}
|
285 |
304 |
# Comment out if you want to have the Columns select statment logged.
|
286 |
305 |
# Personally, I think it adds unnecessary bloat to the log.
|
... | ... | |
289 |
308 |
#result = @connection.select_all(sql)
|
290 |
309 |
columns = []
|
291 |
310 |
result.each do |field|
|
292 |
|
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/i ? nil : field[:DefaultValue]
|
|
311 |
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null|NULL/ ? nil : field[:DefaultValue]
|
293 |
312 |
if field[:ColType] =~ /numeric|decimal/i
|
294 |
313 |
type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
|
295 |
314 |
else
|
... | ... | |
301 |
320 |
end
|
302 |
321 |
columns
|
303 |
322 |
end
|
304 |
|
|
305 |
|
def empty_insert_statement(table_name)
|
306 |
|
"INSERT INTO #{table_name} DEFAULT VALUES"
|
307 |
|
end
|
308 |
323 |
|
309 |
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
310 |
|
super || select_value("SELECT @@IDENTITY AS Ident")
|
|
324 |
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
325 |
execute(sql, name)
|
|
326 |
id_value || select_one("SELECT scope_identity() AS Ident")["Ident"]
|
311 |
327 |
end
|
312 |
328 |
|
313 |
|
def update_sql(sql, name = nil)
|
314 |
|
autoCommiting = @connection["AutoCommit"]
|
315 |
|
begin
|
316 |
|
begin_db_transaction if autoCommiting
|
317 |
|
execute(sql, name)
|
318 |
|
affectedRows = select_value("SELECT @@ROWCOUNT AS AffectedRows")
|
319 |
|
commit_db_transaction if autoCommiting
|
320 |
|
affectedRows
|
321 |
|
rescue
|
322 |
|
rollback_db_transaction if autoCommiting
|
323 |
|
raise
|
324 |
|
end
|
|
329 |
def update(sql, name = nil)
|
|
330 |
execute(sql, name) do |handle|
|
|
331 |
handle.rows
|
|
332 |
end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
|
325 |
333 |
end
|
|
334 |
|
|
335 |
alias_method :delete, :update
|
326 |
336 |
|
327 |
337 |
def execute(sql, name = nil)
|
328 |
338 |
if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
|
... | ... | |
366 |
376 |
case value
|
367 |
377 |
when TrueClass then '1'
|
368 |
378 |
when FalseClass then '0'
|
369 |
|
else
|
370 |
|
if value.acts_like?(:time)
|
371 |
|
"'#{value.strftime("%Y%m%d %H:%M:%S")}'"
|
372 |
|
elsif value.acts_like?(:date)
|
373 |
|
"'#{value.strftime("%Y%m%d")}'"
|
374 |
|
else
|
375 |
|
super
|
376 |
|
end
|
|
379 |
when Time, DateTime then "'#{value.strftime("%Y%m%d %H:%M:%S")}'"
|
|
380 |
when Date then "'#{value.strftime("%Y%m%d")}'"
|
|
381 |
else super
|
377 |
382 |
end
|
378 |
383 |
end
|
379 |
384 |
|
... | ... | |
381 |
386 |
string.gsub(/\'/, "''")
|
382 |
387 |
end
|
383 |
388 |
|
384 |
|
def quote_column_name(name)
|
385 |
|
"[#{name}]"
|
|
389 |
def quoted_true
|
|
390 |
"1"
|
386 |
391 |
end
|
387 |
392 |
|
388 |
|
def add_limit_offset!(sql, options)
|
389 |
|
if options[:limit] and options[:offset]
|
390 |
|
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
|
391 |
|
if (options[:limit] + options[:offset]) >= total_rows
|
392 |
|
options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
|
393 |
|
end
|
394 |
|
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
|
395 |
|
sql << ") AS tmp1"
|
396 |
|
if options[:order]
|
397 |
|
order = options[:order].split(',').map do |field|
|
398 |
|
parts = field.split(" ")
|
399 |
|
tc = parts[0]
|
400 |
|
if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
|
401 |
|
tc.gsub!(/\./, '\\.\\[')
|
402 |
|
tc << '\\]'
|
403 |
|
end
|
404 |
|
if sql =~ /#{tc} AS (t\d_r\d\d?)/
|
405 |
|
parts[0] = $1
|
406 |
|
elsif parts[0] =~ /\w+\.(\w+)/
|
407 |
|
parts[0] = $1
|
408 |
|
end
|
409 |
|
parts.join(' ')
|
410 |
|
end.join(', ')
|
411 |
|
sql << " ORDER BY #{change_order_direction(order)}) AS tmp2 ORDER BY #{order}"
|
412 |
|
else
|
413 |
|
sql << " ) AS tmp2"
|
414 |
|
end
|
415 |
|
elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
|
416 |
|
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
|
417 |
|
"SELECT#{$1} TOP #{options[:limit]}"
|
418 |
|
end unless options[:limit].nil?
|
419 |
|
end
|
|
393 |
def quoted_false
|
|
394 |
"0"
|
420 |
395 |
end
|
421 |
396 |
|
422 |
|
def add_lock!(sql, options)
|
423 |
|
@logger.info "Warning: SQLServer :lock option '#{options[:lock].inspect}' not supported" if @logger && options.has_key?(:lock)
|
424 |
|
sql
|
|
397 |
def quote_column_name(name)
|
|
398 |
"[#{name}]"
|
425 |
399 |
end
|
426 |
400 |
|
|
401 |
def add_limit_offset!(sql, options)
|
|
402 |
if options[:limit] && options[:offset] && options[:offset] > 0
|
|
403 |
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) {"SELECT#{$1} TOP #{options[:limit] + options[:offset]} "}
|
|
404 |
sql.sub!(/ FROM /i, " INTO #limit_offset_temp -- limit => #{options[:limit]} offset => #{options[:offset]} \n FROM ")
|
|
405 |
elsif options[:limit] && (sql !~ /^\s*SELECT (@@|COUNT\()/i)
|
|
406 |
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) {"SELECT#{$1} TOP #{options[:limit]} "}
|
|
407 |
end
|
|
408 |
end
|
|
409 |
|
427 |
410 |
def recreate_database(name)
|
428 |
411 |
drop_database(name)
|
429 |
412 |
create_database(name)
|
... | ... | |
443 |
426 |
|
444 |
427 |
def tables(name = nil)
|
445 |
428 |
execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
|
446 |
|
result = sth.inject([]) do |tables, field|
|
|
429 |
sth.inject([]) do |tables, field|
|
447 |
430 |
table_name = field[0]
|
448 |
|
tables << table_name unless table_name == 'dtproperties'
|
|
431 |
tables << table_name unless table_name == 'dtproperties'
|
449 |
432 |
tables
|
450 |
433 |
end
|
451 |
434 |
end
|
... | ... | |
454 |
437 |
def indexes(table_name, name = nil)
|
455 |
438 |
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false
|
456 |
439 |
indexes = []
|
457 |
|
execute("EXEC sp_helpindex '#{table_name}'", name) do |handle|
|
458 |
|
if handle.column_info.any?
|
459 |
|
handle.each do |index|
|
460 |
|
unique = index[1] =~ /unique/
|
461 |
|
primary = index[1] =~ /primary key/
|
462 |
|
if !primary
|
463 |
|
indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", ").map {|e| e.gsub('(-)','')})
|
464 |
|
end
|
|
440 |
execute("EXEC sp_helpindex '#{table_name}'", name) do |sth|
|
|
441 |
sth.each do |index|
|
|
442 |
unique = index[1] =~ /unique/
|
|
443 |
primary = index[1] =~ /primary key/
|
|
444 |
if !primary
|
|
445 |
indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
|
465 |
446 |
end
|
466 |
447 |
end
|
467 |
448 |
end
|
468 |
449 |
indexes
|
469 |
|
ensure
|
470 |
|
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
|
|
450 |
ensure
|
|
451 |
ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
|
471 |
452 |
end
|
|
453 |
|
|
454 |
def add_order_by_for_association_limiting!(sql, options)
|
|
455 |
# Just skip ORDER BY clause. I dont know better solution for DISTINCT plus ORDER BY.
|
|
456 |
# And this doesnt cause to much problem..
|
|
457 |
return sql
|
|
458 |
end
|
472 |
459 |
|
473 |
460 |
def rename_table(name, new_name)
|
474 |
461 |
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
475 |
462 |
end
|
476 |
|
|
|
463 |
|
|
464 |
# Adds a new column to the named table.
|
|
465 |
# See TableDefinition#column for details of the options you can use.
|
477 |
466 |
def add_column(table_name, column_name, type, options = {})
|
478 |
|
add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
467 |
add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
479 |
468 |
add_column_options!(add_column_sql, options)
|
480 |
469 |
# TODO: Add support to mimic date columns, using constraints to mark them as such in the database
|
481 |
470 |
# 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
|
... | ... | |
487 |
476 |
end
|
488 |
477 |
|
489 |
478 |
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
490 |
|
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
491 |
|
sql << " NOT NULL" if options[:null] == false
|
492 |
|
sql_commands = [sql]
|
|
479 |
sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
|
493 |
480 |
if options_include_default?(options)
|
494 |
481 |
remove_default_constraint(table_name, column_name)
|
495 |
|
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{quote_column_name(column_name)}"
|
|
482 |
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
|
496 |
483 |
end
|
497 |
484 |
sql_commands.each {|c|
|
498 |
485 |
execute(c)
|
499 |
486 |
}
|
500 |
487 |
end
|
501 |
488 |
|
502 |
|
def change_column_default(table_name, column_name, default)
|
503 |
|
remove_default_constraint(table_name, column_name)
|
504 |
|
execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default, column_name)} FOR #{quote_column_name(column_name)}"
|
505 |
|
end
|
506 |
|
|
507 |
489 |
def remove_column(table_name, column_name)
|
508 |
490 |
remove_check_constraints(table_name, column_name)
|
509 |
491 |
remove_default_constraint(table_name, column_name)
|
510 |
|
execute "ALTER TABLE [#{table_name}] DROP COLUMN #{quote_column_name(column_name)}"
|
|
492 |
execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
|
511 |
493 |
end
|
512 |
494 |
|
513 |
495 |
def remove_default_constraint(table_name, column_name)
|
... | ... | |
533 |
515 |
private
|
534 |
516 |
def select(sql, name = nil)
|
535 |
517 |
repair_special_columns(sql)
|
536 |
|
|
|
518 |
if match = query_has_limit_and_offset?(sql)
|
|
519 |
matched, limit, offset = *match
|
|
520 |
execute(sql)
|
|
521 |
# SET ROWCOUNT n causes all statements to only affect n rows, which we use
|
|
522 |
# to delete offset rows from the temporary table
|
|
523 |
execute("SET ROWCOUNT #{offset}")
|
|
524 |
execute("DELETE from #limit_offset_temp")
|
|
525 |
execute("SET ROWCOUNT 0")
|
|
526 |
result = execute_select("SELECT * FROM #limit_offset_temp")
|
|
527 |
execute("DROP TABLE #limit_offset_temp")
|
|
528 |
result
|
|
529 |
else
|
|
530 |
execute_select(sql)
|
|
531 |
end
|
|
532 |
end
|
|
533 |
|
|
534 |
def execute_select(sql)
|
537 |
535 |
result = []
|
538 |
536 |
execute(sql) do |handle|
|
539 |
537 |
handle.each do |row|
|
... | ... | |
550 |
548 |
result
|
551 |
549 |
end
|
552 |
550 |
|
|
551 |
def query_has_limit_and_offset?(sql)
|
|
552 |
match = sql.match(/#limit_offset_temp -- limit => (\d+) offset => (\d+)/)
|
|
553 |
end
|
|
554 |
|
553 |
555 |
# Turns IDENTITY_INSERT ON for table during execution of the block
|
554 |
556 |
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
555 |
557 |
# block has been executed without regard to its previous state
|