presto-client 0.1.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.
- data/Gemfile +2 -0
- data/Gemfile.lock +38 -0
- data/README.md +34 -0
- data/Rakefile +13 -0
- data/lib/presto-client.rb +1 -0
- data/lib/presto/client.rb +23 -0
- data/lib/presto/client/client.rb +35 -0
- data/lib/presto/client/models.rb +194 -0
- data/lib/presto/client/query.rb +85 -0
- data/lib/presto/client/statement_client.rb +168 -0
- data/lib/presto/client/version.rb +20 -0
- data/presto-client.gemspec +28 -0
- data/presto-client.rb +420 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/statement_client_spec.rb +39 -0
- metadata +146 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
presto-client (0.1.0)
|
5
|
+
faraday (~> 0.8.8)
|
6
|
+
multi_json (~> 1.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
addressable (2.3.5)
|
12
|
+
crack (0.3.2)
|
13
|
+
diff-lcs (1.2.4)
|
14
|
+
faraday (0.8.8)
|
15
|
+
multipart-post (~> 1.2.0)
|
16
|
+
multi_json (1.8.2)
|
17
|
+
multipart-post (1.2.0)
|
18
|
+
rake (10.1.1)
|
19
|
+
rspec (2.13.0)
|
20
|
+
rspec-core (~> 2.13.0)
|
21
|
+
rspec-expectations (~> 2.13.0)
|
22
|
+
rspec-mocks (~> 2.13.0)
|
23
|
+
rspec-core (2.13.1)
|
24
|
+
rspec-expectations (2.13.0)
|
25
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
26
|
+
rspec-mocks (2.13.1)
|
27
|
+
webmock (1.16.1)
|
28
|
+
addressable (>= 2.2.7)
|
29
|
+
crack (>= 0.3.2)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
presto-client!
|
36
|
+
rake (>= 0.9.2)
|
37
|
+
rspec (~> 2.13.0)
|
38
|
+
webmock (~> 1.16.1)
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Presto client library for Ruby
|
2
|
+
|
3
|
+
Presto is a distributed SQL query engine for big data:
|
4
|
+
https://github.com/facebook/presto
|
5
|
+
|
6
|
+
This is a client library for Ruby to run queries on Presto.
|
7
|
+
|
8
|
+
## Example
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
require 'presto-client'
|
12
|
+
|
13
|
+
# create a client object
|
14
|
+
client = PrestoClient::Client.new(
|
15
|
+
server: "localhost:8880",
|
16
|
+
user: "frsyuki",
|
17
|
+
catalog: "native",
|
18
|
+
schema: "default",
|
19
|
+
)
|
20
|
+
|
21
|
+
# start running a query on presto
|
22
|
+
q = client.query("select * from sys.query")
|
23
|
+
|
24
|
+
# wait for completion and get columns
|
25
|
+
q.columns.each {|column|
|
26
|
+
puts "column: #{column.name}.#{column.type}"
|
27
|
+
}
|
28
|
+
|
29
|
+
# get query results
|
30
|
+
q.each_row {|row|
|
31
|
+
p row
|
32
|
+
}
|
33
|
+
```
|
34
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'presto/client'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#
|
2
|
+
# Presto 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 Presto
|
17
|
+
module Client
|
18
|
+
|
19
|
+
require 'presto/client/version'
|
20
|
+
require 'presto/client/client'
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#
|
2
|
+
# Presto 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 Presto::Client
|
17
|
+
|
18
|
+
require 'presto/client/models'
|
19
|
+
require 'presto/client/query'
|
20
|
+
|
21
|
+
class Client
|
22
|
+
def initialize(options)
|
23
|
+
@session = ClientSession.new(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def query(query)
|
27
|
+
Query.start(@session, query)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.new(*args)
|
32
|
+
Client.new(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
#
|
2
|
+
# Presto 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 Presto::Client
|
17
|
+
|
18
|
+
class Column
|
19
|
+
attr_reader :name
|
20
|
+
attr_reader :type
|
21
|
+
|
22
|
+
def initialize(options={})
|
23
|
+
@name = options[:name]
|
24
|
+
@type = options[:type]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.decode_hash(hash)
|
28
|
+
new(
|
29
|
+
name: hash["name"],
|
30
|
+
type: hash["type"],
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ClientSession
|
36
|
+
def initialize(options)
|
37
|
+
@server = options[:server]
|
38
|
+
@user = options[:user]
|
39
|
+
@source = options[:source]
|
40
|
+
@catalog = options[:catalog]
|
41
|
+
@schema = options[:schema]
|
42
|
+
@debug = !!options[:debug]
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :server
|
46
|
+
attr_reader :user
|
47
|
+
attr_reader :source
|
48
|
+
attr_reader :catalog
|
49
|
+
attr_reader :schema
|
50
|
+
|
51
|
+
def debug?
|
52
|
+
@debug
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#class StageStats
|
57
|
+
# attr_reader :stage_id
|
58
|
+
# attr_reader :state
|
59
|
+
# attr_reader :done
|
60
|
+
# attr_reader :nodes
|
61
|
+
# attr_reader :total_splits
|
62
|
+
# attr_reader :queued_splits
|
63
|
+
# attr_reader :running_splits
|
64
|
+
# attr_reader :completed_splits
|
65
|
+
# attr_reader :user_time_millis
|
66
|
+
# attr_reader :cpu_time_millis
|
67
|
+
# attr_reader :wall_time_millis
|
68
|
+
# attr_reader :processed_rows
|
69
|
+
# attr_reader :processed_bytes
|
70
|
+
# attr_reader :sub_stages
|
71
|
+
#
|
72
|
+
# def initialize(options={})
|
73
|
+
# @stage_id = options[:stage_id]
|
74
|
+
# @state = options[:state]
|
75
|
+
# @done = options[:done]
|
76
|
+
# @nodes = options[:nodes]
|
77
|
+
# @total_splits = options[:total_splits]
|
78
|
+
# @queued_splits = options[:queued_splits]
|
79
|
+
# @running_splits = options[:running_splits]
|
80
|
+
# @completed_splits = options[:completed_splits]
|
81
|
+
# @user_time_millis = options[:user_time_millis]
|
82
|
+
# @cpu_time_millis = options[:cpu_time_millis]
|
83
|
+
# @wall_time_millis = options[:wall_time_millis]
|
84
|
+
# @processed_rows = options[:processed_rows]
|
85
|
+
# @processed_bytes = options[:processed_bytes]
|
86
|
+
# @sub_stages = options[:sub_stages]
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# def self.decode_hash(hash)
|
90
|
+
# new(
|
91
|
+
# stage_id: hash["stageId"],
|
92
|
+
# state: hash["state"],
|
93
|
+
# done: hash["done"],
|
94
|
+
# nodes: hash["nodes"],
|
95
|
+
# total_splits: hash["totalSplits"],
|
96
|
+
# queued_splits: hash["queuedSplits"],
|
97
|
+
# running_splits: hash["runningSplits"],
|
98
|
+
# completed_splits: hash["completedSplits"],
|
99
|
+
# user_time_millis: hash["userTimeMillis"],
|
100
|
+
# cpu_time_millis: hash["cpuTimeMillis"],
|
101
|
+
# wall_time_millis: hash["wallTimeMillis"],
|
102
|
+
# processed_rows: hash["processedRows"],
|
103
|
+
# processed_bytes: hash["processedBytes"],
|
104
|
+
# sub_stages: hash["subStages"].map {|h| StageStats.decode_hash(h) },
|
105
|
+
# )
|
106
|
+
# end
|
107
|
+
#end
|
108
|
+
|
109
|
+
class StatementStats
|
110
|
+
attr_reader :state
|
111
|
+
attr_reader :scheduled
|
112
|
+
attr_reader :nodes
|
113
|
+
attr_reader :total_splits
|
114
|
+
attr_reader :queued_splits
|
115
|
+
attr_reader :running_splits
|
116
|
+
attr_reader :completed_splits
|
117
|
+
attr_reader :user_time_millis
|
118
|
+
attr_reader :cpu_time_millis
|
119
|
+
attr_reader :wall_time_millis
|
120
|
+
attr_reader :processed_rows
|
121
|
+
attr_reader :processed_bytes
|
122
|
+
#attr_reader :root_stage
|
123
|
+
|
124
|
+
def initialize(options={})
|
125
|
+
@state = state
|
126
|
+
@scheduled = scheduled
|
127
|
+
@nodes = nodes
|
128
|
+
@total_splits = total_splits
|
129
|
+
@queued_splits = queued_splits
|
130
|
+
@running_splits = running_splits
|
131
|
+
@completed_splits = completed_splits
|
132
|
+
@user_time_millis = user_time_millis
|
133
|
+
@cpu_time_millis = cpu_time_millis
|
134
|
+
@wall_time_millis = wall_time_millis
|
135
|
+
@processed_rows = processed_rows
|
136
|
+
@processed_bytes = processed_bytes
|
137
|
+
#@root_stage = root_stage
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.decode_hash(hash)
|
141
|
+
new(
|
142
|
+
state: hash["state"],
|
143
|
+
scheduled: hash["scheduled"],
|
144
|
+
nodes: hash["nodes"],
|
145
|
+
total_splits: hash["totalSplits"],
|
146
|
+
queued_splits: hash["queuedSplits"],
|
147
|
+
running_splits: hash["runningSplits"],
|
148
|
+
completed_splits: hash["completedSplits"],
|
149
|
+
user_time_millis: hash["userTimeMillis"],
|
150
|
+
cpu_time_millis: hash["cpuTimeMillis"],
|
151
|
+
wall_time_millis: hash["wallTimeMillis"],
|
152
|
+
processed_rows: hash["processedRows"],
|
153
|
+
processed_bytes: hash["processedBytes"],
|
154
|
+
#root_stage: StageStats.decode_hash(hash["rootStage"]),
|
155
|
+
)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class QueryResults
|
160
|
+
attr_reader :id
|
161
|
+
attr_reader :info_uri
|
162
|
+
attr_reader :partial_cache_uri
|
163
|
+
attr_reader :next_uri
|
164
|
+
attr_reader :columns
|
165
|
+
attr_reader :data
|
166
|
+
attr_reader :stats
|
167
|
+
attr_reader :error
|
168
|
+
|
169
|
+
def initialize(options={})
|
170
|
+
@id = options[:id]
|
171
|
+
@info_uri = options[:info_uri]
|
172
|
+
@partial_cache_uri = options[:partial_cache_uri]
|
173
|
+
@next_uri = options[:next_uri]
|
174
|
+
@columns = options[:columns]
|
175
|
+
@data = options[:data]
|
176
|
+
@stats = options[:stats]
|
177
|
+
@error = options[:error]
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.decode_hash(hash)
|
181
|
+
new(
|
182
|
+
id: hash["id"],
|
183
|
+
info_uri: hash["infoUri"],
|
184
|
+
partial_cache_uri: hash["partialCancelUri"],
|
185
|
+
next_uri: hash["nextUri"],
|
186
|
+
columns: hash["columns"] ? hash["columns"].map {|h| Column.decode_hash(h) } : nil,
|
187
|
+
data: hash["data"],
|
188
|
+
stats: StatementStats.decode_hash(hash["stats"]),
|
189
|
+
error: hash["error"], # TODO
|
190
|
+
)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# Presto 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 Presto::Client
|
17
|
+
|
18
|
+
require 'faraday'
|
19
|
+
require 'presto/client/models'
|
20
|
+
require 'presto/client/statement_client'
|
21
|
+
|
22
|
+
class Query
|
23
|
+
def self.start(session, query)
|
24
|
+
faraday = Faraday.new(url: "http://#{session.server}") do |faraday|
|
25
|
+
#faraday.request :url_encoded
|
26
|
+
faraday.response :logger
|
27
|
+
faraday.adapter Faraday.default_adapter
|
28
|
+
end
|
29
|
+
|
30
|
+
new StatementClient.new(faraday, session, query)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(client)
|
34
|
+
@client = client
|
35
|
+
end
|
36
|
+
|
37
|
+
def wait_for_data
|
38
|
+
while @client.has_next? && @client.current_results.data == nil
|
39
|
+
@client.advance
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private :wait_for_data
|
44
|
+
|
45
|
+
def columns
|
46
|
+
wait_for_data
|
47
|
+
|
48
|
+
raise_error unless @client.query_succeeded?
|
49
|
+
|
50
|
+
return @client.current_results.columns
|
51
|
+
end
|
52
|
+
|
53
|
+
def each_row(&block)
|
54
|
+
wait_for_data
|
55
|
+
|
56
|
+
raise_error unless @client.query_succeeded?
|
57
|
+
|
58
|
+
if self.columns == nil
|
59
|
+
raise "Query #{@client.current_results.id} has no columns"
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
if data = @client.current_results.data
|
64
|
+
data.each(&block)
|
65
|
+
end
|
66
|
+
@client.advance
|
67
|
+
end while @client.has_next?
|
68
|
+
end
|
69
|
+
|
70
|
+
def raise_error
|
71
|
+
if @client.closed?
|
72
|
+
raise "Query aborted by user"
|
73
|
+
elsif @client.exception?
|
74
|
+
raise "Query is gone: #{@client.exception}"
|
75
|
+
elsif @client.query_failed?
|
76
|
+
results = @client.current_results
|
77
|
+
# TODO error location
|
78
|
+
raise "Query #{results.id} failed: #{results.error}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private :raise_error
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#
|
2
|
+
# Presto 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 Presto::Client
|
17
|
+
|
18
|
+
require 'multi_json'
|
19
|
+
require 'presto/client/models'
|
20
|
+
|
21
|
+
module PrestoHeaders
|
22
|
+
PRESTO_USER = "X-Presto-User"
|
23
|
+
PRESTO_SOURCE = "X-Presto-Source"
|
24
|
+
PRESTO_CATALOG = "X-Presto-Catalog"
|
25
|
+
PRESTO_SCHEMA = "X-Presto-Schema"
|
26
|
+
|
27
|
+
PRESTO_CURRENT_STATE = "X-Presto-Current-State"
|
28
|
+
PRESTO_MAX_WAIT = "X-Presto-Max-Wait"
|
29
|
+
PRESTO_MAX_SIZE = "X-Presto-Max-Size"
|
30
|
+
PRESTO_PAGE_SEQUENCE_ID = "X-Presto-Page-Sequence-Id"
|
31
|
+
end
|
32
|
+
|
33
|
+
class StatementClient
|
34
|
+
HEADERS = {
|
35
|
+
"User-Agent" => "presto-ruby/#{VERSION}"
|
36
|
+
}
|
37
|
+
|
38
|
+
def initialize(faraday, session, query)
|
39
|
+
@faraday = faraday
|
40
|
+
@faraday.headers.merge!(HEADERS)
|
41
|
+
|
42
|
+
@session = session
|
43
|
+
@query = query
|
44
|
+
@closed = false
|
45
|
+
@exception = nil
|
46
|
+
post_query_request!
|
47
|
+
end
|
48
|
+
|
49
|
+
def post_query_request!
|
50
|
+
response = @faraday.post do |req|
|
51
|
+
req.url "/v1/statement"
|
52
|
+
|
53
|
+
if v = @session.user
|
54
|
+
req.headers[PrestoHeaders::PRESTO_USER] = v
|
55
|
+
end
|
56
|
+
if v = @session.source
|
57
|
+
req.headers[PrestoHeaders::PRESTO_SOURCE] = v
|
58
|
+
end
|
59
|
+
if v = @session.catalog
|
60
|
+
req.headers[PrestoHeaders::PRESTO_CATALOG] = v
|
61
|
+
end
|
62
|
+
if v = @session.schema
|
63
|
+
req.headers[PrestoHeaders::PRESTO_SCHEMA] = v
|
64
|
+
end
|
65
|
+
|
66
|
+
req.body = @query
|
67
|
+
end
|
68
|
+
|
69
|
+
# TODO error handling
|
70
|
+
if response.status != 200
|
71
|
+
raise "Failed to start query: #{response.body}" # TODO error class
|
72
|
+
end
|
73
|
+
|
74
|
+
body = response.body
|
75
|
+
hash = MultiJson.load(body)
|
76
|
+
@results = QueryResults.decode_hash(hash)
|
77
|
+
end
|
78
|
+
|
79
|
+
private :post_query_request!
|
80
|
+
|
81
|
+
attr_reader :query
|
82
|
+
|
83
|
+
def debug?
|
84
|
+
@session.debug?
|
85
|
+
end
|
86
|
+
|
87
|
+
def closed?
|
88
|
+
@closed
|
89
|
+
end
|
90
|
+
|
91
|
+
attr_reader :exception
|
92
|
+
|
93
|
+
def exception?
|
94
|
+
@exception
|
95
|
+
end
|
96
|
+
|
97
|
+
def query_failed?
|
98
|
+
@results.error != nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def query_succeeded?
|
102
|
+
@results.error == nil && !@exception && !@closed
|
103
|
+
end
|
104
|
+
|
105
|
+
def current_results
|
106
|
+
@results
|
107
|
+
end
|
108
|
+
|
109
|
+
def has_next?
|
110
|
+
!!@results.next_uri
|
111
|
+
end
|
112
|
+
|
113
|
+
def advance
|
114
|
+
if closed? || !has_next?
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
uri = @results.next_uri
|
118
|
+
|
119
|
+
start = Time.now
|
120
|
+
attempts = 0
|
121
|
+
|
122
|
+
begin
|
123
|
+
begin
|
124
|
+
response = @faraday.get do |req|
|
125
|
+
req.url uri
|
126
|
+
end
|
127
|
+
rescue => e
|
128
|
+
@exception = e
|
129
|
+
raise @exception
|
130
|
+
end
|
131
|
+
|
132
|
+
if response.status == 200 && !response.body.to_s.empty?
|
133
|
+
@results = QueryResults.decode_hash(MultiJson.load(response.body))
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
|
137
|
+
if response.status != 503 # retry on 503 Service Unavailable
|
138
|
+
# deterministic error
|
139
|
+
@exception = StandardError.new("Error fetching next at #{uri} returned #{response.status}: #{response.body}") # TODO error class
|
140
|
+
raise @exception
|
141
|
+
end
|
142
|
+
|
143
|
+
attempts += 1
|
144
|
+
sleep attempts * 0.1
|
145
|
+
end while (Time.now - start) < 2*60*60 && !@closed
|
146
|
+
|
147
|
+
@exception = StandardError.new("Error fetching next") # TODO error class
|
148
|
+
raise @exception
|
149
|
+
end
|
150
|
+
|
151
|
+
def close
|
152
|
+
return if @closed
|
153
|
+
|
154
|
+
# cancel running statement
|
155
|
+
if uri = @results.next_uri
|
156
|
+
# TODO error handling
|
157
|
+
# TODO make async reqeust and ignore response
|
158
|
+
@faraday.delete do |req|
|
159
|
+
req.url uri
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
@closed = true
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#
|
2
|
+
# Presto 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 Presto
|
17
|
+
module Client
|
18
|
+
VERSION = "0.1.0"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path 'lib/presto/client/version', File.dirname(__FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "presto-client"
|
5
|
+
gem.version = Presto::Client::VERSION
|
6
|
+
|
7
|
+
gem.authors = ["Sadayuki Furuhashi"]
|
8
|
+
gem.email = ["sf@treasure-data.com"]
|
9
|
+
gem.description = %q{Presto client library}
|
10
|
+
gem.summary = %q{Presto client library}
|
11
|
+
gem.homepage = "https://github.com/treasure-data/presto-client-ruby"
|
12
|
+
gem.license = "Apache 2.0"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.has_rdoc = false
|
19
|
+
|
20
|
+
gem.required_ruby_version = ">= 1.9.3"
|
21
|
+
|
22
|
+
gem.add_dependency "faraday", ["~> 0.8.8"]
|
23
|
+
gem.add_dependency "multi_json", ["~> 1.0"]
|
24
|
+
|
25
|
+
gem.add_development_dependency "rake", [">= 0.9.2"]
|
26
|
+
gem.add_development_dependency "rspec", ["~> 2.13.0"]
|
27
|
+
gem.add_development_dependency "webmock", ["~> 1.16.1"]
|
28
|
+
end
|
data/presto-client.rb
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
|
2
|
+
module PrestoClient
|
3
|
+
VERSION = "0.1.0"
|
4
|
+
|
5
|
+
require 'faraday'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
class ClientSession
|
9
|
+
def initialize(options)
|
10
|
+
@server = options[:server]
|
11
|
+
@user = options[:user]
|
12
|
+
@source = options[:source]
|
13
|
+
@catalog = options[:catalog]
|
14
|
+
@schema = options[:schema]
|
15
|
+
@debug = !!options[:debug]
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :server
|
19
|
+
attr_reader :user
|
20
|
+
attr_reader :source
|
21
|
+
attr_reader :catalog
|
22
|
+
attr_reader :schema
|
23
|
+
|
24
|
+
def debug?
|
25
|
+
@debug
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#class StageStats
|
30
|
+
# attr_reader :stage_id
|
31
|
+
# attr_reader :state
|
32
|
+
# attr_reader :done
|
33
|
+
# attr_reader :nodes
|
34
|
+
# attr_reader :total_splits
|
35
|
+
# attr_reader :queued_splits
|
36
|
+
# attr_reader :running_splits
|
37
|
+
# attr_reader :completed_splits
|
38
|
+
# attr_reader :user_time_millis
|
39
|
+
# attr_reader :cpu_time_millis
|
40
|
+
# attr_reader :wall_time_millis
|
41
|
+
# attr_reader :processed_rows
|
42
|
+
# attr_reader :processed_bytes
|
43
|
+
# attr_reader :sub_stages
|
44
|
+
#
|
45
|
+
# def initialize(options={})
|
46
|
+
# @stage_id = options[:stage_id]
|
47
|
+
# @state = options[:state]
|
48
|
+
# @done = options[:done]
|
49
|
+
# @nodes = options[:nodes]
|
50
|
+
# @total_splits = options[:total_splits]
|
51
|
+
# @queued_splits = options[:queued_splits]
|
52
|
+
# @running_splits = options[:running_splits]
|
53
|
+
# @completed_splits = options[:completed_splits]
|
54
|
+
# @user_time_millis = options[:user_time_millis]
|
55
|
+
# @cpu_time_millis = options[:cpu_time_millis]
|
56
|
+
# @wall_time_millis = options[:wall_time_millis]
|
57
|
+
# @processed_rows = options[:processed_rows]
|
58
|
+
# @processed_bytes = options[:processed_bytes]
|
59
|
+
# @sub_stages = options[:sub_stages]
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# def self.decode_hash(hash)
|
63
|
+
# new(
|
64
|
+
# stage_id: hash["stageId"],
|
65
|
+
# state: hash["state"],
|
66
|
+
# done: hash["done"],
|
67
|
+
# nodes: hash["nodes"],
|
68
|
+
# total_splits: hash["totalSplits"],
|
69
|
+
# queued_splits: hash["queuedSplits"],
|
70
|
+
# running_splits: hash["runningSplits"],
|
71
|
+
# completed_splits: hash["completedSplits"],
|
72
|
+
# user_time_millis: hash["userTimeMillis"],
|
73
|
+
# cpu_time_millis: hash["cpuTimeMillis"],
|
74
|
+
# wall_time_millis: hash["wallTimeMillis"],
|
75
|
+
# processed_rows: hash["processedRows"],
|
76
|
+
# processed_bytes: hash["processedBytes"],
|
77
|
+
# sub_stages: hash["subStages"].map {|h| StageStats.decode_hash(h) },
|
78
|
+
# )
|
79
|
+
# end
|
80
|
+
#end
|
81
|
+
|
82
|
+
class StatementStats
|
83
|
+
attr_reader :state
|
84
|
+
attr_reader :scheduled
|
85
|
+
attr_reader :nodes
|
86
|
+
attr_reader :total_splits
|
87
|
+
attr_reader :queued_splits
|
88
|
+
attr_reader :running_splits
|
89
|
+
attr_reader :completed_splits
|
90
|
+
attr_reader :user_time_millis
|
91
|
+
attr_reader :cpu_time_millis
|
92
|
+
attr_reader :wall_time_millis
|
93
|
+
attr_reader :processed_rows
|
94
|
+
attr_reader :processed_bytes
|
95
|
+
#attr_reader :root_stage
|
96
|
+
|
97
|
+
def initialize(options={})
|
98
|
+
@state = state
|
99
|
+
@scheduled = scheduled
|
100
|
+
@nodes = nodes
|
101
|
+
@total_splits = total_splits
|
102
|
+
@queued_splits = queued_splits
|
103
|
+
@running_splits = running_splits
|
104
|
+
@completed_splits = completed_splits
|
105
|
+
@user_time_millis = user_time_millis
|
106
|
+
@cpu_time_millis = cpu_time_millis
|
107
|
+
@wall_time_millis = wall_time_millis
|
108
|
+
@processed_rows = processed_rows
|
109
|
+
@processed_bytes = processed_bytes
|
110
|
+
#@root_stage = root_stage
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.decode_hash(hash)
|
114
|
+
new(
|
115
|
+
state: hash["state"],
|
116
|
+
scheduled: hash["scheduled"],
|
117
|
+
nodes: hash["nodes"],
|
118
|
+
total_splits: hash["totalSplits"],
|
119
|
+
queued_splits: hash["queuedSplits"],
|
120
|
+
running_splits: hash["runningSplits"],
|
121
|
+
completed_splits: hash["completedSplits"],
|
122
|
+
user_time_millis: hash["userTimeMillis"],
|
123
|
+
cpu_time_millis: hash["cpuTimeMillis"],
|
124
|
+
wall_time_millis: hash["wallTimeMillis"],
|
125
|
+
processed_rows: hash["processedRows"],
|
126
|
+
processed_bytes: hash["processedBytes"],
|
127
|
+
#root_stage: StageStats.decode_hash(hash["rootStage"]),
|
128
|
+
)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Column
|
133
|
+
attr_reader :name
|
134
|
+
attr_reader :type
|
135
|
+
|
136
|
+
def initialize(options={})
|
137
|
+
@name = options[:name]
|
138
|
+
@type = options[:type]
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.decode_hash(hash)
|
142
|
+
new(
|
143
|
+
name: hash["name"],
|
144
|
+
type: hash["type"],
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class QueryResults
|
150
|
+
attr_reader :id
|
151
|
+
attr_reader :info_uri
|
152
|
+
attr_reader :partial_cache_uri
|
153
|
+
attr_reader :next_uri
|
154
|
+
attr_reader :columns
|
155
|
+
attr_reader :data
|
156
|
+
attr_reader :stats
|
157
|
+
attr_reader :error
|
158
|
+
|
159
|
+
def initialize(options={})
|
160
|
+
@id = options[:id]
|
161
|
+
@info_uri = options[:info_uri]
|
162
|
+
@partial_cache_uri = options[:partial_cache_uri]
|
163
|
+
@next_uri = options[:next_uri]
|
164
|
+
@columns = options[:columns]
|
165
|
+
@data = options[:data]
|
166
|
+
@stats = options[:stats]
|
167
|
+
@error = options[:error]
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.decode_hash(hash)
|
171
|
+
new(
|
172
|
+
id: hash["id"],
|
173
|
+
info_uri: hash["infoUri"],
|
174
|
+
partial_cache_uri: hash["partialCancelUri"],
|
175
|
+
next_uri: hash["nextUri"],
|
176
|
+
columns: hash["columns"] ? hash["columns"].map {|h| Column.decode_hash(h) } : nil,
|
177
|
+
data: hash["data"]
|
178
|
+
stats: StatementStats.decode_hash(hash["stats"]),
|
179
|
+
error: hash["error"], # TODO
|
180
|
+
)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
module PrestoHeaders
|
185
|
+
PRESTO_USER = "X-Presto-User"
|
186
|
+
PRESTO_SOURCE = "X-Presto-Source"
|
187
|
+
PRESTO_CATALOG = "X-Presto-Catalog"
|
188
|
+
PRESTO_SCHEMA = "X-Presto-Schema"
|
189
|
+
|
190
|
+
PRESTO_CURRENT_STATE = "X-Presto-Current-State"
|
191
|
+
PRESTO_MAX_WAIT = "X-Presto-Max-Wait"
|
192
|
+
PRESTO_MAX_SIZE = "X-Presto-Max-Size"
|
193
|
+
PRESTO_PAGE_SEQUENCE_ID = "X-Presto-Page-Sequence-Id"
|
194
|
+
end
|
195
|
+
|
196
|
+
class StatementClient
|
197
|
+
HEADERS = {
|
198
|
+
"User-Agent" => "presto-ruby/#{VERSION}"
|
199
|
+
}
|
200
|
+
|
201
|
+
def initialize(faraday, session, query)
|
202
|
+
@faraday = faraday
|
203
|
+
@faraday.headers.merge!(HEADERS)
|
204
|
+
|
205
|
+
@session = session
|
206
|
+
@query = query
|
207
|
+
@closed = false
|
208
|
+
@exception = nil
|
209
|
+
post_query_request!
|
210
|
+
end
|
211
|
+
|
212
|
+
def post_query_request!
|
213
|
+
response = @faraday.post do |req|
|
214
|
+
req.url "/v1/statement"
|
215
|
+
|
216
|
+
if v = @session.user
|
217
|
+
req.headers[PrestoHeaders::PRESTO_USER] = v
|
218
|
+
end
|
219
|
+
if v = @session.source
|
220
|
+
req.headers[PrestoHeaders::PRESTO_SOURCE] = v
|
221
|
+
end
|
222
|
+
if catalog = @session.catalog
|
223
|
+
req.headers[PrestoHeaders::PRESTO_CATALOG] = catalog
|
224
|
+
end
|
225
|
+
if v = @session.schema
|
226
|
+
req.headers[PrestoHeaders::PRESTO_SCHEMA] = v
|
227
|
+
end
|
228
|
+
|
229
|
+
req.body = @query
|
230
|
+
end
|
231
|
+
|
232
|
+
# TODO error handling
|
233
|
+
if response.status != 200
|
234
|
+
raise "Failed to start query: #{response.body}" # TODO error class
|
235
|
+
end
|
236
|
+
|
237
|
+
body = response.body
|
238
|
+
hash = JSON.parse(body)
|
239
|
+
@results = QueryResults.decode_hash(hash)
|
240
|
+
end
|
241
|
+
|
242
|
+
private :post_query_request!
|
243
|
+
|
244
|
+
attr_reader :query
|
245
|
+
|
246
|
+
def debug?
|
247
|
+
@session.debug?
|
248
|
+
end
|
249
|
+
|
250
|
+
def closed?
|
251
|
+
@closed
|
252
|
+
end
|
253
|
+
|
254
|
+
attr_reader :exception
|
255
|
+
|
256
|
+
def exception?
|
257
|
+
@exception
|
258
|
+
end
|
259
|
+
|
260
|
+
def query_failed?
|
261
|
+
@results.error != nil
|
262
|
+
end
|
263
|
+
|
264
|
+
def query_succeeded?
|
265
|
+
@results.error == nil && !@exception && !@closed
|
266
|
+
end
|
267
|
+
|
268
|
+
def current_results
|
269
|
+
@results
|
270
|
+
end
|
271
|
+
|
272
|
+
def has_next?
|
273
|
+
!!@results.next_uri
|
274
|
+
end
|
275
|
+
|
276
|
+
def advance
|
277
|
+
if closed? || !has_next?
|
278
|
+
return false
|
279
|
+
end
|
280
|
+
uri = @results.next_uri
|
281
|
+
|
282
|
+
start = Time.now
|
283
|
+
attempts = 0
|
284
|
+
|
285
|
+
begin
|
286
|
+
begin
|
287
|
+
response = @faraday.get do |req|
|
288
|
+
req.url uri
|
289
|
+
end
|
290
|
+
rescue => e
|
291
|
+
@exception = e
|
292
|
+
raise @exception
|
293
|
+
end
|
294
|
+
|
295
|
+
if response.status == 200 && !response.body.to_s.empty?
|
296
|
+
@results = QueryResults.decode_hash(JSON.parse(response.body))
|
297
|
+
return true
|
298
|
+
end
|
299
|
+
|
300
|
+
if response.status != 503 # retry on 503 Service Unavailable
|
301
|
+
# deterministic error
|
302
|
+
@exception = StandardError.new("Error fetching next at #{uri} returned #{response.status}: #{response.body}") # TODO error class
|
303
|
+
raise @exception
|
304
|
+
end
|
305
|
+
|
306
|
+
attempts += 1
|
307
|
+
sleep attempts * 0.1
|
308
|
+
end while (Time.now - start) < 2*60*60 && !@closed
|
309
|
+
|
310
|
+
@exception = StandardError.new("Error fetching next") # TODO error class
|
311
|
+
raise @exception
|
312
|
+
end
|
313
|
+
|
314
|
+
def close
|
315
|
+
return if @closed
|
316
|
+
|
317
|
+
# cancel running statement
|
318
|
+
if uri = @results.next_uri
|
319
|
+
# TODO error handling
|
320
|
+
# TODO make async reqeust and ignore response
|
321
|
+
@faraday.delete do |req|
|
322
|
+
req.url uri
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
@closed = true
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
class Query
|
332
|
+
def self.start(session, query)
|
333
|
+
faraday = Faraday.new(url: "http://#{session.server}") do |faraday|
|
334
|
+
faraday.request :url_encoded
|
335
|
+
faraday.response :logger
|
336
|
+
faraday.adapter Faraday.default_adapter
|
337
|
+
end
|
338
|
+
|
339
|
+
new StatementClient.new(faraday, session, query)
|
340
|
+
end
|
341
|
+
|
342
|
+
def initialize(client)
|
343
|
+
@client = client
|
344
|
+
end
|
345
|
+
|
346
|
+
def wait_for_data
|
347
|
+
while @client.has_next? && @client.current_results.data == nil
|
348
|
+
@client.advance
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
private :wait_for_data
|
353
|
+
|
354
|
+
def columns
|
355
|
+
wait_for_data
|
356
|
+
|
357
|
+
raise_error unless @client.query_succeeded?
|
358
|
+
|
359
|
+
return @client.current_results.columns
|
360
|
+
end
|
361
|
+
|
362
|
+
def each_row(&block)
|
363
|
+
wait_for_data
|
364
|
+
|
365
|
+
raise_error unless @client.query_succeeded?
|
366
|
+
|
367
|
+
if self.columns == nil
|
368
|
+
raise "Query #{@client.current_results.id} has no columns"
|
369
|
+
end
|
370
|
+
|
371
|
+
begin
|
372
|
+
if data = @client.current_results.data
|
373
|
+
data.each(&block)
|
374
|
+
end
|
375
|
+
@client.advance
|
376
|
+
end while @client.has_next?
|
377
|
+
end
|
378
|
+
|
379
|
+
def raise_error
|
380
|
+
if @client.closed?
|
381
|
+
raise "Query aborted by user"
|
382
|
+
elsif @client.exception?
|
383
|
+
raise "Query is gone: #{@client.exception}"
|
384
|
+
elsif @client.query_failed?
|
385
|
+
results = @client.current_results
|
386
|
+
# TODO error location
|
387
|
+
raise "Query #{results.id} failed: #{results.error}"
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
private :raise_error
|
392
|
+
end
|
393
|
+
|
394
|
+
class Client
|
395
|
+
def initialize(options)
|
396
|
+
@session = ClientSession.new(options)
|
397
|
+
end
|
398
|
+
|
399
|
+
def query(query)
|
400
|
+
Query.start(@session, query)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
require 'pp'
|
406
|
+
|
407
|
+
client = PrestoClient::Client.new(
|
408
|
+
server: "localhost:8880",
|
409
|
+
user: "frsyuki",
|
410
|
+
catalog: "native",
|
411
|
+
schema: "default",
|
412
|
+
debug: true
|
413
|
+
)
|
414
|
+
|
415
|
+
q = client.query("select * from sys.query")
|
416
|
+
p q.columns
|
417
|
+
q.each_row {|row|
|
418
|
+
p row
|
419
|
+
}
|
420
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
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 'json'
|
12
|
+
require 'webmock/rspec'
|
13
|
+
|
14
|
+
require 'presto-client'
|
15
|
+
include Presto::Client
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Presto::Client::StatementClient do
|
4
|
+
let :session do
|
5
|
+
session = ClientSession.new(
|
6
|
+
server: "localhost",
|
7
|
+
user: "frsyuki",
|
8
|
+
catalog: "native",
|
9
|
+
schema: "default",
|
10
|
+
debug: true,
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
let :query do
|
15
|
+
"select * from sys.node"
|
16
|
+
end
|
17
|
+
|
18
|
+
let :response_json do
|
19
|
+
{
|
20
|
+
id: "queryid",
|
21
|
+
stats: {}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it do
|
26
|
+
stub_request(:post, "localhost/v1/statement").
|
27
|
+
with(body: query,
|
28
|
+
headers: {
|
29
|
+
"User-Agent" => "presto-ruby/#{VERSION}",
|
30
|
+
"X-Presto-Catalog" => session.catalog,
|
31
|
+
"X-Presto-Schema" => session.schema,
|
32
|
+
"X-Presto-User" => session.user,
|
33
|
+
}).to_return(body: response_json.to_json)
|
34
|
+
|
35
|
+
faraday = Faraday.new(url: "http://localhost")
|
36
|
+
StatementClient.new(faraday, session, query)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: presto-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sadayuki Furuhashi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faraday
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.8.8
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.9.2
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.2
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.13.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.13.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: webmock
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.16.1
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.16.1
|
94
|
+
description: Presto client library
|
95
|
+
email:
|
96
|
+
- sf@treasure-data.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- Gemfile
|
102
|
+
- Gemfile.lock
|
103
|
+
- README.md
|
104
|
+
- Rakefile
|
105
|
+
- lib/presto-client.rb
|
106
|
+
- lib/presto/client.rb
|
107
|
+
- lib/presto/client/client.rb
|
108
|
+
- lib/presto/client/models.rb
|
109
|
+
- lib/presto/client/query.rb
|
110
|
+
- lib/presto/client/statement_client.rb
|
111
|
+
- lib/presto/client/version.rb
|
112
|
+
- presto-client.gemspec
|
113
|
+
- presto-client.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
- spec/statement_client_spec.rb
|
116
|
+
homepage: https://github.com/treasure-data/presto-client-ruby
|
117
|
+
licenses:
|
118
|
+
- Apache 2.0
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 1.9.3
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ! '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
hash: 1002266789771022757
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 1.8.23
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: Presto client library
|
144
|
+
test_files:
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/statement_client_spec.rb
|