Guest User

Untitled

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