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
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# Trino client for Ruby
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
module Trino::Client
|
17
|
+
|
18
|
+
####
|
19
|
+
## lib/trino/client/models.rb is automatically generated using "rake modelgen:latest" command.
|
20
|
+
## You should not edit this file directly. To modify the class definitions, edit
|
21
|
+
## modelgen/models.rb file and run "rake modelgen:latest".
|
22
|
+
##
|
23
|
+
|
24
|
+
module ModelVersions
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'trino/client/model_versions/0.149.rb'
|
28
|
+
require 'trino/client/model_versions/0.153.rb'
|
29
|
+
require 'trino/client/model_versions/0.173.rb'
|
30
|
+
require 'trino/client/model_versions/0.178.rb'
|
31
|
+
require 'trino/client/model_versions/0.205.rb'
|
32
|
+
require 'trino/client/model_versions/303.rb'
|
33
|
+
require 'trino/client/model_versions/316.rb'
|
34
|
+
require 'trino/client/model_versions/351.rb'
|
35
|
+
|
36
|
+
Models = ModelVersions::V351
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#
|
2
|
+
# Trino client for Ruby
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
module Trino::Client
|
17
|
+
|
18
|
+
require 'faraday'
|
19
|
+
require 'faraday_middleware'
|
20
|
+
require 'trino/client/models'
|
21
|
+
require 'trino/client/errors'
|
22
|
+
require 'trino/client/faraday_client'
|
23
|
+
require 'trino/client/statement_client'
|
24
|
+
|
25
|
+
class Query
|
26
|
+
def self.start(query, options)
|
27
|
+
new StatementClient.new(faraday_client(options), query, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.resume(next_uri, options)
|
31
|
+
new StatementClient.new(faraday_client(options), nil, options, next_uri)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.kill(query_id, options)
|
35
|
+
faraday = faraday_client(options)
|
36
|
+
response = faraday.delete do |req|
|
37
|
+
req.url "/v1/query/#{query_id}"
|
38
|
+
end
|
39
|
+
return response.status / 100 == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.faraday_client(options)
|
43
|
+
Trino::Client.faraday_client(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(api)
|
47
|
+
@api = api
|
48
|
+
end
|
49
|
+
|
50
|
+
def current_results
|
51
|
+
@api.current_results
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_results_headers
|
55
|
+
@api.current_results_headers
|
56
|
+
end
|
57
|
+
|
58
|
+
def advance
|
59
|
+
@api.advance
|
60
|
+
end
|
61
|
+
|
62
|
+
def advance_and_raise
|
63
|
+
cont = @api.advance
|
64
|
+
raise_if_failed
|
65
|
+
cont
|
66
|
+
end
|
67
|
+
|
68
|
+
def wait_for_columns
|
69
|
+
while @api.current_results.columns == nil && advance_and_raise
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def wait_for_data
|
74
|
+
while @api.current_results.data == nil && advance_and_raise
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private :advance_and_raise
|
79
|
+
private :wait_for_columns
|
80
|
+
private :wait_for_data
|
81
|
+
|
82
|
+
def columns
|
83
|
+
wait_for_columns
|
84
|
+
|
85
|
+
return @api.current_results.columns
|
86
|
+
end
|
87
|
+
|
88
|
+
def rows
|
89
|
+
rows = []
|
90
|
+
each_row_chunk {|chunk|
|
91
|
+
rows.concat(chunk)
|
92
|
+
}
|
93
|
+
return rows
|
94
|
+
end
|
95
|
+
|
96
|
+
def each_row(&block)
|
97
|
+
each_row_chunk {|chunk|
|
98
|
+
chunk.each(&block)
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def each_row_chunk(&block)
|
103
|
+
wait_for_data
|
104
|
+
|
105
|
+
if self.columns == nil
|
106
|
+
raise TrinoError, "Query #{@api.current_results.id} has no columns"
|
107
|
+
end
|
108
|
+
|
109
|
+
begin
|
110
|
+
if data = @api.current_results.data
|
111
|
+
block.call(data)
|
112
|
+
end
|
113
|
+
end while advance_and_raise
|
114
|
+
end
|
115
|
+
|
116
|
+
def query_info
|
117
|
+
@api.query_info
|
118
|
+
end
|
119
|
+
|
120
|
+
def next_uri
|
121
|
+
@api.current_results.next_uri
|
122
|
+
end
|
123
|
+
|
124
|
+
def cancel
|
125
|
+
@api.cancel_leaf_stage
|
126
|
+
end
|
127
|
+
|
128
|
+
def close
|
129
|
+
@api.close
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def raise_if_failed
|
134
|
+
if @api.client_aborted?
|
135
|
+
raise TrinoClientError, "Query aborted by user"
|
136
|
+
elsif @api.query_failed?
|
137
|
+
results = @api.current_results
|
138
|
+
error = results.error
|
139
|
+
raise TrinoQueryError.new("Query #{results.id} failed: #{error.message}", results.id, error.error_code, error.error_name, error.failure_info)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
#
|
2
|
+
# Trino client for Ruby
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
module Trino::Client
|
17
|
+
|
18
|
+
require 'json'
|
19
|
+
require 'msgpack'
|
20
|
+
require 'trino/client/models'
|
21
|
+
require 'trino/client/errors'
|
22
|
+
|
23
|
+
class StatementClient
|
24
|
+
# Trino can return too deep nested JSON
|
25
|
+
JSON_OPTIONS = {
|
26
|
+
:max_nesting => false
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize(faraday, query, options, next_uri=nil)
|
30
|
+
@faraday = faraday
|
31
|
+
|
32
|
+
@options = options
|
33
|
+
@query = query
|
34
|
+
@state = :running
|
35
|
+
@retry_timeout = options[:retry_timeout] || 120
|
36
|
+
if model_version = @options[:model_version]
|
37
|
+
@models = ModelVersions.const_get("V#{model_version.gsub(".", "_")}")
|
38
|
+
else
|
39
|
+
@models = Models
|
40
|
+
end
|
41
|
+
|
42
|
+
@plan_timeout = options[:plan_timeout]
|
43
|
+
@query_timeout = options[:query_timeout]
|
44
|
+
|
45
|
+
if @plan_timeout || @query_timeout
|
46
|
+
# this is set before the first call of faraday_get_with_retry so that
|
47
|
+
# resuming StatementClient with next_uri is also under timeout control.
|
48
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
+
end
|
50
|
+
|
51
|
+
if next_uri
|
52
|
+
response = faraday_get_with_retry(next_uri)
|
53
|
+
@results_headers = response.headers
|
54
|
+
@results = @models::QueryResults.decode(parse_body(response))
|
55
|
+
else
|
56
|
+
post_query_request!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def init_request(req)
|
61
|
+
req.options.timeout = @options[:http_timeout] || 300
|
62
|
+
req.options.open_timeout = @options[:http_open_timeout] || 60
|
63
|
+
end
|
64
|
+
|
65
|
+
private :init_request
|
66
|
+
|
67
|
+
def post_query_request!
|
68
|
+
uri = "/v1/statement"
|
69
|
+
response = @faraday.post do |req|
|
70
|
+
req.url uri
|
71
|
+
|
72
|
+
req.body = @query
|
73
|
+
init_request(req)
|
74
|
+
end
|
75
|
+
|
76
|
+
# TODO error handling
|
77
|
+
if response.status != 200
|
78
|
+
exception! TrinoHttpError.new(response.status, "Failed to start query: #{response.body} (#{response.status})")
|
79
|
+
end
|
80
|
+
|
81
|
+
@results_headers = response.headers
|
82
|
+
@results = decode_model(uri, parse_body(response), @models::QueryResults)
|
83
|
+
end
|
84
|
+
|
85
|
+
private :post_query_request!
|
86
|
+
|
87
|
+
attr_reader :query
|
88
|
+
|
89
|
+
def debug?
|
90
|
+
!!@options[:debug]
|
91
|
+
end
|
92
|
+
|
93
|
+
def running?
|
94
|
+
@state == :running
|
95
|
+
end
|
96
|
+
|
97
|
+
def client_aborted?
|
98
|
+
@state == :client_aborted
|
99
|
+
end
|
100
|
+
|
101
|
+
def client_error?
|
102
|
+
@state == :client_error
|
103
|
+
end
|
104
|
+
|
105
|
+
def finished?
|
106
|
+
@state == :finished
|
107
|
+
end
|
108
|
+
|
109
|
+
def query_failed?
|
110
|
+
@results.error != nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def query_succeeded?
|
114
|
+
@results.error == nil && finished?
|
115
|
+
end
|
116
|
+
|
117
|
+
def current_results
|
118
|
+
@results
|
119
|
+
end
|
120
|
+
|
121
|
+
def current_results_headers
|
122
|
+
@results_headers
|
123
|
+
end
|
124
|
+
|
125
|
+
def query_id
|
126
|
+
@results.id
|
127
|
+
end
|
128
|
+
|
129
|
+
def has_next?
|
130
|
+
!!@results.next_uri
|
131
|
+
end
|
132
|
+
|
133
|
+
def exception!(e)
|
134
|
+
@state = :client_error
|
135
|
+
raise e
|
136
|
+
end
|
137
|
+
|
138
|
+
def advance
|
139
|
+
return false unless running?
|
140
|
+
|
141
|
+
unless has_next?
|
142
|
+
@state = :finished
|
143
|
+
return false
|
144
|
+
end
|
145
|
+
|
146
|
+
uri = @results.next_uri
|
147
|
+
|
148
|
+
response = faraday_get_with_retry(uri)
|
149
|
+
@results_headers = response.headers
|
150
|
+
@results = decode_model(uri, parse_body(response), @models::QueryResults)
|
151
|
+
|
152
|
+
raise_if_timeout!
|
153
|
+
|
154
|
+
return true
|
155
|
+
end
|
156
|
+
|
157
|
+
def query_info
|
158
|
+
uri = "/v1/query/#{@results.id}"
|
159
|
+
response = faraday_get_with_retry(uri)
|
160
|
+
decode_model(uri, parse_body(response), @models::QueryInfo)
|
161
|
+
end
|
162
|
+
|
163
|
+
def decode_model(uri, hash, body_class)
|
164
|
+
begin
|
165
|
+
body_class.decode(hash)
|
166
|
+
rescue => e
|
167
|
+
body = JSON.dump(hash)
|
168
|
+
if body.size > 1024 + 3
|
169
|
+
body = "#{body[0, 1024]}..."
|
170
|
+
end
|
171
|
+
exception! TrinoHttpError.new(500, "Trino API returned unexpected structure at #{uri}. Expected #{body_class} but got #{body}: #{e}")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private :decode_model
|
176
|
+
|
177
|
+
def parse_body(response)
|
178
|
+
begin
|
179
|
+
case response.headers['Content-Type']
|
180
|
+
when 'application/x-msgpack'
|
181
|
+
MessagePack.load(response.body)
|
182
|
+
else
|
183
|
+
JSON.parse(response.body, opts = JSON_OPTIONS)
|
184
|
+
end
|
185
|
+
rescue => e
|
186
|
+
exception! TrinoHttpError.new(500, "Trino API returned unexpected data format. #{e}")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private :parse_body
|
191
|
+
|
192
|
+
def faraday_get_with_retry(uri, &block)
|
193
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
194
|
+
attempts = 0
|
195
|
+
|
196
|
+
begin
|
197
|
+
begin
|
198
|
+
response = @faraday.get(uri)
|
199
|
+
rescue Faraday::Error::TimeoutError, Faraday::Error::ConnectionFailed
|
200
|
+
# temporally error to retry
|
201
|
+
response = nil
|
202
|
+
rescue => e
|
203
|
+
exception! e
|
204
|
+
end
|
205
|
+
|
206
|
+
if response
|
207
|
+
if response.status == 200 && !response.body.to_s.empty?
|
208
|
+
return response
|
209
|
+
end
|
210
|
+
|
211
|
+
if response.status != 503 # retry only if 503 Service Unavailable
|
212
|
+
# deterministic error
|
213
|
+
exception! TrinoHttpError.new(response.status, "Trino API error at #{uri} returned #{response.status}: #{response.body}")
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
raise_if_timeout!
|
218
|
+
|
219
|
+
attempts += 1
|
220
|
+
sleep attempts * 0.1
|
221
|
+
end while (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) < @retry_timeout && !client_aborted?
|
222
|
+
|
223
|
+
exception! TrinoHttpError.new(408, "Trino API error due to timeout")
|
224
|
+
end
|
225
|
+
|
226
|
+
def raise_if_timeout!
|
227
|
+
if @started_at
|
228
|
+
return if finished?
|
229
|
+
|
230
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at
|
231
|
+
|
232
|
+
if @query_timeout && elapsed > @query_timeout
|
233
|
+
raise_timeout_error!
|
234
|
+
end
|
235
|
+
|
236
|
+
if @plan_timeout && (@results == nil || @results.columns == nil) &&
|
237
|
+
elapsed > @plan_timeout
|
238
|
+
# @results is not set (even first faraday_get_with_retry isn't called yet) or
|
239
|
+
# result from Trino doesn't include result schema. Query planning isn't done yet.
|
240
|
+
raise_timeout_error!
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def raise_timeout_error!
|
246
|
+
if query_id = @results && @results.id
|
247
|
+
exception! TrinoQueryTimeoutError.new("Query #{query_id} timed out")
|
248
|
+
else
|
249
|
+
exception! TrinoQueryTimeoutError.new("Query timed out")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def cancel_leaf_stage
|
254
|
+
if uri = @results.partial_cancel_uri
|
255
|
+
@faraday.delete do |req|
|
256
|
+
req.url uri
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def close
|
262
|
+
return unless running?
|
263
|
+
|
264
|
+
@state = :client_aborted
|
265
|
+
|
266
|
+
begin
|
267
|
+
if uri = @results.next_uri
|
268
|
+
@faraday.delete do |req|
|
269
|
+
req.url uri
|
270
|
+
end
|
271
|
+
end
|
272
|
+
rescue => e
|
273
|
+
end
|
274
|
+
|
275
|
+
nil
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|