trino-client 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +1 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- data/.github/workflows/ruby.yml +30 -0
- data/.gitignore +4 -0
- data/ChangeLog.md +168 -0
- data/Gemfile +7 -0
- data/LICENSE +202 -0
- data/README.md +131 -0
- data/Rakefile +45 -0
- data/lib/trino-client.rb +1 -0
- data/lib/trino/client.rb +23 -0
- data/lib/trino/client/client.rb +78 -0
- data/lib/trino/client/errors.rb +46 -0
- data/lib/trino/client/faraday_client.rb +242 -0
- data/lib/trino/client/model_versions/0.149.rb +1683 -0
- data/lib/trino/client/model_versions/0.153.rb +1719 -0
- data/lib/trino/client/model_versions/0.173.rb +1685 -0
- data/lib/trino/client/model_versions/0.178.rb +1964 -0
- data/lib/trino/client/model_versions/0.205.rb +2169 -0
- data/lib/trino/client/model_versions/303.rb +2574 -0
- data/lib/trino/client/model_versions/316.rb +2595 -0
- data/lib/trino/client/model_versions/351.rb +2726 -0
- data/lib/trino/client/models.rb +38 -0
- data/lib/trino/client/query.rb +144 -0
- data/lib/trino/client/statement_client.rb +279 -0
- data/lib/trino/client/version.rb +20 -0
- data/modelgen/model_versions.rb +280 -0
- data/modelgen/modelgen.rb +119 -0
- data/modelgen/models.rb +31 -0
- data/modelgen/trino_models.rb +270 -0
- data/release.rb +56 -0
- data/spec/basic_query_spec.rb +82 -0
- data/spec/client_spec.rb +75 -0
- data/spec/gzip_spec.rb +40 -0
- data/spec/model_spec.rb +35 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/statement_client_spec.rb +637 -0
- data/spec/tpch/q01.sql +21 -0
- data/spec/tpch/q02.sql +43 -0
- data/spec/tpch_query_spec.rb +41 -0
- data/trino-client.gemspec +31 -0
- metadata +211 -0
data/spec/gzip_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trino::Client::Client do
|
4
|
+
before(:all) do
|
5
|
+
@spec_path = File.dirname(__FILE__)
|
6
|
+
WebMock.disable!
|
7
|
+
@cluster = TinyPresto::Cluster.new()
|
8
|
+
@container = @cluster.run
|
9
|
+
@client = Trino::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('show schemas')
|
14
|
+
break
|
15
|
+
rescue StandardError => exception
|
16
|
+
puts "Waiting for cluster ready... #{exception}"
|
17
|
+
sleep(3)
|
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
|
data/spec/model_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trino::Client::Models do
|
4
|
+
describe 'rehash of BlockedReason' do
|
5
|
+
h = {
|
6
|
+
"operatorId" => 0,
|
7
|
+
"planNodeId" => "47",
|
8
|
+
"operatorType" => "ScanFilterAndProjectOperator",
|
9
|
+
"addInputCalls" => 0,
|
10
|
+
"addInputWall" => "0.00ns",
|
11
|
+
"addInputCpu" => "0.00ns",
|
12
|
+
"addInputUser" => "0.00ns",
|
13
|
+
"inputDataSize" => "9.46MB",
|
14
|
+
"inputPositions" => 440674,
|
15
|
+
"getOutputCalls" => 734,
|
16
|
+
"getOutputWall" => "7.29s",
|
17
|
+
"getOutputCpu" => "0.00ns",
|
18
|
+
"getOutputUser" => "0.00ns",
|
19
|
+
"outputDataSize" => "36.99MB",
|
20
|
+
"outputPositions" => 440674,
|
21
|
+
"blockedWall" => "0.00ns",
|
22
|
+
"finishCalls" => 0,
|
23
|
+
"finishWall" => "0.00ns",
|
24
|
+
"finishCpu" => "0.00ns",
|
25
|
+
"finishUser" => "0.00ns",
|
26
|
+
"memoryReservation" => "0B",
|
27
|
+
"systemMemoryReservation" => "0b",
|
28
|
+
"blockedReason" => "WAITING_FOR_MEMORY",
|
29
|
+
"info" => {"k" => "v"}
|
30
|
+
}
|
31
|
+
|
32
|
+
stats = Models::OperatorStats.decode(h)
|
33
|
+
stats.blocked_reason.should == :waiting_for_memory
|
34
|
+
end
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :test)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'simplecov'
|
12
|
+
SimpleCov.start
|
13
|
+
|
14
|
+
require 'json'
|
15
|
+
require 'webmock/rspec'
|
16
|
+
|
17
|
+
require 'trino-client'
|
18
|
+
include Trino::Client
|
19
|
+
|
20
|
+
require 'tiny-presto'
|
21
|
+
|
22
|
+
MAX_RETRY_COUNT = 5
|
23
|
+
RETRYABLE_ERRORS = [
|
24
|
+
/No nodes available to run query/
|
25
|
+
]
|
26
|
+
|
27
|
+
def run_with_retry(client, sql)
|
28
|
+
i = 0
|
29
|
+
while i < MAX_RETRY_COUNT
|
30
|
+
begin
|
31
|
+
columns, rows = @client.run(sql)
|
32
|
+
return columns, rows
|
33
|
+
rescue Trino::Client::TrinoQueryError => e
|
34
|
+
if RETRYABLE_ERRORS.any? { |error| e.message =~ error }
|
35
|
+
sleep(i)
|
36
|
+
i += 1
|
37
|
+
next
|
38
|
+
end
|
39
|
+
raise "Fail to run query: #{e}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,637 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Trino::Client::StatementClient do
|
4
|
+
let :options do
|
5
|
+
{
|
6
|
+
server: "localhost",
|
7
|
+
user: "frsyuki",
|
8
|
+
catalog: "native",
|
9
|
+
schema: "default",
|
10
|
+
time_zone: "US/Pacific",
|
11
|
+
language: "ja_JP",
|
12
|
+
debug: true,
|
13
|
+
follow_redirect: true
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let :query do
|
18
|
+
"select * from sys.node"
|
19
|
+
end
|
20
|
+
|
21
|
+
let :response_json do
|
22
|
+
{
|
23
|
+
id: "queryid",
|
24
|
+
stats: {}
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
let :faraday do
|
29
|
+
Trino::Client.faraday_client(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "sets headers" do
|
33
|
+
stub_request(:post, "localhost/v1/statement").
|
34
|
+
with(body: query,
|
35
|
+
headers: {
|
36
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
37
|
+
"X-Trino-Catalog" => options[:catalog],
|
38
|
+
"X-Trino-Schema" => options[:schema],
|
39
|
+
"X-Trino-User" => options[:user],
|
40
|
+
"X-Trino-Language" => options[:language],
|
41
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
42
|
+
}).to_return(body: response_json.to_json)
|
43
|
+
|
44
|
+
StatementClient.new(faraday, query, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
let :response_json2 do
|
48
|
+
{
|
49
|
+
id: "queryid",
|
50
|
+
nextUri: 'http://localhost/v1/next_uri',
|
51
|
+
stats: {}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "sets headers" do
|
56
|
+
retry_p = false
|
57
|
+
stub_request(:post, "localhost/v1/statement").
|
58
|
+
with(body: query,
|
59
|
+
headers: {
|
60
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
61
|
+
"X-Trino-Catalog" => options[:catalog],
|
62
|
+
"X-Trino-Schema" => options[:schema],
|
63
|
+
"X-Trino-User" => options[:user],
|
64
|
+
"X-Trino-Language" => options[:language],
|
65
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
66
|
+
}).to_return(body: response_json2.to_json)
|
67
|
+
|
68
|
+
stub_request(:get, "localhost/v1/next_uri").
|
69
|
+
with(headers: {
|
70
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
71
|
+
"X-Trino-Catalog" => options[:catalog],
|
72
|
+
"X-Trino-Schema" => options[:schema],
|
73
|
+
"X-Trino-User" => options[:user],
|
74
|
+
"X-Trino-Language" => options[:language],
|
75
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
76
|
+
}).to_return(body: lambda{|req|if retry_p; response_json.to_json; else; retry_p=true; raise Timeout::Error.new("execution expired"); end })
|
77
|
+
|
78
|
+
sc = StatementClient.new(faraday, query, options.merge(http_open_timeout: 1))
|
79
|
+
sc.has_next?.should be_true
|
80
|
+
sc.advance.should be_true
|
81
|
+
retry_p.should be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it "uses 'Accept: application/x-msgpack' if option is set" do
|
85
|
+
retry_p = false
|
86
|
+
stub_request(:post, "localhost/v1/statement").
|
87
|
+
with(body: query,
|
88
|
+
headers: {
|
89
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
90
|
+
"X-Trino-Catalog" => options[:catalog],
|
91
|
+
"X-Trino-Schema" => options[:schema],
|
92
|
+
"X-Trino-User" => options[:user],
|
93
|
+
"X-Trino-Language" => options[:language],
|
94
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
95
|
+
"Accept" => "application/x-msgpack,application/json"
|
96
|
+
}).to_return(body: MessagePack.dump(response_json2), headers: {"Content-Type" => "application/x-msgpack"})
|
97
|
+
|
98
|
+
stub_request(:get, "localhost/v1/next_uri").
|
99
|
+
with(headers: {
|
100
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
101
|
+
"X-Trino-Catalog" => options[:catalog],
|
102
|
+
"X-Trino-Schema" => options[:schema],
|
103
|
+
"X-Trino-User" => options[:user],
|
104
|
+
"X-Trino-Language" => options[:language],
|
105
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
106
|
+
"Accept" => "application/x-msgpack,application/json"
|
107
|
+
}).to_return(body: lambda{|req|if retry_p; MessagePack.dump(response_json); else; retry_p=true; raise Timeout::Error.new("execution expired"); end }, headers: {"Content-Type" => "application/x-msgpack"})
|
108
|
+
|
109
|
+
options.merge!(http_open_timeout: 1, enable_x_msgpack: "application/x-msgpack")
|
110
|
+
sc = StatementClient.new(faraday, query, options)
|
111
|
+
sc.has_next?.should be_true
|
112
|
+
sc.advance.should be_true
|
113
|
+
retry_p.should be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
# trino version could be "V0_ddd" or "Vddd"
|
117
|
+
/\Trino::Client::ModelVersions::V(\w+)/ =~ Trino::Client::Models.to_s
|
118
|
+
|
119
|
+
# https://github.com/prestosql/presto/commit/80a2c5113d47e3390bf6dc041486a1c9dfc04592
|
120
|
+
# renamed DeleteHandle to DeleteTarget, then DeleteHandle exists when trino version
|
121
|
+
# is less than 313.
|
122
|
+
if $1[0, 2] == "0_" || $1.to_i < 314
|
123
|
+
it "decodes DeleteHandle" do
|
124
|
+
dh = Models::DeleteHandle.decode({
|
125
|
+
"handle" => {
|
126
|
+
"connectorId" => "c1",
|
127
|
+
"connectorHandle" => {}
|
128
|
+
}
|
129
|
+
})
|
130
|
+
dh.handle.should be_a_kind_of Models::TableHandle
|
131
|
+
dh.handle.connector_id.should == "c1"
|
132
|
+
dh.handle.connector_handle.should == {}
|
133
|
+
end
|
134
|
+
|
135
|
+
it "validates models" do
|
136
|
+
lambda do
|
137
|
+
Models::DeleteHandle.decode({
|
138
|
+
"handle" => "invalid"
|
139
|
+
})
|
140
|
+
end.should raise_error(TypeError, /String to Hash/)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
it "decodes DeleteTarget" do
|
144
|
+
dh = Models::DeleteTarget.decode({
|
145
|
+
"handle" => {
|
146
|
+
"catalogName" => "c1",
|
147
|
+
"connectorHandle" => {}
|
148
|
+
}
|
149
|
+
})
|
150
|
+
dh.handle.should be_a_kind_of Models::TableHandle
|
151
|
+
dh.handle.catalog_name.should == "c1"
|
152
|
+
dh.handle.connector_handle.should == {}
|
153
|
+
end
|
154
|
+
|
155
|
+
it "validates models" do
|
156
|
+
lambda do
|
157
|
+
Models::DeleteTarget.decode({
|
158
|
+
"catalogName" => "c1",
|
159
|
+
"handle" => "invalid"
|
160
|
+
})
|
161
|
+
end.should raise_error(TypeError, /String to Hash/)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it "receives headers of POST" do
|
166
|
+
stub_request(:post, "localhost/v1/statement").
|
167
|
+
with(body: query).to_return(body: response_json2.to_json, headers: {"X-Test-Header" => "123"})
|
168
|
+
|
169
|
+
sc = StatementClient.new(faraday, query, options.merge(http_open_timeout: 1))
|
170
|
+
sc.current_results_headers["X-Test-Header"].should == "123"
|
171
|
+
end
|
172
|
+
|
173
|
+
it "receives headers of POST through Query" do
|
174
|
+
stub_request(:post, "localhost/v1/statement").
|
175
|
+
with(body: query).to_return(body: response_json2.to_json, headers: {"X-Test-Header" => "123"})
|
176
|
+
|
177
|
+
q = Trino::Client.new(options).query(query)
|
178
|
+
q.current_results_headers["X-Test-Header"].should == "123"
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "#query_id" do
|
182
|
+
it "returns query_id" do
|
183
|
+
stub_request(:post, "localhost/v1/statement").
|
184
|
+
with(body: query).to_return(body: response_json2.to_json, headers: {"X-Test-Header" => "123"})
|
185
|
+
|
186
|
+
stub_request(:get, "localhost/v1/next_uri").
|
187
|
+
to_return(body: response_json.to_json, headers: {"X-Test-Header" => "123"})
|
188
|
+
|
189
|
+
sc = StatementClient.new(faraday, query, options.merge(http_open_timeout: 1))
|
190
|
+
sc.query_id.should == "queryid"
|
191
|
+
sc.has_next?.should be_true
|
192
|
+
sc.advance.should be_true
|
193
|
+
sc.query_id.should == "queryid"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe '#query_info' do
|
198
|
+
let :headers do
|
199
|
+
{
|
200
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
201
|
+
"X-Trino-Catalog" => options[:catalog],
|
202
|
+
"X-Trino-Schema" => options[:schema],
|
203
|
+
"X-Trino-User" => options[:user],
|
204
|
+
"X-Trino-Language" => options[:language],
|
205
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
let :statement_client do
|
210
|
+
stub_request(:post, "http://localhost/v1/statement").
|
211
|
+
with(body: query, headers: headers).
|
212
|
+
to_return(body: response_json2.to_json)
|
213
|
+
StatementClient.new(faraday, query, options)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "raises an exception with sample JSON if response is unexpected" do
|
217
|
+
lambda do
|
218
|
+
stub_request(:get, "http://localhost/v1/query/#{response_json2[:id]}").
|
219
|
+
with(headers: headers).
|
220
|
+
to_return(body: {"session" => "invalid session structure"}.to_json)
|
221
|
+
statement_client.query_info
|
222
|
+
end.should raise_error(TrinoHttpError, /Trino API returned unexpected structure at \/v1\/query\/queryid\. Expected Trino::Client::ModelVersions::.*::QueryInfo but got {"session":"invalid session structure"}/)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "raises an exception if response format is unexpected" do
|
226
|
+
lambda do
|
227
|
+
stub_request(:get, "http://localhost/v1/query/#{response_json2[:id]}").
|
228
|
+
with(headers: headers).
|
229
|
+
to_return(body: "unexpected data structure (not JSON)")
|
230
|
+
statement_client.query_info
|
231
|
+
end.should raise_error(TrinoHttpError, /Trino API returned unexpected data format./)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "is redirected if server returned 301" do
|
235
|
+
stub_request(:get, "http://localhost/v1/query/#{response_json2[:id]}").
|
236
|
+
with(headers: headers).
|
237
|
+
to_return(status: 301, headers: {"Location" => "http://localhost/v1/query/redirected"})
|
238
|
+
|
239
|
+
stub_request(:get, "http://localhost/v1/query/redirected").
|
240
|
+
with(headers: headers).
|
241
|
+
to_return(body: {"queryId" => "queryid"}.to_json)
|
242
|
+
|
243
|
+
query_info = statement_client.query_info
|
244
|
+
query_info.query_id.should == "queryid"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "Killing a query" do
|
249
|
+
let(:query_id) { 'A_QUERY_ID' }
|
250
|
+
|
251
|
+
it "sends DELETE request with empty body to /v1/query/{queryId}" do
|
252
|
+
stub_request(:delete, "http://localhost/v1/query/#{query_id}").
|
253
|
+
with(body: "",
|
254
|
+
headers: {
|
255
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
256
|
+
"X-Trino-Catalog" => options[:catalog],
|
257
|
+
"X-Trino-Schema" => options[:schema],
|
258
|
+
"X-Trino-User" => options[:user],
|
259
|
+
"X-Trino-Language" => options[:language],
|
260
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
261
|
+
}).to_return(body: {}.to_json)
|
262
|
+
|
263
|
+
Trino::Client.new(options).kill(query_id)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe 'advanced HTTP headers' do
|
268
|
+
let(:headers) do
|
269
|
+
{
|
270
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
271
|
+
"X-Trino-Catalog" => options[:catalog],
|
272
|
+
"X-Trino-Schema" => options[:schema],
|
273
|
+
"X-Trino-User" => options[:user],
|
274
|
+
"X-Trino-Language" => options[:language],
|
275
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
276
|
+
}
|
277
|
+
end
|
278
|
+
|
279
|
+
it "sets X-Trino-Session from properties" do
|
280
|
+
options[:properties] = {"hello" => "world", "name"=>"value"}
|
281
|
+
|
282
|
+
stub_request(:post, "localhost/v1/statement").
|
283
|
+
with(body: query,
|
284
|
+
headers: headers.merge({
|
285
|
+
"X-Trino-Session" => options[:properties].map {|k,v| "#{k}=#{v}"}.join(", ")
|
286
|
+
})).
|
287
|
+
to_return(body: response_json.to_json)
|
288
|
+
|
289
|
+
StatementClient.new(faraday, query, options)
|
290
|
+
end
|
291
|
+
|
292
|
+
it "sets X-Trino-Client-Info from client_info" do
|
293
|
+
options[:client_info] = "raw"
|
294
|
+
|
295
|
+
stub_request(:post, "localhost/v1/statement").
|
296
|
+
with(body: query,
|
297
|
+
headers: headers.merge("X-Trino-Client-Info" => "raw")).
|
298
|
+
to_return(body: response_json.to_json)
|
299
|
+
|
300
|
+
StatementClient.new(faraday, query, options)
|
301
|
+
end
|
302
|
+
|
303
|
+
it "sets X-Trino-Client-Info in JSON from client_info" do
|
304
|
+
options[:client_info] = {"k1" => "v1", "k2" => "v2"}
|
305
|
+
|
306
|
+
stub_request(:post, "localhost/v1/statement").
|
307
|
+
with(body: query,
|
308
|
+
headers: headers.merge("X-Trino-Client-Info" => '{"k1":"v1","k2":"v2"}')).
|
309
|
+
to_return(body: response_json.to_json)
|
310
|
+
|
311
|
+
StatementClient.new(faraday, query, options)
|
312
|
+
end
|
313
|
+
|
314
|
+
it "sets X-Trino-Client-Tags" do
|
315
|
+
options[:client_tags] = ["k1:v1", "k2:v2"]
|
316
|
+
|
317
|
+
stub_request(:post, "localhost/v1/statement").
|
318
|
+
with(body: query,
|
319
|
+
headers: headers.merge("X-Trino-Client-Tags" => "k1:v1,k2:v2")).
|
320
|
+
to_return(body: response_json.to_json)
|
321
|
+
|
322
|
+
StatementClient.new(faraday, query, options)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe 'HTTP basic auth' do
|
327
|
+
let(:password) { 'abcd' }
|
328
|
+
|
329
|
+
it "adds basic auth headers when ssl is enabled and a password is given" do
|
330
|
+
stub_request(:post, "https://localhost/v1/statement").
|
331
|
+
with(body: query,
|
332
|
+
headers: {
|
333
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
334
|
+
"X-Trino-Catalog" => options[:catalog],
|
335
|
+
"X-Trino-Schema" => options[:schema],
|
336
|
+
"X-Trino-User" => options[:user],
|
337
|
+
"X-Trino-Language" => options[:language],
|
338
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
339
|
+
},
|
340
|
+
basic_auth: [options[:user], password]
|
341
|
+
).to_return(body: response_json.to_json)
|
342
|
+
|
343
|
+
options.merge!(ssl: { verify: true }, password: password)
|
344
|
+
StatementClient.new(faraday, query, options)
|
345
|
+
end
|
346
|
+
|
347
|
+
it "forbids using basic auth when ssl is disabled" do
|
348
|
+
lambda do
|
349
|
+
Query.__send__(:faraday_client, {
|
350
|
+
server: 'localhost',
|
351
|
+
password: 'abcd'
|
352
|
+
})
|
353
|
+
end.should raise_error(ArgumentError)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
describe "ssl" do
|
358
|
+
it "is disabled by default" do
|
359
|
+
f = Query.__send__(:faraday_client, {
|
360
|
+
server: "localhost",
|
361
|
+
})
|
362
|
+
f.url_prefix.to_s.should == "http://localhost/"
|
363
|
+
end
|
364
|
+
|
365
|
+
it "is enabled with ssl: true" do
|
366
|
+
f = Query.__send__(:faraday_client, {
|
367
|
+
server: "localhost",
|
368
|
+
ssl: true,
|
369
|
+
})
|
370
|
+
f.url_prefix.to_s.should == "https://localhost/"
|
371
|
+
f.ssl.verify?.should == true
|
372
|
+
end
|
373
|
+
|
374
|
+
it "is enabled with ssl: {verify: false}" do
|
375
|
+
f = Query.__send__(:faraday_client, {
|
376
|
+
server: "localhost",
|
377
|
+
ssl: {verify: false}
|
378
|
+
})
|
379
|
+
f.url_prefix.to_s.should == "https://localhost/"
|
380
|
+
f.ssl.verify?.should == false
|
381
|
+
end
|
382
|
+
|
383
|
+
it "rejects invalid ssl: verify: object" do
|
384
|
+
lambda do
|
385
|
+
f = Query.__send__(:faraday_client, {
|
386
|
+
server: "localhost",
|
387
|
+
ssl: {verify: "??"}
|
388
|
+
})
|
389
|
+
end.should raise_error(ArgumentError, /String/)
|
390
|
+
end
|
391
|
+
|
392
|
+
it "is enabled with ssl: Hash" do
|
393
|
+
require 'openssl'
|
394
|
+
|
395
|
+
ssl = {
|
396
|
+
ca_file: "/path/to/dummy.pem",
|
397
|
+
ca_path: "/path/to/pemdir",
|
398
|
+
cert_store: OpenSSL::X509::Store.new,
|
399
|
+
client_cert: OpenSSL::X509::Certificate.new,
|
400
|
+
client_key: OpenSSL::PKey::DSA.new,
|
401
|
+
}
|
402
|
+
|
403
|
+
f = Query.__send__(:faraday_client, {
|
404
|
+
server: "localhost",
|
405
|
+
ssl: ssl,
|
406
|
+
})
|
407
|
+
|
408
|
+
f.url_prefix.to_s.should == "https://localhost/"
|
409
|
+
f.ssl.verify?.should == true
|
410
|
+
f.ssl.ca_file.should == ssl[:ca_file]
|
411
|
+
f.ssl.ca_path.should == ssl[:ca_path]
|
412
|
+
f.ssl.cert_store.should == ssl[:cert_store]
|
413
|
+
f.ssl.client_cert.should == ssl[:client_cert]
|
414
|
+
f.ssl.client_key.should == ssl[:client_key]
|
415
|
+
end
|
416
|
+
|
417
|
+
it "rejects an invalid string" do
|
418
|
+
lambda do
|
419
|
+
Query.__send__(:faraday_client, {
|
420
|
+
server: "localhost",
|
421
|
+
ssl: '??',
|
422
|
+
})
|
423
|
+
end.should raise_error(ArgumentError, /String/)
|
424
|
+
end
|
425
|
+
|
426
|
+
it "rejects an integer" do
|
427
|
+
lambda do
|
428
|
+
Query.__send__(:faraday_client, {
|
429
|
+
server: "localhost",
|
430
|
+
ssl: 3,
|
431
|
+
})
|
432
|
+
end.should raise_error(ArgumentError, /:ssl/)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
it "supports Presto" do
|
437
|
+
stub_request(:post, "localhost/v1/statement").
|
438
|
+
with({body: query}).
|
439
|
+
to_return(body: response_json.to_json)
|
440
|
+
|
441
|
+
faraday = Faraday.new(url: "http://localhost")
|
442
|
+
client = StatementClient.new(faraday, query, options.merge(model_version: "316"))
|
443
|
+
client.current_results.should be_a_kind_of(ModelVersions::V316::QueryResults)
|
444
|
+
end
|
445
|
+
|
446
|
+
it "rejects unsupported model version" do
|
447
|
+
lambda do
|
448
|
+
StatementClient.new(faraday, query, options.merge(model_version: "0.111"))
|
449
|
+
end.should raise_error(NameError)
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
let :nested_json do
|
454
|
+
nested_stats = {createTime: Time.now}
|
455
|
+
# JSON max nesting default value is 100
|
456
|
+
for i in 0..100 do
|
457
|
+
nested_stats = {stats: nested_stats}
|
458
|
+
end
|
459
|
+
{
|
460
|
+
id: "queryid",
|
461
|
+
stats: nested_stats
|
462
|
+
}
|
463
|
+
end
|
464
|
+
|
465
|
+
it "parse nested json properly" do
|
466
|
+
stub_request(:post, "localhost/v1/statement").
|
467
|
+
with(body: query,
|
468
|
+
headers: {
|
469
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
470
|
+
"X-Trino-Catalog" => options[:catalog],
|
471
|
+
"X-Trino-Schema" => options[:schema],
|
472
|
+
"X-Trino-User" => options[:user],
|
473
|
+
"X-Trino-Language" => options[:language],
|
474
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
475
|
+
}).to_return(body: nested_json.to_json(:max_nesting => false))
|
476
|
+
|
477
|
+
StatementClient.new(faraday, query, options)
|
478
|
+
end
|
479
|
+
|
480
|
+
describe "query timeout" do
|
481
|
+
let :headers do
|
482
|
+
{
|
483
|
+
"User-Agent" => "trino-ruby/#{VERSION}",
|
484
|
+
"X-Trino-Catalog" => options[:catalog],
|
485
|
+
"X-Trino-Schema" => options[:schema],
|
486
|
+
"X-Trino-User" => options[:user],
|
487
|
+
"X-Trino-Language" => options[:language],
|
488
|
+
"X-Trino-Time-Zone" => options[:time_zone],
|
489
|
+
}
|
490
|
+
end
|
491
|
+
|
492
|
+
let :planning_response do
|
493
|
+
{
|
494
|
+
id: "queryid",
|
495
|
+
nextUri: 'http://localhost/v1/next_uri',
|
496
|
+
stats: {},
|
497
|
+
}
|
498
|
+
end
|
499
|
+
|
500
|
+
let :early_running_response do
|
501
|
+
{
|
502
|
+
id: "queryid",
|
503
|
+
nextUri: 'http://localhost/v1/next_uri',
|
504
|
+
stats: {},
|
505
|
+
columns: [{name: "_col0", type: "bigint"}],
|
506
|
+
}
|
507
|
+
end
|
508
|
+
|
509
|
+
let :late_running_response do
|
510
|
+
{
|
511
|
+
id: "queryid",
|
512
|
+
nextUri: 'http://localhost/v1/next_uri',
|
513
|
+
stats: {},
|
514
|
+
columns: [{name: "_col0", type: "bigint"}],
|
515
|
+
data: "",
|
516
|
+
}
|
517
|
+
end
|
518
|
+
|
519
|
+
let :done_response do
|
520
|
+
{
|
521
|
+
id: "queryid",
|
522
|
+
stats: {},
|
523
|
+
columns: [{name: "_col0", type: "bigint"}],
|
524
|
+
}
|
525
|
+
end
|
526
|
+
|
527
|
+
before(:each) do
|
528
|
+
end
|
529
|
+
|
530
|
+
[:plan_timeout, :query_timeout].each do |timeout_type|
|
531
|
+
it "raises TrinoQueryTimeoutError if timeout during planning" do
|
532
|
+
stub_request(:post, "localhost/v1/statement").
|
533
|
+
with(body: query, headers: headers).
|
534
|
+
to_return(body: planning_response.to_json)
|
535
|
+
|
536
|
+
client = StatementClient.new(faraday, query, options.merge(timeout_type => 1))
|
537
|
+
|
538
|
+
stub_request(:get, "localhost/v1/next_uri").
|
539
|
+
with(headers: headers).
|
540
|
+
to_return(body: planning_response.to_json)
|
541
|
+
client.advance
|
542
|
+
|
543
|
+
sleep 1
|
544
|
+
stub_request(:get, "localhost/v1/next_uri").
|
545
|
+
with(headers: headers).
|
546
|
+
to_return(body: planning_response.to_json)
|
547
|
+
lambda do
|
548
|
+
client.advance
|
549
|
+
end.should raise_error(Trino::Client::TrinoQueryTimeoutError, "Query queryid timed out")
|
550
|
+
end
|
551
|
+
|
552
|
+
it "raises TrinoQueryTimeoutError if timeout during initial resuming" do
|
553
|
+
stub_request(:get, "localhost/v1/next_uri").
|
554
|
+
with(headers: headers).
|
555
|
+
to_return(body: lambda{|req| raise Timeout::Error.new("execution expired")})
|
556
|
+
|
557
|
+
lambda do
|
558
|
+
StatementClient.new(faraday, query, options.merge(timeout_type => 1), "/v1/next_uri")
|
559
|
+
end.should raise_error(Trino::Client::TrinoQueryTimeoutError, "Query timed out")
|
560
|
+
end
|
561
|
+
|
562
|
+
it "raises TrinoHttpError if timeout during initial resuming and #{timeout_type} < retry_timeout" do
|
563
|
+
stub_request(:get, "localhost/v1/next_uri").
|
564
|
+
with(headers: headers).
|
565
|
+
to_return(body: lambda{|req| raise Timeout::Error.new("execution expired")})
|
566
|
+
|
567
|
+
lambda do
|
568
|
+
StatementClient.new(faraday, query, options.merge(timeout_type => 2, retry_timeout: 1), "/v1/next_uri")
|
569
|
+
end.should raise_error(Trino::Client::TrinoHttpError, "Trino API error due to timeout")
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
it "doesn't raise errors with plan_timeout if query planning is done" do
|
574
|
+
stub_request(:post, "localhost/v1/statement").
|
575
|
+
with(body: query, headers: headers).
|
576
|
+
to_return(body: planning_response.to_json)
|
577
|
+
|
578
|
+
client = StatementClient.new(faraday, query, options.merge(plan_timeout: 1))
|
579
|
+
|
580
|
+
sleep 1
|
581
|
+
|
582
|
+
stub_request(:get, "localhost/v1/next_uri").
|
583
|
+
with(headers: headers).
|
584
|
+
to_return(body: early_running_response.to_json)
|
585
|
+
client.advance
|
586
|
+
|
587
|
+
stub_request(:get, "localhost/v1/next_uri").
|
588
|
+
with(headers: headers).
|
589
|
+
to_return(body: late_running_response.to_json)
|
590
|
+
client.advance
|
591
|
+
end
|
592
|
+
|
593
|
+
it "raises TrinoQueryTimeoutError if timeout during execution" do
|
594
|
+
stub_request(:post, "localhost/v1/statement").
|
595
|
+
with(body: query, headers: headers).
|
596
|
+
to_return(body: planning_response.to_json)
|
597
|
+
|
598
|
+
client = StatementClient.new(faraday, query, options.merge(query_timeout: 1))
|
599
|
+
|
600
|
+
stub_request(:get, "localhost/v1/next_uri").
|
601
|
+
with(headers: headers).
|
602
|
+
to_return(body: early_running_response.to_json)
|
603
|
+
client.advance
|
604
|
+
|
605
|
+
sleep 1
|
606
|
+
stub_request(:get, "localhost/v1/next_uri").
|
607
|
+
with(headers: headers).
|
608
|
+
to_return(body: late_running_response.to_json)
|
609
|
+
lambda do
|
610
|
+
client.advance
|
611
|
+
end.should raise_error(Trino::Client::TrinoQueryTimeoutError, "Query queryid timed out")
|
612
|
+
end
|
613
|
+
|
614
|
+
it "doesn't raise errors if query is done" do
|
615
|
+
stub_request(:post, "localhost/v1/statement").
|
616
|
+
with(body: query, headers: headers).
|
617
|
+
to_return(body: planning_response.to_json)
|
618
|
+
|
619
|
+
client = StatementClient.new(faraday, query, options.merge(query_timeout: 1))
|
620
|
+
|
621
|
+
stub_request(:get, "localhost/v1/next_uri").
|
622
|
+
with(headers: headers).
|
623
|
+
to_return(body: early_running_response.to_json)
|
624
|
+
client.advance
|
625
|
+
|
626
|
+
stub_request(:get, "localhost/v1/next_uri").
|
627
|
+
with(headers: headers).
|
628
|
+
to_return(body: done_response.to_json)
|
629
|
+
client.advance # set finished
|
630
|
+
|
631
|
+
sleep 1
|
632
|
+
client.advance # set finished
|
633
|
+
end
|
634
|
+
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|