Class: Blazer::Adapters::SodaAdapter

Inherits:
BaseAdapter show all
Defined in:
lib/blazer/adapters/soda_adapter.rb

Instance Attribute Summary

Attributes inherited from BaseAdapter

#data_source

Instance Method Summary collapse

Methods inherited from BaseAdapter

#cachable?, #cancel, #cohort_analysis_statement, #cost, #explain, #initialize, #parameter_binding, #reconnect, #schema, #supports_cohort_analysis?

Constructor Details

This class inherits a constructor from Blazer::Adapters::BaseAdapter

Instance Method Details

#preview_statementObject



91
92
93
# File 'lib/blazer/adapters/soda_adapter.rb', line 91

def preview_statement
  "SELECT * LIMIT 10"
end

#quotingObject



100
101
102
# File 'lib/blazer/adapters/soda_adapter.rb', line 100

def quoting
  :single_quote_escape
end

#run_statement(statement, comment) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/blazer/adapters/soda_adapter.rb', line 4

def run_statement(statement, comment)
  require "json"
  require "net/http"
  require "uri"

  columns = []
  rows = []
  error = nil

  # remove comments manually
  statement = statement.gsub(/--.+/, "")
  # only supports single line /* */ comments
  # regex not perfect, but should be good enough
  statement = statement.gsub(/\/\*.+\*\//, "")

  # remove trailing semicolon
  statement = statement.sub(/;\s*\z/, "")

  # remove whitespace
  statement = statement.squish

  uri = URI(settings["url"])
  uri.query = URI.encode_www_form("$query" => statement)

  req = Net::HTTP::Get.new(uri)
  req["X-App-Token"] = settings["app_token"] if settings["app_token"]

  options = {
    use_ssl: uri.scheme == "https",
    open_timeout: 3,
    read_timeout: 30
  }

  begin
    # use Net::HTTP instead of soda-ruby for types and better error messages
    res = Net::HTTP.start(uri.hostname, uri.port, options) do |http|
      http.request(req)
    end

    if res.is_a?(Net::HTTPSuccess)
      body = JSON.parse(res.body)

      columns = JSON.parse(res["x-soda2-fields"])
      column_types = columns.zip(JSON.parse(res["x-soda2-types"])).to_h

      columns.reject! { |f| f.start_with?(":@") }
      # rows can be missing some keys in JSON, so need to map by column
      rows = body.map { |r| columns.map { |c| r[c] } }

      columns.each_with_index do |column, i|
        # nothing to do for boolean
        case column_types[column]
        when "number"
          # check if likely an integer column
          if rows.all? { |r| r[i].to_i == r[i].to_f }
            rows.each do |row|
              row[i] = row[i].to_i
            end
          else
            rows.each do |row|
              row[i] = row[i].to_f
            end
          end
        when "floating_timestamp"
          # check if likely a date column
          if rows.all? { |r| r[i].end_with?("T00:00:00.000") }
            rows.each do |row|
              row[i] = Date.parse(row[i])
            end
          else
            utc = ActiveSupport::TimeZone["Etc/UTC"]
            rows.each do |row|
              row[i] = utc.parse(row[i])
            end
          end
        end
      end
    else
      error = JSON.parse(res.body)["message"] rescue "Bad response: #{res.code}"
    end
  rescue => e
    error = e.message
  end

  [columns, rows, error]
end

#tablesObject



95
96
97
# File 'lib/blazer/adapters/soda_adapter.rb', line 95

def tables
  ["all"]
end