presto-client-legacy 0.4.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -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