|
| 1 | +# encoding: UTF-8 |
| 2 | +# frozen_string_literal: true |
| 3 | +# |
| 4 | +# Copyright (c) 2021 GoodData Corporation. All rights reserved. |
| 5 | +# This source code is licensed under the BSD-style license found in the |
| 6 | +# LICENSE file in the root directory of this source tree. |
| 7 | + |
| 8 | +require 'securerandom' |
| 9 | +require 'java' |
| 10 | +require 'pathname' |
| 11 | +require_relative '../cloud_resource_client' |
| 12 | + |
| 13 | +base = Pathname(__FILE__).dirname.expand_path |
| 14 | +Dir.glob(base + 'drivers/*.jar').each do |file| |
| 15 | + require file unless file.start_with?('lcm-mysql-driver') |
| 16 | +end |
| 17 | + |
| 18 | +module GoodData |
| 19 | + module CloudResources |
| 20 | + class MysqlClient < CloudResourceClient |
| 21 | + JDBC_MYSQL_PATTERN = %r{jdbc:mysql:\/\/([^:^\/]+)(:([0-9]+))?(\/)?} |
| 22 | + MYSQL_DEFAULT_PORT = 3306 |
| 23 | + JDBC_MYSQL_PROTOCOL = 'jdbc:mysql://' |
| 24 | + VERIFY_FULL = 'VERIFY_IDENTITY' |
| 25 | + PREFER = 'PREFERRED' |
| 26 | + REQUIRE = 'REQUIRED' |
| 27 | + MYSQL_FETCH_SIZE = 1000 |
| 28 | + |
| 29 | + class << self |
| 30 | + def accept?(type) |
| 31 | + type == 'mysql' |
| 32 | + end |
| 33 | + end |
| 34 | + |
| 35 | + def initialize(options = {}) |
| 36 | + raise("Data Source needs a client to Mysql to be able to query the storage but 'mysql_client' is empty.") unless options['mysql_client'] |
| 37 | + |
| 38 | + if options['mysql_client']['connection'].is_a?(Hash) |
| 39 | + @database = options['mysql_client']['connection']['database'] |
| 40 | + @authentication = options['mysql_client']['connection']['authentication'] |
| 41 | + @ssl_mode = options['mysql_client']['connection']['sslMode'] |
| 42 | + raise "SSL Mode should be prefer, require and verify-full" unless @ssl_mode == 'prefer' || @ssl_mode == 'require' || @ssl_mode == 'verify-full' |
| 43 | + |
| 44 | + @url = build_url(options['mysql_client']['connection']['url']) |
| 45 | + else |
| 46 | + raise('Missing connection info for Mysql client') |
| 47 | + end |
| 48 | + |
| 49 | + Java.com.mysql.cj.jdbc.Driver |
| 50 | + end |
| 51 | + |
| 52 | + def realize_query(query, _params) |
| 53 | + GoodData.gd_logger.info("Realize SQL query: type=mysql status=started") |
| 54 | + |
| 55 | + connect |
| 56 | + filename = "#{SecureRandom.urlsafe_base64(6)}_#{Time.now.to_i}.csv" |
| 57 | + measure = Benchmark.measure do |
| 58 | + statement = @connection.create_statement |
| 59 | + statement.set_fetch_size(MYSQL_FETCH_SIZE) |
| 60 | + has_result = statement.execute(query) |
| 61 | + if has_result |
| 62 | + result = statement.get_result_set |
| 63 | + metadata = result.get_meta_data |
| 64 | + col_count = metadata.column_count |
| 65 | + CSV.open(filename, 'wb') do |csv| |
| 66 | + csv << Array(1..col_count).map { |i| metadata.get_column_name(i) } # build the header |
| 67 | + csv << Array(1..col_count).map { |i| result.get_string(i)&.to_s } while result.next |
| 68 | + end |
| 69 | + end |
| 70 | + end |
| 71 | + GoodData.gd_logger.info("Realize SQL query: type=mysql status=finished duration=#{measure.real}") |
| 72 | + filename |
| 73 | + ensure |
| 74 | + @connection&.close |
| 75 | + @connection = nil |
| 76 | + end |
| 77 | + |
| 78 | + def connect |
| 79 | + GoodData.logger.info "Setting up connection to Mysql #{@url}" |
| 80 | + |
| 81 | + prop = java.util.Properties.new |
| 82 | + prop.setProperty('user', @authentication['basic']['userName']) |
| 83 | + prop.setProperty('password', @authentication['basic']['password']) |
| 84 | + |
| 85 | + @connection = java.sql.DriverManager.getConnection(@url, prop) |
| 86 | + @connection.set_auto_commit(false) |
| 87 | + end |
| 88 | + |
| 89 | + def build_url(url) |
| 90 | + matches = url.scan(JDBC_MYSQL_PATTERN) |
| 91 | + raise 'Cannot reach the url' unless matches |
| 92 | + |
| 93 | + host = matches[0][0] |
| 94 | + port = matches[0][2]&.to_i || MYSQL_DEFAULT_PORT |
| 95 | + |
| 96 | + "#{JDBC_MYSQL_PROTOCOL}#{host}:#{port}/#{@database}?sslmode=#{get_ssl_mode(@ssl_mode)}&useCursorFetch=true" |
| 97 | + end |
| 98 | + |
| 99 | + def get_ssl_mode(ssl_mode) |
| 100 | + mode = PREFER |
| 101 | + if ssl_mode == 'verify-full' |
| 102 | + mode = VERIFY_FULL |
| 103 | + elsif ssl_mode == 'require' |
| 104 | + mode = REQUIRE |
| 105 | + end |
| 106 | + |
| 107 | + mode |
| 108 | + end |
| 109 | + end |
| 110 | + end |
| 111 | +end |
0 commit comments