Guest User

Untitled

a guest
Mar 6th, 2018
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.82 KB | None | 0 0
  1. # Add the path to the bundled libs to be used if the native bindings aren't installed
  2. $: << ENV['TM_BUNDLE_SUPPORT'] + '/lib/connectors' if ENV['TM_BUNDLE_SUPPORT']
  3.  
  4. require 'ostruct'
  5. require ENV['TM_SUPPORT_PATH'] + '/lib/password'
  6.  
  7. class ConnectorException < Exception; end;
  8. class MissingConfigurationException < ConnectorException; end;
  9.  
  10. class Connector
  11. @@connector = nil
  12.  
  13. def initialize(settings)
  14. @server = settings.server
  15. @settings = settings
  16. begin
  17. if @server == 'mysql'
  18. require 'mysql'
  19. get_mysql
  20. elsif @server == 'postgresql'
  21. require 'postgres-compat'
  22. get_pgsql
  23. end
  24. rescue LoadError
  25. TextMate::exit_show_tool_tip "Database connection library not found [#{$!}]"
  26. end
  27. end
  28.  
  29. def do_query(query, database = nil)
  30. if @server == 'postgresql'
  31. mycon = self.get_pgsql(database)
  32. res = mycon.query(query)
  33. elsif @server == 'mysql'
  34. mycon = self.get_mysql(database)
  35. res = mycon.query(query)
  36. end
  37. if res
  38. Result.new(res)
  39. else
  40. mycon.affected_rows
  41. end
  42. end
  43.  
  44. def server_version
  45. if @server == 'mysql'
  46. res = self.get_mysql.query('SHOW VARIABLES LIKE "version"')
  47. res.fetch_row[1]
  48. else
  49. puts "Not implemented for PGSQL yet!"
  50. exit
  51. end
  52. end
  53.  
  54. def get_mysql(database = nil)
  55. @@connector ||= Mysql::new(@settings.host, @settings.user, @settings.password, database || @settings.name, @settings.port)
  56. @@connector
  57. end
  58.  
  59. def get_pgsql(database = nil)
  60. @@connector ||= PostgresPR::Connection.new(database || @settings.name, @settings.user, @settings.password, 'tcp://' + @settings.host + ":" + @settings.port.to_s)
  61. @@connector
  62. end
  63.  
  64. ####
  65. def table_list(database = nil)
  66. if @server == 'postgresql'
  67. query = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"
  68. elsif @server == 'mysql'
  69. query = 'SHOW TABLES'
  70. end
  71. tables = []
  72. do_query(query, database).rows.each {|row| tables << row[0] }
  73. tables
  74. end
  75.  
  76. def database_list
  77. databases = []
  78. db_list = []
  79. if @server == 'postgresql'
  80. @settings.password = "r3v0lution"
  81. # Postgres doesn't allow passwords to be specified on the commandline
  82. # We can use --password to force the password prompt and then provide the password (even if it's blank)
  83. IO.popen("${TM_PSQL:-psql} -l --host='#{@settings.host}' --port='#{@settings.port}' --user='#{@settings.user}' --password --html 2>&1", 'w+') do |proc|
  84. proc << @settings.password
  85. proc.close_write
  86. db_list = $'.strip.to_a if proc.read =~ /Password.+?:/
  87. end
  88. raise ConnectorException.new(db_list) unless $?.to_i == 0
  89. while line = db_list.shift
  90. databases << $2 if db_list.shift.match(/\s+(<td align=.*?>)(.*?)(<\/td>)/) if line.include? '<tr valign'
  91. end
  92. elsif @server == 'mysql'
  93. db_list = `"${TM_MYSQL:-mysql}" -e 'show databases' --host="#{@settings.host}" --port="#{@settings.port}" --user="#{@settings.user}" --password="#{@settings.password}" --xml 2>&1`
  94. raise ConnectorException.new(db_list) unless $?.to_i == 0
  95. db_list.each_line { |line| databases << $1 if line.match(/<(?:Database|field name="Database")>(.+)<\/(Database|field)>/) }
  96. end
  97. databases
  98. end
  99.  
  100. def get_fields(table = nil)
  101. table ||= @settings.table
  102. field_list = []
  103. if @server == 'postgresql'
  104. query = "SELECT column_name, data_type, is_nullable, ordinal_position, column_default FROM information_schema.columns
  105. WHERE table_name='%s' ORDER BY ordinal_position" % table
  106. elsif @server == 'mysql'
  107. query = 'DESCRIBE ' + table
  108. end
  109. fields = do_query(query).rows
  110. fields.each { |field| field_list << {:name => field[0], :type => field[1], :nullable => field[2], :default => field[4]} }
  111. field_list
  112. end
  113. end
  114.  
  115. class Result
  116. def initialize(res)
  117. @res = res
  118. end
  119.  
  120. def rows
  121. if defined?(Mysql) and @res.is_a? Mysql::Result
  122. @res
  123. elsif @res.is_a? PostgresPR::Connection::Result
  124. @res.rows
  125. end
  126. end
  127.  
  128. def fields
  129. if defined?(Mysql) and @res.is_a? Mysql::Result
  130. @res.fetch_fields.map do |field|
  131. {:name => field.name,
  132. :type => [Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_TINY, Mysql::Field::TYPE_SHORT,
  133. Mysql::Field::TYPE_LONG, Mysql::Field::TYPE_FLOAT, Mysql::Field::TYPE_DOUBLE].include?(field.type) ? :number : :string }
  134. end
  135. elsif @res.is_a? PostgresPR::Connection::Result
  136. @res.fields.map{|field| {:name => field.name, :type => :string } }
  137. end
  138. end
  139.  
  140. def num_rows
  141. if @res.respond_to? :num_rows
  142. @res.num_rows
  143. else
  144. rows.size
  145. end
  146. end
  147. end
  148.  
  149. def render(template_file)
  150. template = File.read(File.dirname(__FILE__) + '/../templates/' + template_file + '.rhtml')
  151. ERB.new(template).result(binding)
  152. end
  153.  
  154. def html(subtitle = nil)
  155. puts html_head(:window_title => "SQL", :page_title => "Database Browser", :sub_title => subtitle || @options.database.name, :html_head => render('head'))
  156. yield
  157. html_footer
  158. exit
  159. end
  160.  
  161. def get_connection_settings(options)
  162. begin
  163. plist = open(File.expand_path('~/Library/Preferences/com.macromates.textmate.plist')) { |io| OSX::PropertyList.load(io) }
  164. connection = plist['SQL Connections'][plist['SQL Active Connection'].first.to_i]
  165.  
  166. options.host = connection['hostName']
  167. options.user = connection['userName']
  168. if connection['serverType'] && ['mysql', 'postgresql'].include?(connection['serverType'].downcase!)
  169. options.server = connection['serverType']
  170. else
  171. options.server = 'mysql'
  172. end
  173. options.name ||= connection['database']
  174. if connection['port']
  175. options.port = connection['port'].to_i
  176. else
  177. options.port = (options.server == 'postgresql') ? 5432 : 3306
  178. end
  179. rescue
  180. raise MissingConfigurationException.new
  181. end
  182. end
  183.  
  184. def get_connection_password(options)
  185. proto = options.server == 'postgresql' ? 'pgsq' : 'mysq'
  186.  
  187. rd, wr = IO.pipe
  188. if pid = fork
  189. wr.close
  190. Process.waitpid(pid)
  191. else
  192. STDERR.reopen(wr)
  193. STDOUT.reopen('/dev/null', 'r')
  194. rd.close; wr.close
  195. exec(['/usr/bin/security', TextMate.app_path + "/Contents/MacOS/TextMate"], 'find-internet-password', '-g', '-a', options.user, '-s', options.host, '-r', proto)
  196. end
  197.  
  198. $1 if rd.gets =~ /^password: "(.*)"$/
  199. end
  200.  
  201. def store_connection_password(options, password)
  202. proto = @options.database.server == 'postgresql' ? 'pgsq' : 'mysq'
  203.  
  204. rd, wr = IO.pipe
  205. if pid = fork
  206. wr.close
  207. Process.waitpid(pid)
  208. else
  209. STDERR.reopen(wr)
  210. STDOUT.reopen('/dev/null', 'r')
  211. rd.close; wr.close
  212. exec(['/usr/bin/security', TextMate.app_path + "/Contents/MacOS/TextMate"], 'add-internet-password', '-a', options.user, '-s', options.host, '-r', proto, '-w', password)
  213. end
  214.  
  215. if rd.gets =~ /already exists/
  216. TextMate::UI.alert(:warning, "Unable to store password", <<-WARNING
  217. There is already a keychain entry for
  218. #{options.user}@#{options.name} on #{options.host}
  219. You must either change the entry manually or delete it so that it can be stored.
  220. Both can be done with the Keychain Access application found in /Applications/Utilities
  221. WARNING
  222. )
  223. end
  224. end
  225.  
  226. def get_connection
  227. raise MissingConfigurationException.new unless @options.database.host and not @options.database.host.empty?
  228.  
  229. # Get the stored password if available, or nil otherwise
  230. @options.database.password = get_connection_password(@options.database)
  231.  
  232. begin
  233. # Try to connect with either our stored password, or with no password
  234. @connection = Connector.new(@options.database)
  235. rescue Exception => error
  236. if error.message.include?('Access denied') or error.message.include?('no password specified') or error.message.include?('authentication failed')
  237. # If we got an access denied error then we can request a password from the user
  238. begin
  239. # Longer prompts get cut off
  240. break unless password = TextMate::UI.request_secure_string(:title => "Enter Password", :prompt => "Enter password for #{@options.database.user}@#{@options.database.name}")
  241. @options.database.password = password
  242. # Try to connect with the new password
  243. @connection = Connector.new(@options.database) rescue nil
  244. end until @connection
  245. store_connection_password(@options.database, password) if @connection
  246. else
  247. # Rethrow other errors (e.g. host not found)
  248. message = error.message
  249. message = message.split("\t")[2][1..-1] rescue message if error.is_a? RuntimeError
  250. raise ConnectorException.new(message)
  251. end
  252. end
  253. unless @connection
  254. abort <<-HTML
  255. <script type="text/javascript" charset="utf-8">window.close()</script>
  256. HTML
  257. end
  258. @connection
  259. end
Add Comment
Please, Sign In to add comment