120 lines
3.6 KiB
Ruby
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.get_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
|