Guest User

Untitled

a guest
Mar 5th, 2018
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.26 KB | None | 0 0
  1. require 'active_record/connection_adapters/abstract_adapter'
  2. require 'mscorlib'
  3. require 'System.Data'
  4.  
  5. module System
  6. class DBNull
  7. def nil?
  8. true
  9. end
  10. end
  11. end
  12.  
  13. module ActiveRecord
  14. class Base
  15.  
  16. # Establishes a connection to the database that's used by all Active Record objects
  17. def self.mssql_connection(config) # :nodoc:
  18. config = config.symbolize_keys
  19. if config.has_key?(:connection_string)
  20. connection_string = config[:connection_string]
  21. else
  22. builder = System::Data::SqlClient::SqlConnectionStringBuilder.new
  23. builder.data_source = config[:host]
  24. builder.integrated_security = config[:integrated_security]
  25. if not builder.integrated_security
  26. builder.UserID = config[:username].to_s
  27. builder.password = config[:password].to_s
  28. end
  29. builder.initial_catalog = config[:database]
  30. connection_string = builder.connection_string
  31. end
  32. connection = System::Data::SqlClient::SqlConnection.new connection_string
  33. ConnectionAdapters::MSSQLAdapter.new(connection, logger, config)
  34. end
  35.  
  36. end
  37.  
  38. module ConnectionAdapters
  39. #
  40. # The IronRuby MSSQL adapter works with System.Data.SqlClient
  41. #
  42. class MSSQLAdapter < AbstractAdapter
  43.  
  44. # Returns 'MSSQL' as adapter name for identification purposes.
  45. def adapter_name
  46. 'MSSQL'
  47. end
  48.  
  49. # Initializes and connects a MSSQL adapter.
  50. def initialize(connection, logger, config)
  51. super(connection, logger)
  52. @config = config
  53. @transaction = nil
  54. connect
  55. end
  56.  
  57. # Is this connection alive and ready for queries?
  58. def active?
  59. @connection.state == System::Data::ConnectionState::open
  60. end
  61.  
  62. # Close then reopen the connection.
  63. def reconnect!
  64. if active?
  65. disconnect
  66. end
  67. connect
  68. end
  69.  
  70. # Close the connection.
  71. def disconnect!
  72. @connection.close rescue nil
  73. end
  74.  
  75. def native_database_types #:nodoc:
  76. {
  77. :primary_key => "int not null primary key",
  78. :string => { :name => "varchar", :limit => 255 },
  79. :text => { :name => "text" },
  80. :integer => { :name => "int" },
  81. :float => { :name => "float" },
  82. :decimal => { :name => "numeric" },
  83. :datetime => { :name => "datetime" },
  84. :timestamp => { :name => "datetime" },
  85. :time => { :name => "datetime" },
  86. :date => { :name => "datetime" },
  87. :binary => { :name => "image" },
  88. :boolean => { :name => "bit" }
  89. }
  90. end
  91.  
  92. # Does MSSQL support migrations?
  93. def supports_migrations?
  94. true
  95. end
  96.  
  97. # Does MSSQL support standard conforming strings?
  98. def supports_standard_conforming_strings?
  99. true
  100. end
  101.  
  102. # Returns the configured supported identifier length supported by MSSQL,
  103. def table_alias_length
  104. @table_alias_length ||= (query("select length from systypes where name = 'sysname'")[0][0].to_i)
  105. end
  106.  
  107. # QUOTING ==================================================
  108.  
  109. # Quotes MSSQL-specific data types for SQL input.
  110. def quote(value, column = nil) #:nodoc:
  111. if value.kind_of?(String) && column && column.type == :binary
  112. "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
  113. elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
  114. "xml '#{quote_string(value)}'"
  115. elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
  116. # Not truly string input, so doesn't require (or allow) escape string syntax.
  117. "'#{value.to_s}'"
  118. elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
  119. case value
  120. when /^[01]*$/
  121. "B'#{value}'" # Bit-string notation
  122. when /^[0-9A-F]*$/i
  123. "X'#{value}'" # Hexadecimal notation
  124. end
  125. elsif column && column.sql_type =~ /^boolean$/
  126. "'#{value ? 1 : 0}'"
  127. else
  128. super
  129. end
  130. end
  131.  
  132. # Double any single-quote characters in the string
  133. def quote_string(s) #:nodoc:
  134. s.gsub(/'/, "''") # ' (for ruby-mode)
  135. end
  136.  
  137. # Quotes column names for use in SQL queries.
  138. def quote_column_name(name) #:nodoc:
  139. '[' + name.to_s + ']'
  140. end
  141.  
  142. # Quotes table names for use in SQL queries.
  143. def quote_table_name(name)
  144. '[' + name.to_s + ']'
  145. end
  146.  
  147. # Quote date/time values for use in SQL input. Includes microseconds
  148. # if the value is a Time responding to usec.
  149. def quoted_date(value) #:nodoc:
  150. if value.acts_like?(:time) && value.respond_to?(:usec)
  151. "#{super}.#{sprintf("%06d", value.usec)}"
  152. else
  153. super
  154. end
  155. end
  156.  
  157. # REFERENTIAL INTEGRITY ====================================
  158.  
  159. def disable_referential_integrity(&block) #:nodoc:
  160. execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
  161. yield
  162. ensure
  163. execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
  164. end
  165.  
  166. # DATABASE STATEMENTS ======================================
  167.  
  168. # Executes a SELECT query and returns an array of rows. Each row is an
  169. # array of field values.
  170. def select_rows(sql, name = nil)
  171. select_raw(sql, name).last
  172. end
  173.  
  174. # Executes an INSERT query and returns the new record's ID
  175. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  176. table = sql.split(" ", 4)[2]
  177. super || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
  178. end
  179.  
  180. # Queries the database and returns the results in an Array or nil otherwise.
  181. def query(sql, name = nil) #:nodoc:
  182. #log(sql, name) do
  183. #TODO: @async
  184. select_rows sql, name
  185. end
  186.  
  187. # Executes an SQL statement
  188. def execute(sql, name = nil)
  189. #log(sql, name) do
  190. # TODO: @async
  191. begin
  192. command = System::Data::SqlClient::SqlCommand.new sql, @connection
  193. command.transaction = @transaction
  194. command.execute_non_query
  195. rescue System::Data::SqlClient::SqlException
  196. raise ActiveRecord::StatementInvalid, "#{$!}"
  197. end
  198. end
  199.  
  200. # Executes an UPDATE query and returns the number of affected tuples.
  201. def update_sql(sql, name = nil)
  202. super
  203. end
  204.  
  205. def add_limit_offset!(sql, options)
  206. if options[:limit] and options[:offset]
  207. 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
  208. if (options[:limit] + options[:offset]) >= total_rows
  209. options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
  210. end
  211. sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
  212. sql << ") AS tmp1"
  213. if options[:order]
  214. options[:order] = options[:order].split(',').map do |field|
  215. parts = field.split(" ")
  216. tc = parts[0]
  217. if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
  218. tc.gsub!(/\./, '\\.\\[')
  219. tc << '\\]'
  220. end
  221. if sql =~ /#{tc} AS (t\d_r\d\d?)/
  222. parts[0] = $1
  223. elsif parts[0] =~ /\w+\.(\w+)/
  224. parts[0] = $1
  225. end
  226. parts.join(' ')
  227. end.join(', ')
  228. sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
  229. else
  230. sql << " ) AS tmp2"
  231. end
  232. elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
  233. sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
  234. "SELECT#{$1} TOP #{options[:limit]}"
  235. end unless options[:limit].nil?
  236. end
  237. end
  238.  
  239. # Begins a transaction.
  240. def begin_db_transaction
  241. @transaction = @connection.begin_transaction
  242. end
  243.  
  244. # Commits a transaction.
  245. def commit_db_transaction
  246. @transaction.commit
  247. @transaction = nil
  248. end
  249.  
  250. # Aborts a transaction.
  251. def rollback_db_transaction
  252. return unless @transaction
  253. @transaction.rollback
  254. @transaction = nil
  255. end
  256.  
  257. # SCHEMA STATEMENTS ========================================
  258.  
  259. # Returns the list of all tables in the schema search path or a specified schema.
  260. def tables(name = nil)
  261. select_rows(<<-SQL, name).map { |row| row[0] }
  262. SELECT name
  263. FROM sysobjects
  264. WHERE type = 'U'
  265. SQL
  266. end
  267.  
  268. # Returns the list of all indexes for a table.
  269. def indexes(table_name, name = nil)
  270. result = query("exec sp_helpindex '#{table_name}'", name)
  271.  
  272. indexes = []
  273. result.each do |row|
  274. if row[1].match('primary key') == nil
  275. indexes << IndexDefinition.new(table_name, row[0], row[1].match('unique') != nil, row[2].split(',').each {|x| x.strip!})
  276. end
  277. end
  278.  
  279. indexes
  280. end
  281.  
  282. # Returns the list of all column definitions for a table.
  283. def columns(table_name, name = nil)
  284. # Limit, precision, and scale are all handled by the superclass.
  285. column_definitions(table_name).collect do |name, type, default, notnull|
  286. notnull = [false, true][notnull]
  287. Column.new(name, default, type, notnull)
  288. end
  289. end
  290.  
  291. # Sets the schema search path to a string of comma-separated schema names.
  292. # Names beginning with $ have to be quoted (e.g. $user => '$user').
  293. #
  294. # This should be not be called manually but set in database.yml.
  295. def schema_search_path=(schema_csv)
  296. end
  297.  
  298. # Returns the active schema search path.
  299. def schema_search_path
  300. 'dbo'
  301. end
  302.  
  303. # Renames a table.
  304. def rename_table(name, new_name)
  305. execute "exec sp_rename '#{name}', '#{new_name}'"
  306. end
  307.  
  308. # Adds a column to a table.
  309. def add_column(table_name, column_name, type, options = {})
  310. if options_include_default?(options)
  311. default = 'default ' + quote(options[:default])
  312. else
  313. default = ''
  314. end
  315. notnull = options[:null] == false
  316.  
  317. # Add the column.
  318. execute("ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])} #{notnull ? 'NOT NULL' : 'NULL'} #{default}")
  319. end
  320.  
  321. # Changes the column of a table.
  322. def change_column(table_name, column_name, type, options = {})
  323. begin
  324. execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
  325. rescue ActiveRecord::StatementInvalid
  326. # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
  327. begin_db_transaction
  328. tmp_column_name = "#{column_name}_ar_tmp"
  329. add_column(table_name, tmp_column_name, type, options)
  330. execute "UPDATE #{table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
  331. remove_column(table_name, column_name)
  332. rename_column(table_name, tmp_column_name, column_name)
  333. commit_db_transaction
  334. end
  335.  
  336. change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
  337. change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
  338. end
  339.  
  340. # Changes the default value of a table column.
  341. def change_column_default(table_name, column_name, default)
  342. print "change_column_default to '#{default}'\n"
  343. execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
  344. end
  345.  
  346. def change_column_null(table_name, column_name, null, default = nil)
  347. unless null || default.nil?
  348. execute("UPDATE #{table_name} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
  349. end
  350. execute("ALTER TABLE #{table_name} ALTER #{quote_column_name(column_name)} #{null ? 'NULL' : 'NOT NULL'}")
  351. end
  352.  
  353. # Renames a column in a table.
  354. def rename_column(table_name, column_name, new_column_name)
  355. execute "exec sp_rename '#{table_name}.#{column_name}', '#{new_column_name}'"
  356. end
  357.  
  358. # Drops an index from a table.
  359. def remove_index(table_name, options = {})
  360. execute "DROP INDEX #{index_name(table_name, options)}"
  361. end
  362.  
  363. # Maps logical Rails types to MSSQL-specific data types.
  364. def type_to_sql(type, limit = nil, precision = nil, scale = nil)
  365. return super unless type.to_s == 'integer'
  366.  
  367. if limit.nil? || limit == 4
  368. 'integer'
  369. elsif limit < 4
  370. 'smallint'
  371. else
  372. 'bigint'
  373. end
  374. end
  375.  
  376. protected
  377. # Returns the version of the connected SQL Server.
  378. def mssql_version
  379. @mssql_version ||=
  380. begin
  381. query('SELECT @@version')[0][0] =~ /(\d+)\.(\d+)\.(\d+).(\d+)/
  382. [$1, $2, $3, $4]
  383. rescue
  384. [0, 0, 0, 0]
  385. end
  386. end
  387.  
  388. private
  389.  
  390. # Connects to SQL Server
  391. def connect
  392. @connection.open
  393. end
  394.  
  395. # Returns the current ID of a table's sequence.
  396. def last_insert_id(table, sequence_name) #:nodoc:
  397. identity = select_value("SELECT scope_identity()")
  398. if identity.class == System::DBNull
  399. nil
  400. else
  401. System::Convert.to_int32(identity)
  402. end
  403. end
  404.  
  405. # Executes a SELECT query and returns the results, performing any data type
  406. # conversions that are required to be performed here
  407. def select(sql, name = nil)
  408. fields, rows = select_raw(sql, name)
  409. result = []
  410. for row in rows
  411. row_hash = {}
  412. fields.each_with_index do |f, i|
  413. val = row[i]
  414. row_hash[f.to_s] = val.respond_to?(:rstrip) ? val.rstrip : val
  415. end
  416. result << row_hash
  417. end
  418. result
  419. end
  420.  
  421. def select_raw(sql, name = nil)
  422. reader = nil
  423. begin
  424. command = System::Data::SqlClient::SqlCommand.new sql, @connection
  425. command.transaction = @transaction
  426. reader = command.execute_reader
  427. fields = []
  428. schema = reader.get_schema_table
  429. if schema != nil
  430. for row in reader.get_schema_table.rows
  431. fields << row.item(0)
  432. end
  433. end
  434.  
  435. rows = []
  436. while reader.read
  437. row = []
  438. for i in (1..fields.length)
  439. value = reader.get_value(i - 1)
  440. if value.class == System::String
  441. value = value.to_s
  442. end
  443. row << value
  444. end
  445. rows << row
  446. end
  447. return fields, rows
  448. rescue System::Data::SqlClient::SqlException
  449. raise ActiveRecord::StatementInvalid, "#{$!}"
  450. ensure
  451. if reader != nil
  452. reader.close
  453. end
  454. end
  455. end
  456.  
  457.  
  458. # Returns the list of a table's column names, data types, and default values.
  459. #
  460. def column_definitions(table_name) #:nodoc:
  461. query <<-end_sql
  462. select
  463. c.name,
  464. case
  465. when t.name in ('char', 'varchar', 'nchar', 'nvarchar') then 'string'
  466. when t.name in ('binary', 'varbinary', 'image') then 'binary'
  467. when t.name in ('int', 'smallint', 'tinyint') then 'integer'
  468. when t.name in ('datetime', 'smalldatetime') then 'datetime'
  469. when t.name = 'bit' then 'boolean'
  470. when t.name = 'numeric' and c.prec < 10 and c.scale = 0 then 'integer'
  471. when t.name = 'numeric' then 'decimal'
  472. when t.name = 'text' then 'text'
  473. else t.name
  474. end type,
  475. d.text,
  476. c.isnullable
  477. from
  478. syscolumns c
  479. inner join systypes t
  480. on c.xusertype = t.xusertype
  481. left outer join syscomments d
  482. on c.cdefault = d.id
  483. where
  484. c.id = object_id('#{table_name}')
  485. order by
  486. c.colid
  487. end_sql
  488. end
  489. end
  490. end
  491. end
Add Comment
Please, Sign In to add comment