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.
data/Rakefile CHANGED
@@ -6,9 +6,7 @@ require 'rake/clean'
6
6
 
7
7
  require 'rspec/core/rake_task'
8
8
 
9
- RSpec::Core::RakeTask.new(:spec) do |t|
10
- t.fail_on_error = false
11
- end
9
+ RSpec::Core::RakeTask.new(:spec)
12
10
 
13
11
  task :default => [:spec, :build]
14
12
 
@@ -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 "CreateHandle" then CreateHandle
202
- when "InsertHandle" then InsertHandle
203
- when "DeleteHandle" then DeleteHandle
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)
@@ -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.closed?
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
- @closed = false
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
- raise PrestoHttpError.new(response.status, "Failed to start query: #{response.body} (#{response.status})")
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 closed?
95
- @closed
93
+ def running?
94
+ @state == :running
96
95
  end
97
96
 
98
- attr_reader :exception
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 exception?
101
- @exception
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 && !@exception && !@closed
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
- if closed? || !has_next?
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
- @exception = PrestoHttpError.new(500, "Presto API returned unexpected structure at #{uri}. Expected #{body_class} but got #{body}: #{e}")
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
- @exception = PrestoHttpError.new(500, "Presto API returned unexpected data format. #{e}")
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
- @exception = e
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
- @exception = PrestoHttpError.new(response.status, "Presto API error at #{uri} returned #{response.status}: #{response.body}")
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 && !@closed
221
+ end while (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) < @retry_timeout && !client_aborted?
208
222
 
209
- @exception = PrestoHttpError.new(408, "Presto API error due to timeout")
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 @results && @results.next_uri == nil
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
- raise PrestoQueryTimeoutError, "Query #{query_id} timed out"
247
+ exception! PrestoQueryTimeoutError.new("Query #{query_id} timed out")
238
248
  else
239
- raise PrestoQueryTimeoutError, "Query timed out"
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 if @closed
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
@@ -15,6 +15,6 @@
15
15
  #
16
16
  module Presto
17
17
  module Client
18
- VERSION = "0.6.0"
18
+ VERSION = "0.6.5"
19
19
  end
20
20
  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 "CreateHandle" then CreateHandle
202
- when "InsertHandle" then InsertHandle
203
- when "DeleteHandle" then DeleteHandle
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)
@@ -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 2.0"
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