trino-client 1.0.0
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 +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
|
+
|