cartodb/services/sql-api/sql_api.rb
2020-06-15 10:58:47 +08:00

120 lines
3.6 KiB
Ruby

require 'open-uri'
require 'json'
require_relative '../../lib/carto/http/client'
module CartoDB
class SQLApi
class SQLApiError < StandardError; end
class SQLError < SQLApiError; end
class PermissionError < SQLApiError; end
class TimeoutError < SQLApiError; end
class DnsError < SQLApiError; end
# seconds
CONNECT_TIMEOUT = 45
DEFAULT_TIMEOUT = 60
attr_accessor :api_key, :username, :timeout, :base_url
attr_reader :response_code, :parsed_response
# privacy: 'public' / 'private'
def self.with_username_api_key(username, api_key, privacy,
base_url: ::ApplicationHelper.sql_api_url(username, privacy))
new(
base_url: base_url,
username: username,
api_key: api_key
)
end
def self.with_user(user, privacy)
with_username_api_key(user.username, user.api_key, privacy)
end
def initialize(arguments)
self.username = arguments.fetch(:username)
self.api_key = arguments.fetch(:api_key, nil)
self.timeout = arguments.fetch(:timeout, DEFAULT_TIMEOUT)
self.base_url = arguments.fetch(:base_url, nil)
end
def url(query, format = '', filename = '')
build_request(query, format, filename, :get, :public).url
end
def export_table_url(table, format = 'gpkg', filename = table)
query = %{select * from "#{table}"}
url(query, format, filename)
end
def fetch(query, format = '')
request = build_request(query, format)
response = request.run
handle_response(response)
end
def handle_response(response)
@response_code = response.response_code
body = inflate(response.body.to_s)
@parsed_response = ::JSON.parse(body) rescue nil
raise_if_error(response)
parsed_response["rows"] rescue body
end
def inflate(text)
Zlib::GzipReader.new(StringIO.new(text)).read
rescue Zlib::GzipFile::Error
return text
end
def raise_if_error(response)
error_message = parsed_response["error"].first rescue nil
raise DnsError if response.return_code == :couldnt_resolve_host
raise TimeoutError if response.timed_out?
raise PermissionError if error_message =~ /^permission denied for relation/
raise SQLError.new(error_message) if response_code != 200
end
def build_base_url(sql_api_config_type)
config = ::Cartodb.config[:sql_api][sql_api_config_type.to_s]
if self.base_url.nil?
%Q[#{config["protocol"]}://#{username}.#{config["domain"]}#{config["endpoint"]}]
else
%Q[#{self.base_url}#{config["endpoint"]}]
end
end
def build_request(query, format = '', filename = '', method = :post, config_type = :private)
params = build_params(query, format, filename)
http_client = Carto::Http::Client.get('sql_api')
request = http_client.request(
build_base_url(config_type),
method: method,
headers: { 'Accept-Encoding' => 'gzip,deflate' },
connecttimeout: CONNECT_TIMEOUT,
timeout: timeout
)
set_request_parameters(request, method, params)
end
def build_params(query, format = '', filename = '')
params = {q: query}
params["format"] = format if !format.empty?
params["filename"] = filename if !filename.empty?
params["api_key"] = api_key if !api_key.nil? && !api_key.empty?
params
end
def set_request_parameters(request, method, params)
if method == :get
request.options[:params] = URI.encode_www_form(params)
else
request.options[:body] = URI.encode_www_form(params)
end
request
end
end
end