Guest User

Untitled

a guest
Mar 3rd, 2018
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.93 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 identity(1,1) 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.sql_type =~ /^xml$/
  112. "xml '#{quote_string(value)}'"
  113. elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
  114. # Not truly string input, so doesn't require (or allow) escape string syntax.
  115. "'#{value.to_s}'"
  116. elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
  117. case value
  118. when /^[01]*$/
  119. "B'#{value}'" # Bit-string notation
  120. when /^[0-9A-F]*$/i
  121. "X'#{value}'" # Hexadecimal notation
  122. end
  123. elsif column && column.sql_type =~ /^datetime$/
  124. "'#{quoted_date(value)}'"
  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. # REFERENTIAL INTEGRITY ====================================
  148.  
  149. def disable_referential_integrity(&block) #:nodoc:
  150. execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
  151. yield
  152. ensure
  153. execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
  154. end
  155.  
  156. # DATABASE STATEMENTS ======================================
  157.  
  158. # Executes a SELECT query and returns an array of rows. Each row is an
  159. # array of field values.
  160. def select_rows(sql, name = nil)
  161. select_raw(sql, name).last
  162. end
  163.  
  164. # Executes an INSERT query and returns the new record's ID
  165. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  166. table = sql.split(" ", 4)[2]
  167. super || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
  168. end
  169.  
  170. # Queries the database and returns the results in an Array or nil otherwise.
  171. def query(sql, name = nil) #:nodoc:
  172. #log(sql, name) do
  173. #TODO: @async
  174. select_rows sql, name
  175. end
  176.  
  177. # Executes an SQL statement
  178. def execute(sql, name = nil)
  179. #log(sql, name) do
  180. # TODO: @async
  181. begin
  182. command = System::Data::SqlClient::SqlCommand.new sql, @connection
  183. command.transaction = @transaction
  184. command.execute_non_query
  185. rescue System::Data::SqlClient::SqlException
  186. raise ActiveRecord::StatementInvalid, "#{$!}"
  187. end
  188. end
  189.  
  190. # Executes an UPDATE query and returns the number of affected tuples.
  191. def update_sql(sql, name = nil)
  192. super
  193. end
  194.  
  195. def add_limit_offset!(sql, options)
  196. if options[:limit] and options[:offset]
  197. 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
  198. if (options[:limit] + options[:offset]) >= total_rows
  199. options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
  200. end
  201. sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
  202. sql << ") AS tmp1"
  203. if options[:order]
  204. options[:order] = options[:order].split(',').map do |field|
  205. parts = field.split(" ")
  206. tc = parts[0]
  207. if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
  208. tc.gsub!(/\./, '\\.\\[')
  209. tc << '\\]'
  210. end
  211. if sql =~ /#{tc} AS (t\d_r\d\d?)/
  212. parts[0] = $1
  213. elsif parts[0] =~ /\w+\.(\w+)/
  214. parts[0] = $1
  215. end
  216. parts.join(' ')
  217. end.join(', ')
  218. sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
  219. else
  220. sql << " ) AS tmp2"
  221. end
  222. elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
  223. sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
  224. "SELECT#{$1} TOP #{options[:limit]}"
  225. end unless options[:limit].nil?
  226. end
  227. end
  228.  
  229. # Begins a transaction.
  230. def begin_db_transaction
  231. @transaction = @connection.begin_transaction
  232. end
  233.  
  234. # Commits a transaction.
  235. def commit_db_transaction
  236. return unless @transaction
  237. @transaction.commit
  238. @transaction = nil
  239. end
  240.  
  241. # Aborts a transaction.
  242. def rollback_db_transaction
  243. return unless @transaction
  244. @transaction.rollback
  245. @transaction = nil
  246. end
  247.  
  248. # SCHEMA STATEMENTS ========================================
  249.  
  250. # Returns the list of all tables in the schema search path or a specified schema.
  251. def tables(name = nil)
  252. select_rows(<<-SQL, name).map { |row| row[0] }
  253. SELECT name
  254. FROM sysobjects
  255. WHERE type = 'U'
  256. SQL
  257. end
  258.  
  259. # Returns the list of all indexes for a table.
  260. def indexes(table_name, name = nil)
  261. result = query("exec sp_helpindex '#{table_name}'", name)
  262.  
  263. indexes = []
  264. result.each do |row|
  265. if row[1].match('primary key') == nil
  266. indexes << IndexDefinition.new(table_name, row[0], row[1].match('unique') != nil, row[2].split(',').each {|x| x.strip!})
  267. end
  268. end
  269.  
  270. indexes
  271. end
  272.  
  273. # Returns the list of all column definitions for a table.
  274. def columns(table_name, name = nil)
  275. # Limit, precision, and scale are all handled by the superclass.
  276. column_definitions(table_name).collect do |name, type, default, notnull|
  277. notnull = [false, true][notnull]
  278. Column.new(name, default, type, notnull)
  279. end
  280. end
  281.  
  282. # Sets the schema search path to a string of comma-separated schema names.
  283. # Names beginning with $ have to be quoted (e.g. $user => '$user').
  284. #
  285. # This should be not be called manually but set in database.yml.
  286. def schema_search_path=(schema_csv)
  287. end
  288.  
  289. # Returns the active schema search path.
  290. def schema_search_path
  291. 'dbo'
  292. end
  293.  
  294. # Renames a table.
  295. def rename_table(name, new_name)
  296. execute "exec sp_rename '#{name}', '#{new_name}'"
  297. end
  298.  
  299. # Adds a column to a table.
  300. def add_column(table_name, column_name, type, options = {})
  301. if options_include_default?(options)
  302. default = 'default ' + quote(options[:default])
  303. else
  304. default = ''
  305. end
  306. notnull = options[:null] == false
  307.  
  308. # Add the column.
  309. execute("ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])} #{notnull ? 'NOT NULL' : 'NULL'} #{default}")
  310. end
  311.  
  312. # Changes the column of a table.
  313. def change_column(table_name, column_name, type, options = {})
  314. begin
  315. execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
  316. rescue ActiveRecord::StatementInvalid
  317. # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
  318. begin_db_transaction
  319. tmp_column_name = "#{column_name}_ar_tmp"
  320. add_column(table_name, tmp_column_name, type, options)
  321. 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])})"
  322. remove_column(table_name, column_name)
  323. rename_column(table_name, tmp_column_name, column_name)
  324. commit_db_transaction
  325. end
  326.  
  327. change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
  328. change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
  329. end
  330.  
  331. # Changes the default value of a table column.
  332. def change_column_default(table_name, column_name, default)
  333. print "change_column_default to '#{default}'\n"
  334. execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
  335. end
  336.  
  337. def change_column_null(table_name, column_name, null, default = nil)
  338. unless null || default.nil?
  339. execute("UPDATE #{table_name} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
  340. end
  341. execute("ALTER TABLE #{table_name} ALTER #{quote_column_name(column_name)} #{null ? 'NULL' : 'NOT NULL'}")
  342. end
  343.  
  344. # Renames a column in a table.
  345. def rename_column(table_name, column_name, new_column_name)
  346. execute "exec sp_rename '#{table_name}.#{column_name}', '#{new_column_name}'"
  347. end
  348.  
  349. # Drops an index from a table.
  350. def remove_index(table_name, options = {})
  351. execute "DROP INDEX #{index_name(table_name, options)}"
  352. end
  353.  
  354. # Maps logical Rails types to MSSQL-specific data types.
  355. def type_to_sql(type, limit = nil, precision = nil, scale = nil)
  356. return super unless type.to_s == 'integer'
  357.  
  358. if limit.nil? || limit == 4
  359. 'integer'
  360. elsif limit < 4
  361. 'smallint'
  362. else
  363. 'bigint'
  364. end
  365. end
  366.  
  367. protected
  368. # Returns the version of the connected SQL Server.
  369. def mssql_version
  370. @mssql_version ||=
  371. begin
  372. query('SELECT @@version')[0][0] =~ /(\d+)\.(\d+)\.(\d+).(\d+)/
  373. [$1, $2, $3, $4]
  374. rescue
  375. [0, 0, 0, 0]
  376. end
  377. end
  378.  
  379. private
  380.  
  381. # Connects to SQL Server
  382. def connect
  383. @connection.open
  384. end
  385.  
  386. # Returns the current ID of a table's sequence.
  387. def last_insert_id(table, sequence_name) #:nodoc:
  388. identity = select_value("SELECT scope_identity()")
  389. if identity.class == System::DBNull
  390. nil
  391. else
  392. System::Convert.to_int32(identity)
  393. end
  394. end
  395.  
  396. # Executes a SELECT query and returns the results, performing any data type
  397. # conversions that are required to be performed here
  398. def select(sql, name = nil)
  399. fields, rows = select_raw(sql, name)
  400. result = []
  401. for row in rows
  402. row_hash = {}
  403. fields.each_with_index do |f, i|
  404. val = row[i]
  405. row_hash[f.to_s] = val.respond_to?(:rstrip) ? val.rstrip : val
  406. end
  407. result << row_hash
  408. end
  409. result
  410. end
  411.  
  412. def select_raw(sql, name = nil)
  413. reader = nil
  414. begin
  415. command = System::Data::SqlClient::SqlCommand.new sql, @connection
  416. command.transaction = @transaction
  417. reader = command.execute_reader
  418. fields = []
  419. schema = reader.get_schema_table
  420. if schema != nil
  421. for row in reader.get_schema_table.rows
  422. fields << row.item(0)
  423. end
  424. end
  425.  
  426. rows = []
  427. while reader.read
  428. row = []
  429. for i in (1..fields.length)
  430. value = reader.get_value(i - 1)
  431. if value.class == System::String
  432. value = value.to_s
  433. end
  434. row << value
  435. end
  436. rows << row
  437. end
  438. return fields, rows
  439. rescue System::Data::SqlClient::SqlException
  440. raise ActiveRecord::StatementInvalid, "#{$!}"
  441. ensure
  442. if reader != nil
  443. reader.close
  444. end
  445. end
  446. end
  447.  
  448.  
  449. # Returns the list of a table's column names, data types, and default values.
  450. #
  451. def column_definitions(table_name) #:nodoc:
  452. query <<-end_sql
  453. select
  454. c.name,
  455. case
  456. when t.name in ('char', 'varchar', 'nchar', 'nvarchar') then 'string'
  457. when t.name in ('binary', 'varbinary', 'image') then 'binary'
  458. when t.name in ('int', 'smallint', 'tinyint') then 'integer'
  459. when t.name in ('datetime', 'smalldatetime') then 'datetime'
  460. when t.name = 'bit' then 'boolean'
  461. when t.name = 'numeric' and c.prec < 10 and c.scale = 0 then 'integer'
  462. when t.name = 'numeric' then 'decimal'
  463. when t.name = 'text' then 'text'
  464. else t.name
  465. end type,
  466. d.text,
  467. c.isnullable
  468. from
  469. syscolumns c
  470. inner join systypes t
  471. on c.xusertype = t.xusertype
  472. left outer join syscomments d
  473. on c.cdefault = d.id
  474. where
  475. c.id = object_id('#{table_name}')
  476. order by
  477. c.colid
  478. end_sql
  479. end
  480. end
  481. end
  482. end
Add Comment
Please, Sign In to add comment