presto-client-legacy 0.4.17

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.
@@ -0,0 +1,230 @@
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 PrestoLegacy::Client
17
+
18
+ ####
19
+ ## lib/presto/client/models.rb is automatically generated using "rake modelgen" command.
20
+ ## You should not edit this file directly. To modify the class definitions, edit
21
+ ## modelgen/models.rb file and run "rake modelgen".
22
+ ##
23
+
24
+ module Models
25
+ class Base < Struct
26
+ class << self
27
+ alias_method :new_struct, :new
28
+
29
+ def new(*args)
30
+ new_struct(*args) do
31
+ # make it immutable
32
+ undef_method :"[]="
33
+ members.each do |m|
34
+ undef_method :"#{m}="
35
+ end
36
+
37
+ # replace constructor to receive hash instead of array
38
+ alias_method :initialize_struct, :initialize
39
+
40
+ def initialize(params={})
41
+ initialize_struct(*members.map {|m| params[m] })
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ class StageId < String
49
+ def initialize(str)
50
+ super
51
+ splitted = split('.', 2)
52
+ @query_id = splitted[0]
53
+ @id = splitted[1]
54
+ end
55
+
56
+ attr_reader :query_id, :id
57
+ end
58
+
59
+ class TaskId < String
60
+ def initialize(str)
61
+ super
62
+ splitted = split('.', 3)
63
+ @stage_id = StageId.new("#{splitted[0]}.#{splitted[1]}")
64
+ @query_id = @stage_id.query_id
65
+ @id = splitted[2]
66
+ end
67
+
68
+ attr_reader :query_id, :stage_id, :id
69
+ end
70
+
71
+ class ConnectorSession < Hash
72
+ def initialize(hash)
73
+ super()
74
+ merge!(hash)
75
+ end
76
+ end
77
+
78
+ module PlanNode
79
+ def self.decode(hash)
80
+ unless hash.is_a?(Hash)
81
+ raise TypeError, "Can't convert #{hash.class} to Hash"
82
+ end
83
+ model_class = case hash["@type"]
84
+ when "output" then OutputNode
85
+ when "project" then ProjectNode
86
+ when "tablescan" then TableScanNode
87
+ when "values" then ValuesNode
88
+ when "aggregation" then AggregationNode
89
+ when "markDistinct" then MarkDistinctNode
90
+ when "filter" then FilterNode
91
+ when "window" then WindowNode
92
+ when "rowNumber" then RowNumberNode
93
+ when "topnRowNumber" then TopNRowNumberNode
94
+ when "limit" then LimitNode
95
+ when "distinctlimit" then DistinctLimitNode
96
+ when "topn" then TopNNode
97
+ when "sample" then SampleNode
98
+ when "sort" then SortNode
99
+ when "remoteSource" then RemoteSourceNode
100
+ when "join" then JoinNode
101
+ when "semijoin" then SemiJoinNode
102
+ when "indexjoin" then IndexJoinNode
103
+ when "indexsource" then IndexSourceNode
104
+ when "tablewriter" then TableWriterNode
105
+ when "delete" then DeleteNode
106
+ when "metadatadelete" then MetadataDeleteNode
107
+ when "tablecommit" then TableFinishNode
108
+ when "unnest" then UnnestNode
109
+ when "exchange" then ExchangeNode
110
+ when "union" then UnionNode
111
+ when "scalar" then EnforceSingleRowNode
112
+ end
113
+ if model_class
114
+ node = model_class.decode(hash)
115
+ class << node
116
+ attr_accessor :plan_node_type
117
+ end
118
+ node.plan_node_type = hash['@type']
119
+ node
120
+ end
121
+ end
122
+ end
123
+
124
+ # io.airlift.stats.Distribution.DistributionSnapshot
125
+ class << DistributionSnapshot =
126
+ Base.new(:max_error, :count, :total, :p01, :p05, :p10, :p25, :p50, :p75, :p90, :p95, :p99, :min, :max)
127
+ def decode(hash)
128
+ unless hash.is_a?(Hash)
129
+ raise TypeError, "Can't convert #{hash.class} to Hash"
130
+ end
131
+ obj = allocate
132
+ obj.send(:initialize_struct,
133
+ hash["maxError"],
134
+ hash["count"],
135
+ hash["total"],
136
+ hash["p01"],
137
+ hash["p05"],
138
+ hash["p10"],
139
+ hash["p25"],
140
+ hash["p50"],
141
+ hash["p75"],
142
+ hash["p90"],
143
+ hash["p95"],
144
+ hash["p99"],
145
+ hash["min"],
146
+ hash["max"],
147
+ )
148
+ obj
149
+ end
150
+ end
151
+
152
+ # This is a hybrid of JoinNode.EquiJoinClause and IndexJoinNode.EquiJoinClause
153
+ class << EquiJoinClause =
154
+ Base.new(:left, :right, :probe, :index)
155
+ def decode(hash)
156
+ unless hash.is_a?(Hash)
157
+ raise TypeError, "Can't convert #{hash.class} to Hash"
158
+ end
159
+ obj = allocate
160
+ obj.send(:initialize_struct,
161
+ hash["left"],
162
+ hash["right"],
163
+ hash["probe"],
164
+ hash["index"],
165
+ )
166
+ obj
167
+ end
168
+ end
169
+
170
+ class << WriterTarget =
171
+ Base.new(:type, :handle)
172
+ def decode(hash)
173
+ unless hash.is_a?(Hash)
174
+ raise TypeError, "Can't convert #{hash.class} to Hash"
175
+ end
176
+ obj = allocate
177
+ model_class = case hash["@type"]
178
+ when "CreateHandle" then OutputTableHandle
179
+ when "InsertHandle" then InsertTableHandle
180
+ when "DeleteHandle" then TableHandle
181
+ end
182
+ obj.send(:initialize_struct,
183
+ hash["@type"],
184
+ model_class.decode(hash['handle'])
185
+ )
186
+ obj
187
+ end
188
+ end
189
+
190
+ class << DeleteHandle =
191
+ Base.new(:handle)
192
+ def decode(hash)
193
+ unless hash.is_a?(Hash)
194
+ raise TypeError, "Can't convert #{hash.class} to Hash"
195
+ end
196
+ obj = allocate
197
+ obj.send(:initialize_struct,
198
+ TableHandle.decode(hash['handle'])
199
+ )
200
+ obj
201
+ end
202
+ end
203
+
204
+
205
+ # A missing JsonCreator in Presto
206
+ class << PageBufferInfo =
207
+ Base.new(:partition, :buffered_pages, :queued_pages, :buffered_bytes, :pages_added)
208
+ def decode(hash)
209
+ unless hash.is_a?(Hash)
210
+ raise TypeError, "Can't convert #{hash.class} to Hash"
211
+ end
212
+ obj = allocate
213
+ obj.send(:initialize_struct,
214
+ hash["partition"],
215
+ hash["bufferedPages"],
216
+ hash["queuedPages"],
217
+ hash["bufferedBytes"],
218
+ hash["pagesAdded"],
219
+ )
220
+ obj
221
+ end
222
+ end
223
+
224
+ ##
225
+ # Those model classes are automatically generated
226
+ #
227
+
228
+ <%= @contents %>
229
+ end
230
+ end
@@ -0,0 +1,228 @@
1
+
2
+ module PrestoModels
3
+ require 'find'
4
+ require 'stringio'
5
+
6
+ PRIMITIVE_TYPES = %w[String boolean long int short byte double float Integer]
7
+
8
+ class Model < Struct.new(:name, :fields)
9
+ end
10
+
11
+ class Field < Struct.new(:key, :nullable, :array, :map, :type, :base_type, :map_value_base_type)
12
+ alias_method :nullable?, :nullable
13
+ alias_method :array?, :array
14
+ alias_method :map?, :map
15
+
16
+ def name
17
+ @name ||= key.gsub(/[A-Z]/) {|f| "_#{f.downcase}" }
18
+ end
19
+ end
20
+
21
+ class ModelAnalysisError < StandardError
22
+ end
23
+
24
+ class ModelAnalyzer
25
+ def initialize(source_path, options={})
26
+ @source_path = source_path
27
+ @ignore_types = PRIMITIVE_TYPES + (options[:skip_models] || [])
28
+ @path_mapping = options[:path_mapping] || {}
29
+ @name_mapping = options[:name_mapping] || {}
30
+ @models = {}
31
+ @skipped_models = []
32
+ end
33
+
34
+ attr_reader :skipped_models
35
+
36
+ def models
37
+ @models.values.sort_by {|model| model.name }
38
+ end
39
+
40
+ def analyze(root_models)
41
+ root_models.each {|model_name|
42
+ analyze_model(model_name)
43
+ }
44
+ end
45
+
46
+ private
47
+
48
+ PROPERTY_PATTERN = /@JsonProperty\(\"(\w+)\"\)\s+(@Nullable\s+)?([\w\<\>\,\s]+)\s+(\w+)/
49
+ CREATOR_PATTERN = /@JsonCreator[\w\s]+\((?:\s*#{PROPERTY_PATTERN}\s*,?)+\)/
50
+
51
+ def analyze_model(model_name, parent_model = nil)
52
+ return if @models[model_name] || @ignore_types.include?(model_name)
53
+
54
+ path = find_class_file(model_name, parent_model)
55
+ java = File.read(path)
56
+
57
+ m = CREATOR_PATTERN.match(java)
58
+ unless m
59
+ raise ModelAnalysisError, "Can't find JsonCreator of a model class #{model_name} of #{parent_model} at #{path}"
60
+ end
61
+
62
+ fields = m[0].scan(PROPERTY_PATTERN).map do |key,nullable,type,field|
63
+ map = false
64
+ array = false
65
+ nullable = !!nullable
66
+ if m = /(?:List|Set)<(\w+)>/.match(type)
67
+ base_type = m[1]
68
+ array = true
69
+ elsif m = /(?:Map|ListMultimap)<(\w+),\s*(\w+)>/.match(type)
70
+ base_type = m[1]
71
+ map_value_base_type = m[2]
72
+ map = true
73
+ elsif m = /Optional<(\w+)>/.match(type)
74
+ base_type = m[1]
75
+ nullable = true
76
+ elsif m = /OptionalInt/.match(type)
77
+ base_type = 'Integer'
78
+ nullable = true
79
+ elsif type =~ /\w+/
80
+ base_type = type
81
+ else
82
+ raise ModelAnalysisError, "Unsupported type #{type} in model #{model_name}"
83
+ end
84
+ base_type = @name_mapping[[model_name, base_type]] || base_type
85
+ map_value_base_type = @name_mapping[[model_name, map_value_base_type]] || map_value_base_type
86
+ Field.new(key, !!nullable, array, map, type, base_type, map_value_base_type)
87
+ end
88
+
89
+ @models[model_name] = Model.new(model_name, fields)
90
+
91
+ # recursive call
92
+ fields.each do |field|
93
+ analyze_model(field.base_type, model_name)
94
+ analyze_model(field.map_value_base_type, model_name) if field.map_value_base_type
95
+ end
96
+
97
+ rescue => e
98
+ puts "Skipping model #{parent_model}/#{model_name}: #{e}"
99
+ @skipped_models << model_name
100
+ end
101
+
102
+ def find_class_file(model_name, parent_model)
103
+ return @path_mapping[model_name] if @path_mapping.has_key? model_name
104
+
105
+ @source_files ||= Find.find(@source_path).to_a
106
+ pattern = /\/#{model_name}.java$/
107
+ matched = @source_files.find_all {|path| path =~ pattern }
108
+ if matched.empty?
109
+ raise ModelAnalysisError, "Model class #{model_name} is not found"
110
+ end
111
+ if matched.size == 1
112
+ return matched.first
113
+ else
114
+ raise ModelAnalysisError, "Model class #{model_name} of #{parent_model} found multiple match #{matched}"
115
+ end
116
+ end
117
+ end
118
+
119
+ class ModelFormatter
120
+ def initialize(options={})
121
+ @indent = options[:indent] || ' '
122
+ @base_indent_count = options[:base_indent_count] || 0
123
+ @struct_class = options[:struct_class] || 'Struct'
124
+ @special_struct_initialize_method = options[:special_struct_initialize_method]
125
+ @primitive_types = PRIMITIVE_TYPES + (options[:primitive_types] || [])
126
+ @skip_types = options[:skip_types] || []
127
+ @simple_classes = options[:simple_classes]
128
+ @enum_types = options[:enum_types]
129
+ @special_types = options[:special_types] || {}
130
+ @data = StringIO.new
131
+ end
132
+
133
+ def contents
134
+ @data.string
135
+ end
136
+
137
+ def format(models)
138
+ @models = models
139
+ models.each do |model|
140
+ @model = model
141
+
142
+ puts_with_indent 0, "class << #{model.name} ="
143
+ puts_with_indent 2, "#{@struct_class}.new(#{model.fields.map {|f| ":#{f.name}" }.join(', ')})"
144
+ format_decode
145
+ puts_with_indent 0, "end"
146
+ line
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def line
153
+ @data.puts ""
154
+ end
155
+
156
+ def puts_with_indent(n, str)
157
+ @data.puts "#{@indent * (@base_indent_count + n)}#{str}"
158
+ end
159
+
160
+ def format_decode
161
+ puts_with_indent 1, "def decode(hash)"
162
+
163
+ puts_with_indent 2, "unless hash.is_a?(Hash)"
164
+ puts_with_indent 3, "raise TypeError, \"Can't convert \#{hash.class} to Hash\""
165
+ puts_with_indent 2, "end"
166
+
167
+ if @special_struct_initialize_method
168
+ puts_with_indent 2, "obj = allocate"
169
+ puts_with_indent 2, "obj.send(:#{@special_struct_initialize_method},"
170
+ else
171
+ puts_with_indent 2, "new("
172
+ end
173
+
174
+ @model.fields.each do |field|
175
+ next if @skip_types.include?(field.base_type) || @skip_types.include?(field.map_value_base_type)
176
+
177
+ if @primitive_types.include?(field.base_type) && !field.map?
178
+ expr = "hash[\"#{field.key}\"]"
179
+ else
180
+ expr = ""
181
+ expr << "hash[\"#{field.key}\"] && " #if field.nullable?
182
+
183
+ if field.map?
184
+ key_expr = convert_expression(field.base_type, field.base_type, "k")
185
+ value_expr = convert_expression(field.map_value_base_type, field.map_value_base_type, "v")
186
+ if key_expr == 'k' && value_expr == 'v'
187
+ expr = "hash[\"#{field.key}\"]"
188
+ else
189
+ expr << "Hash[hash[\"#{field.key}\"].to_a.map! {|k,v| [#{key_expr}, #{value_expr}] }]"
190
+ end
191
+ elsif field.array?
192
+ elem_expr = convert_expression(field.base_type, field.base_type, "h")
193
+ expr << "hash[\"#{field.key}\"].map {|h| #{elem_expr} }"
194
+ else
195
+ expr << convert_expression(field.type, field.base_type, "hash[\"#{field.key}\"]")
196
+ end
197
+ end
198
+
199
+ #comment = "# #{field.base_type}#{field.array? ? '[]' : ''} #{field.key}"
200
+ #puts_with_indent 3, "#{expr}, #{comment}"
201
+ puts_with_indent 3, "#{expr},"
202
+ end
203
+
204
+ puts_with_indent 2, ")"
205
+
206
+ if @special_struct_initialize_method
207
+ puts_with_indent 2, "obj"
208
+ end
209
+
210
+ puts_with_indent 1, "end"
211
+ end
212
+
213
+ def convert_expression(type, base_type, key)
214
+ if @special_types[type]
215
+ special.call(key)
216
+ elsif @enum_types.include?(type)
217
+ "#{key}.downcase.to_sym"
218
+ elsif @primitive_types.include?(base_type)
219
+ key
220
+ elsif @simple_classes.include?(base_type)
221
+ "#{base_type}.new(#{key})"
222
+ else # model class
223
+ "#{base_type}.decode(#{key})"
224
+ end
225
+ end
226
+ end
227
+ end
228
+
@@ -0,0 +1,29 @@
1
+ require File.expand_path 'lib/presto/client/version', File.dirname(__FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "presto-client-legacy"
5
+ gem.version = PrestoLegacy::Client::VERSION
6
+
7
+ gem.authors = ["Sadayuki Furuhash", "Kai Sasaki"]
8
+ gem.email = ["sf@treasure-data.com", "lewuathe@me.com"]
9
+ gem.description = %q{Presto client library for legacy presto version}
10
+ gem.summary = %q{Presto client library for legacy presto version (before 0.151)}
11
+ gem.homepage = "https://github.com/Lewuathe/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.1"
21
+
22
+ gem.add_dependency "faraday", [">= 0.8.8", "< 0.10.0"]
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
+ gem.add_development_dependency "simplecov", ["~> 0.10.0"]
29
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe PrestoLegacy::Client::Client do
4
+ let(:client) { PrestoLegacy::Client.new({}) }
5
+
6
+ describe 'rehashes' do
7
+ let(:columns) do
8
+ [
9
+ Models::Column.new(name: 'animal', type: 'string'),
10
+ Models::Column.new(name: 'score', type: 'integer'),
11
+ Models::Column.new(name: 'name', type: 'string')
12
+ ]
13
+ end
14
+
15
+ it 'multiple rows' do
16
+ rows = [
17
+ ['dog', 1, 'Lassie'],
18
+ ['horse', 5, 'Mr. Ed'],
19
+ ['t-rex', 37, 'Doug']
20
+ ]
21
+ client.stub(:run).and_return([columns, rows])
22
+
23
+ rehashed = client.run_with_names('fake query')
24
+
25
+ rehashed.length.should == 3
26
+
27
+ rehashed[0]['animal'].should == 'dog'
28
+ rehashed[0]['score'].should == 1
29
+ rehashed[0]['name'].should == 'Lassie'
30
+
31
+ rehashed[0].values[0].should == 'dog'
32
+ rehashed[0].values[1].should == 1
33
+ rehashed[0].values[2].should == 'Lassie'
34
+
35
+ rehashed[1]['animal'].should == 'horse'
36
+ rehashed[1]['score'].should == 5
37
+ rehashed[1]['name'].should == 'Mr. Ed'
38
+
39
+ rehashed[1].values[0].should == 'horse'
40
+ rehashed[1].values[1].should == 5
41
+ rehashed[1].values[2].should == 'Mr. Ed'
42
+ end
43
+
44
+ it 'empty results' do
45
+ rows = []
46
+ client.stub(:run).and_return([columns, rows])
47
+
48
+ rehashed = client.run_with_names('fake query')
49
+
50
+ rehashed.length.should == 0
51
+ end
52
+
53
+ it 'handles too few result columns' do
54
+ rows = [['wrong', 'count']]
55
+ client.stub(:run).and_return([columns, rows])
56
+
57
+ client.run_with_names('fake query').should == [{
58
+ "animal" => "wrong",
59
+ "score" => "count",
60
+ "name" => nil,
61
+ }]
62
+ end
63
+
64
+ it 'handles too many result columns' do
65
+ rows = [['wrong', 'count', 'too', 'much', 'columns']]
66
+ client.stub(:run).and_return([columns, rows])
67
+
68
+ client.run_with_names('fake query').should == [{
69
+ "animal" => "wrong",
70
+ "score" => "count",
71
+ "name" => 'too',
72
+ }]
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,18 @@
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 'presto-client-legacy'
18
+ include PrestoLegacy::Client