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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  4. data/.github/workflows/ruby.yml +30 -0
  5. data/.gitignore +4 -0
  6. data/ChangeLog.md +168 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +202 -0
  9. data/README.md +131 -0
  10. data/Rakefile +45 -0
  11. data/lib/trino-client.rb +1 -0
  12. data/lib/trino/client.rb +23 -0
  13. data/lib/trino/client/client.rb +78 -0
  14. data/lib/trino/client/errors.rb +46 -0
  15. data/lib/trino/client/faraday_client.rb +242 -0
  16. data/lib/trino/client/model_versions/0.149.rb +1683 -0
  17. data/lib/trino/client/model_versions/0.153.rb +1719 -0
  18. data/lib/trino/client/model_versions/0.173.rb +1685 -0
  19. data/lib/trino/client/model_versions/0.178.rb +1964 -0
  20. data/lib/trino/client/model_versions/0.205.rb +2169 -0
  21. data/lib/trino/client/model_versions/303.rb +2574 -0
  22. data/lib/trino/client/model_versions/316.rb +2595 -0
  23. data/lib/trino/client/model_versions/351.rb +2726 -0
  24. data/lib/trino/client/models.rb +38 -0
  25. data/lib/trino/client/query.rb +144 -0
  26. data/lib/trino/client/statement_client.rb +279 -0
  27. data/lib/trino/client/version.rb +20 -0
  28. data/modelgen/model_versions.rb +280 -0
  29. data/modelgen/modelgen.rb +119 -0
  30. data/modelgen/models.rb +31 -0
  31. data/modelgen/trino_models.rb +270 -0
  32. data/release.rb +56 -0
  33. data/spec/basic_query_spec.rb +82 -0
  34. data/spec/client_spec.rb +75 -0
  35. data/spec/gzip_spec.rb +40 -0
  36. data/spec/model_spec.rb +35 -0
  37. data/spec/spec_helper.rb +42 -0
  38. data/spec/statement_client_spec.rb +637 -0
  39. data/spec/tpch/q01.sql +21 -0
  40. data/spec/tpch/q02.sql +43 -0
  41. data/spec/tpch_query_spec.rb +41 -0
  42. data/trino-client.gemspec +31 -0
  43. 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