presto-client 0.6.0 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- data/.travis.yml +6 -6
- data/ChangeLog.md +164 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +23 -8
- data/Rakefile +1 -3
- data/lib/presto/client/faraday_client.rb +9 -1
- data/lib/presto/client/model_versions/316.rb +3 -3
- data/lib/presto/client/query.rb +2 -4
- data/lib/presto/client/statement_client.rb +40 -29
- data/lib/presto/client/version.rb +1 -1
- data/modelgen/model_versions.rb +3 -3
- data/presto-client.gemspec +2 -1
- data/release.rb +56 -0
- data/spec/basic_query_spec.rb +82 -0
- data/spec/gzip_spec.rb +40 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/statement_client_spec.rb +79 -18
- data/spec/tpch/q01.sql +21 -0
- data/spec/tpch/q02.sql +43 -0
- data/spec/tpch_query_spec.rb +41 -0
- metadata +33 -6
- data/ChangeLog +0 -176
data/Rakefile
CHANGED
@@ -15,6 +15,8 @@
|
|
15
15
|
#
|
16
16
|
module Presto::Client
|
17
17
|
|
18
|
+
require 'cgi'
|
19
|
+
|
18
20
|
module PrestoHeaders
|
19
21
|
PRESTO_USER = "X-Presto-User"
|
20
22
|
PRESTO_SOURCE = "X-Presto-Source"
|
@@ -58,7 +60,12 @@ module Presto::Client
|
|
58
60
|
if options[:user] && options[:password]
|
59
61
|
faraday.basic_auth(options[:user], options[:password])
|
60
62
|
end
|
61
|
-
|
63
|
+
if options[:follow_redirect]
|
64
|
+
faraday.use FaradayMiddleware::FollowRedirects
|
65
|
+
end
|
66
|
+
if options[:gzip]
|
67
|
+
faraday.use FaradayMiddleware::Gzip
|
68
|
+
end
|
62
69
|
faraday.response :logger if options[:http_debug]
|
63
70
|
faraday.adapter Faraday.default_adapter
|
64
71
|
end
|
@@ -155,6 +162,7 @@ module Presto::Client
|
|
155
162
|
if field_value =~ HTTP11_CTL_CHARSET_REGEXP
|
156
163
|
raise Faraday::ClientError, "Value of properties can't include HTTP/1.1 control characters"
|
157
164
|
end
|
165
|
+
field_value = CGI.escape(field_value)
|
158
166
|
"#{token}=#{field_value}"
|
159
167
|
end
|
160
168
|
end
|
@@ -198,9 +198,9 @@ module Presto::Client::ModelVersions
|
|
198
198
|
end
|
199
199
|
obj = allocate
|
200
200
|
model_class = case hash["@type"]
|
201
|
-
when "
|
202
|
-
when "
|
203
|
-
when "
|
201
|
+
when "CreateTarget" then CreateTarget
|
202
|
+
when "InsertTarget" then InsertTarget
|
203
|
+
when "DeleteTarget" then DeleteTarget
|
204
204
|
end
|
205
205
|
if model_class
|
206
206
|
model_class.decode(hash)
|
data/lib/presto/client/query.rb
CHANGED
@@ -16,6 +16,7 @@
|
|
16
16
|
module Presto::Client
|
17
17
|
|
18
18
|
require 'faraday'
|
19
|
+
require 'faraday_middleware'
|
19
20
|
require 'presto/client/models'
|
20
21
|
require 'presto/client/errors'
|
21
22
|
require 'presto/client/faraday_client'
|
@@ -130,11 +131,8 @@ module Presto::Client
|
|
130
131
|
end
|
131
132
|
|
132
133
|
def raise_if_failed
|
133
|
-
if @api.
|
134
|
+
if @api.client_aborted?
|
134
135
|
raise PrestoClientError, "Query aborted by user"
|
135
|
-
elsif @api.exception?
|
136
|
-
# query is gone
|
137
|
-
raise @api.exception
|
138
136
|
elsif @api.query_failed?
|
139
137
|
results = @api.current_results
|
140
138
|
error = results.error
|
@@ -31,8 +31,7 @@ module Presto::Client
|
|
31
31
|
|
32
32
|
@options = options
|
33
33
|
@query = query
|
34
|
-
@
|
35
|
-
@exception = nil
|
34
|
+
@state = :running
|
36
35
|
@retry_timeout = options[:retry_timeout] || 120
|
37
36
|
if model_version = @options[:model_version]
|
38
37
|
@models = ModelVersions.const_get("V#{model_version.gsub(".", "_")}")
|
@@ -76,7 +75,7 @@ module Presto::Client
|
|
76
75
|
|
77
76
|
# TODO error handling
|
78
77
|
if response.status != 200
|
79
|
-
|
78
|
+
exception! PrestoHttpError.new(response.status, "Failed to start query: #{response.body} (#{response.status})")
|
80
79
|
end
|
81
80
|
|
82
81
|
@results_headers = response.headers
|
@@ -91,14 +90,20 @@ module Presto::Client
|
|
91
90
|
!!@options[:debug]
|
92
91
|
end
|
93
92
|
|
94
|
-
def
|
95
|
-
@
|
93
|
+
def running?
|
94
|
+
@state == :running
|
96
95
|
end
|
97
96
|
|
98
|
-
|
97
|
+
def client_aborted?
|
98
|
+
@state == :client_aborted
|
99
|
+
end
|
100
|
+
|
101
|
+
def client_error?
|
102
|
+
@state == :client_error
|
103
|
+
end
|
99
104
|
|
100
|
-
def
|
101
|
-
@
|
105
|
+
def finished?
|
106
|
+
@state == :finished
|
102
107
|
end
|
103
108
|
|
104
109
|
def query_failed?
|
@@ -106,7 +111,7 @@ module Presto::Client
|
|
106
111
|
end
|
107
112
|
|
108
113
|
def query_succeeded?
|
109
|
-
@results.error == nil &&
|
114
|
+
@results.error == nil && finished?
|
110
115
|
end
|
111
116
|
|
112
117
|
def current_results
|
@@ -117,16 +122,29 @@ module Presto::Client
|
|
117
122
|
@results_headers
|
118
123
|
end
|
119
124
|
|
125
|
+
def query_id
|
126
|
+
@results.id
|
127
|
+
end
|
128
|
+
|
120
129
|
def has_next?
|
121
130
|
!!@results.next_uri
|
122
131
|
end
|
123
132
|
|
133
|
+
def exception!(e)
|
134
|
+
@state = :client_error
|
135
|
+
raise e
|
136
|
+
end
|
137
|
+
|
124
138
|
def advance
|
125
|
-
|
139
|
+
return false unless running?
|
140
|
+
|
141
|
+
unless has_next?
|
142
|
+
@state = :finished
|
126
143
|
return false
|
127
144
|
end
|
128
145
|
|
129
146
|
uri = @results.next_uri
|
147
|
+
|
130
148
|
response = faraday_get_with_retry(uri)
|
131
149
|
@results_headers = response.headers
|
132
150
|
@results = decode_model(uri, parse_body(response), @models::QueryResults)
|
@@ -150,8 +168,7 @@ module Presto::Client
|
|
150
168
|
if body.size > 1024 + 3
|
151
169
|
body = "#{body[0, 1024]}..."
|
152
170
|
end
|
153
|
-
|
154
|
-
raise @exception
|
171
|
+
exception! PrestoHttpError.new(500, "Presto API returned unexpected structure at #{uri}. Expected #{body_class} but got #{body}: #{e}")
|
155
172
|
end
|
156
173
|
end
|
157
174
|
|
@@ -166,8 +183,7 @@ module Presto::Client
|
|
166
183
|
JSON.parse(response.body, opts = JSON_OPTIONS)
|
167
184
|
end
|
168
185
|
rescue => e
|
169
|
-
|
170
|
-
raise @exception
|
186
|
+
exception! PrestoHttpError.new(500, "Presto API returned unexpected data format. #{e}")
|
171
187
|
end
|
172
188
|
end
|
173
189
|
|
@@ -184,8 +200,7 @@ module Presto::Client
|
|
184
200
|
# temporally error to retry
|
185
201
|
response = nil
|
186
202
|
rescue => e
|
187
|
-
|
188
|
-
raise @exception
|
203
|
+
exception! e
|
189
204
|
end
|
190
205
|
|
191
206
|
if response
|
@@ -195,8 +210,7 @@ module Presto::Client
|
|
195
210
|
|
196
211
|
if response.status != 503 # retry only if 503 Service Unavailable
|
197
212
|
# deterministic error
|
198
|
-
|
199
|
-
raise @exception
|
213
|
+
exception! PrestoHttpError.new(response.status, "Presto API error at #{uri} returned #{response.status}: #{response.body}")
|
200
214
|
end
|
201
215
|
end
|
202
216
|
|
@@ -204,18 +218,14 @@ module Presto::Client
|
|
204
218
|
|
205
219
|
attempts += 1
|
206
220
|
sleep attempts * 0.1
|
207
|
-
end while (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) < @retry_timeout &&
|
221
|
+
end while (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) < @retry_timeout && !client_aborted?
|
208
222
|
|
209
|
-
|
210
|
-
raise @exception
|
223
|
+
exception! PrestoHttpError.new(408, "Presto API error due to timeout")
|
211
224
|
end
|
212
225
|
|
213
226
|
def raise_if_timeout!
|
214
227
|
if @started_at
|
215
|
-
if
|
216
|
-
# query is already done
|
217
|
-
return
|
218
|
-
end
|
228
|
+
return if finished?
|
219
229
|
|
220
230
|
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at
|
221
231
|
|
@@ -234,9 +244,9 @@ module Presto::Client
|
|
234
244
|
|
235
245
|
def raise_timeout_error!
|
236
246
|
if query_id = @results && @results.id
|
237
|
-
|
247
|
+
exception! PrestoQueryTimeoutError.new("Query #{query_id} timed out")
|
238
248
|
else
|
239
|
-
|
249
|
+
exception! PrestoQueryTimeoutError.new("Query timed out")
|
240
250
|
end
|
241
251
|
end
|
242
252
|
|
@@ -249,7 +259,9 @@ module Presto::Client
|
|
249
259
|
end
|
250
260
|
|
251
261
|
def close
|
252
|
-
return
|
262
|
+
return unless running?
|
263
|
+
|
264
|
+
@state = :client_aborted
|
253
265
|
|
254
266
|
begin
|
255
267
|
if uri = @results.next_uri
|
@@ -260,7 +272,6 @@ module Presto::Client
|
|
260
272
|
rescue => e
|
261
273
|
end
|
262
274
|
|
263
|
-
@closed = true
|
264
275
|
nil
|
265
276
|
end
|
266
277
|
end
|
data/modelgen/model_versions.rb
CHANGED
@@ -198,9 +198,9 @@ module Presto::Client::ModelVersions
|
|
198
198
|
end
|
199
199
|
obj = allocate
|
200
200
|
model_class = case hash["@type"]
|
201
|
-
when "
|
202
|
-
when "
|
203
|
-
when "
|
201
|
+
when "CreateTarget" then CreateTarget
|
202
|
+
when "InsertTarget" then InsertTarget
|
203
|
+
when "DeleteTarget" then DeleteTarget
|
204
204
|
end
|
205
205
|
if model_class
|
206
206
|
model_class.decode(hash)
|
data/presto-client.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.description = %q{Presto client library}
|
10
10
|
gem.summary = %q{Presto client library}
|
11
11
|
gem.homepage = "https://github.com/treasure-data/presto-client-ruby"
|
12
|
-
gem.license = "Apache
|
12
|
+
gem.license = "Apache-2.0"
|
13
13
|
|
14
14
|
gem.files = `git ls-files`.split($\)
|
15
15
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.required_ruby_version = ">= 1.9.1"
|
21
21
|
|
22
22
|
gem.add_dependency "faraday", ["~> 0.12"]
|
23
|
+
gem.add_dependency "faraday_middleware", ["~> 0.12.2"]
|
23
24
|
gem.add_dependency "msgpack", [">= 0.7.0"]
|
24
25
|
|
25
26
|
gem.add_development_dependency "rake", [">= 0.9.2", "< 11.0"]
|
data/release.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
PREFIX = 'https://github.com/treasure-data/presto-client-ruby'
|
6
|
+
RELEASE_NOTES_FILE = "ChangeLog.md"
|
7
|
+
|
8
|
+
last_tag = `git describe --tags --abbrev=0`.chomp
|
9
|
+
last_version = last_tag.sub("v", "")
|
10
|
+
puts "last version: #{last_version}"
|
11
|
+
|
12
|
+
print "next version? "
|
13
|
+
next_version = STDIN.gets.chomp
|
14
|
+
|
15
|
+
abort("Can't use empty version string") if next_version.empty?
|
16
|
+
|
17
|
+
logs = `git log #{last_tag}..HEAD --pretty=format:'%h %s'`
|
18
|
+
# Add links to GitHub issues
|
19
|
+
logs = logs.gsub(/\#([0-9]+)/, "[#\\1](#{PREFIX}/issues/\\1)")
|
20
|
+
|
21
|
+
new_release_notes = []
|
22
|
+
new_release_notes <<= "\#\# #{next_version}\n"
|
23
|
+
new_release_notes <<= logs.split(/\n/)
|
24
|
+
.reject{|line| line.include?("#{last_version} release notes")}
|
25
|
+
.map{|x|
|
26
|
+
rev = x[0..6]
|
27
|
+
"- #{x[8..-1]} [[#{rev}](#{PREFIX}/commit/#{rev})]\n"
|
28
|
+
}
|
29
|
+
|
30
|
+
release_notes = []
|
31
|
+
notes = File.readlines(RELEASE_NOTES_FILE)
|
32
|
+
|
33
|
+
release_notes <<= notes[0..1]
|
34
|
+
release_notes <<= new_release_notes
|
35
|
+
release_notes <<= "\n"
|
36
|
+
release_notes <<= notes[2..-1]
|
37
|
+
|
38
|
+
TMP_RELEASE_NOTES_FILE = "#{RELEASE_NOTES_FILE}.tmp"
|
39
|
+
File.delete(TMP_RELEASE_NOTES_FILE) if File.exists?(TMP_RELEASE_NOTES_FILE)
|
40
|
+
File.write("#{TMP_RELEASE_NOTES_FILE}", release_notes.join)
|
41
|
+
system("cat #{TMP_RELEASE_NOTES_FILE} | vim - -c ':f #{TMP_RELEASE_NOTES_FILE}' -c ':9'")
|
42
|
+
|
43
|
+
abort("The release note file is not saved. Aborted") unless File.exists?(TMP_RELEASE_NOTES_FILE)
|
44
|
+
|
45
|
+
def run(cmd)
|
46
|
+
puts cmd
|
47
|
+
system cmd
|
48
|
+
end
|
49
|
+
|
50
|
+
FileUtils.cp(TMP_RELEASE_NOTES_FILE, RELEASE_NOTES_FILE)
|
51
|
+
File.delete(TMP_RELEASE_NOTES_FILE)
|
52
|
+
|
53
|
+
# run "git commit #{RELEASE_NOTES_FILE} -m \"Add #{next_version} release notes\""
|
54
|
+
# run "git tag v#{next_version}"
|
55
|
+
# run "git push"
|
56
|
+
# run "git push --tags"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Presto::Client::Client do
|
4
|
+
before(:all) do
|
5
|
+
WebMock.disable!
|
6
|
+
@cluster = TinyPresto::Cluster.new('ghcr.io/trinodb/presto', '316')
|
7
|
+
@container = @cluster.run
|
8
|
+
@client = Presto::Client.new(server: 'localhost:8080', catalog: 'memory', user: 'test-user', schema: 'default')
|
9
|
+
loop do
|
10
|
+
begin
|
11
|
+
@client.run('show schemas')
|
12
|
+
break
|
13
|
+
rescue StandardError => exception
|
14
|
+
puts "Waiting for cluster ready... #{exception}"
|
15
|
+
sleep(3)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
puts 'Cluster is ready'
|
19
|
+
end
|
20
|
+
|
21
|
+
after(:all) do
|
22
|
+
@cluster.stop
|
23
|
+
WebMock.enable!
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'show schemas' do
|
27
|
+
columns, rows = run_with_retry(@client, 'show schemas')
|
28
|
+
expect(columns.length).to be(1)
|
29
|
+
expect(rows.length).to be(2)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'ctas' do
|
33
|
+
expected = [[1, 'a'], [2, 'b']]
|
34
|
+
run_with_retry(@client, "create table ctas1 as select * from (values (1, 'a'), (2, 'b')) t(c1, c2)")
|
35
|
+
columns, rows = run_with_retry(@client, 'select * from ctas1')
|
36
|
+
expect(columns.map(&:name)).to match_array(%w[c1 c2])
|
37
|
+
expect(rows).to eq(expected)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'next_uri' do
|
41
|
+
@client.query('show schemas') do |q|
|
42
|
+
expect(q.next_uri).to start_with('http://localhost:8080/v1/statement/')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'advance' do
|
47
|
+
@client.query('show schemas') do |q|
|
48
|
+
expect(q.advance).to be(true)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'current query result' do
|
53
|
+
@client.query('show schemas') do |q|
|
54
|
+
expect(q.current_results.info_uri).to start_with('http://localhost:8080/ui/query.html')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'statement stats' do
|
59
|
+
@client.query('show schemas') do |q|
|
60
|
+
stats = q.current_results.stats
|
61
|
+
# Immediate subsequent request should get queued result
|
62
|
+
expect(stats.queued).to be(true)
|
63
|
+
expect(stats.scheduled).to be(false)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'partial cancel' do
|
68
|
+
@client.query('show schemas') do |q|
|
69
|
+
q.cancel
|
70
|
+
expect { q.query_info }.to raise_error(Presto::Client::PrestoHttpError, /Error 410 Gone/)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'row chunk' do
|
75
|
+
expected_schemas = %w[default information_schema]
|
76
|
+
@client.query('show schemas') do |q|
|
77
|
+
q.each_row do |r|
|
78
|
+
expect(expected_schemas).to include(r[0])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/spec/gzip_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Presto::Client::Client do
|
4
|
+
before(:all) do
|
5
|
+
@spec_path = File.dirname(__FILE__)
|
6
|
+
WebMock.disable!
|
7
|
+
@cluster = TinyPresto::Cluster.new('ghcr.io/trinodb/presto', '316')
|
8
|
+
@container = @cluster.run
|
9
|
+
@client = Presto::Client.new(server: 'localhost:8080', catalog: 'tpch', user: 'test-user', schema: 'tiny', gzip: true, http_debug: true)
|
10
|
+
loop do
|
11
|
+
begin
|
12
|
+
# Make sure to all workers are available.
|
13
|
+
@client.run('select 1234')
|
14
|
+
break
|
15
|
+
rescue StandardError => exception
|
16
|
+
puts "Waiting for cluster ready... #{exception}"
|
17
|
+
sleep(5)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
puts 'Cluster is ready'
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:all) do
|
24
|
+
@cluster.stop
|
25
|
+
WebMock.enable!
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'tpch q01 with gzip option' do
|
29
|
+
$stdout = StringIO.new
|
30
|
+
begin
|
31
|
+
q = File.read("#{@spec_path}/tpch/q01.sql")
|
32
|
+
columns, rows = run_with_retry(@client, q)
|
33
|
+
expect(columns.length).to be(10)
|
34
|
+
expect(rows.length).to be(4)
|
35
|
+
expect($stdout.string).to include ('content-encoding: "gzip"')
|
36
|
+
ensure
|
37
|
+
$stdout = STDOUT
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|