trino-client 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +4 -0
- data/README.md +1 -1
- data/SECURITY.md +7 -0
- data/lib/trino/client/version.rb +1 -1
- data/trino-client.gemspec +2 -2
- metadata +5 -33
- data/.github/CODEOWNERS +0 -1
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -18
- data/.github/dependabot.yaml +0 -10
- data/.github/release-drafter.yml +0 -45
- data/.github/workflows/codeql-analysis.yml +0 -71
- data/.github/workflows/release-drafter.yml +0 -29
- data/.github/workflows/ruby.yml +0 -39
- data/.gitignore +0 -4
- data/.standard.yml +0 -15
- data/.standard_todo.yml +0 -80
- data/Gemfile +0 -7
- data/Rakefile +0 -44
- data/modelgen/model_versions.rb +0 -280
- data/modelgen/modelgen.rb +0 -117
- data/modelgen/models.rb +0 -31
- data/modelgen/trino_models.rb +0 -268
- data/publish.rb +0 -13
- data/release.rb +0 -56
- data/spec/basic_query_spec.rb +0 -82
- data/spec/client_spec.rb +0 -75
- data/spec/gzip_spec.rb +0 -40
- data/spec/model_spec.rb +0 -37
- data/spec/spec_helper.rb +0 -43
- data/spec/statement_client_spec.rb +0 -634
- data/spec/tpch/q01.sql +0 -21
- data/spec/tpch/q02.sql +0 -43
- data/spec/tpch_query_spec.rb +0 -41
- data/trino-client-ruby/lib/trino-client-ruby.rb +0 -1
- data/trino-client-ruby/trino-client-ruby.gemspec +0 -20
data/modelgen/model_versions.rb
DELETED
@@ -1,280 +0,0 @@
|
|
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::ModelVersions
|
17
|
-
|
18
|
-
####
|
19
|
-
## lib/trino/client/model_versions/*.rb is automatically generated using "rake modelgen:all" command.
|
20
|
-
## You should not edit this file directly. To modify the class definitions, edit
|
21
|
-
## modelgen/model_versions.rb file and run "rake modelgen:all".
|
22
|
-
##
|
23
|
-
|
24
|
-
module V<%= @model_version.gsub(".", "_") %>
|
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 Lifespan < String
|
72
|
-
def initialize(str)
|
73
|
-
super
|
74
|
-
if str == "TaskWide"
|
75
|
-
@grouped = false
|
76
|
-
@group_id = 0
|
77
|
-
else
|
78
|
-
# Group1
|
79
|
-
@grouped = true
|
80
|
-
@group_id = str[5..-1].to_i
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
attr_reader :grouped, :group_id
|
85
|
-
end
|
86
|
-
|
87
|
-
class ConnectorSession < Hash
|
88
|
-
def initialize(hash)
|
89
|
-
super()
|
90
|
-
merge!(hash)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
module PlanNode
|
95
|
-
def self.decode(hash)
|
96
|
-
unless hash.is_a?(Hash)
|
97
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
98
|
-
end
|
99
|
-
model_class = case hash["@type"]
|
100
|
-
when "output" then OutputNode
|
101
|
-
when "project" then ProjectNode
|
102
|
-
when "tablescan" then TableScanNode
|
103
|
-
when "values" then ValuesNode
|
104
|
-
when "aggregation" then AggregationNode
|
105
|
-
when "markDistinct" then MarkDistinctNode
|
106
|
-
when "filter" then FilterNode
|
107
|
-
when "window" then WindowNode
|
108
|
-
when "rowNumber" then RowNumberNode
|
109
|
-
when "topnRowNumber" then TopNRowNumberNode
|
110
|
-
when "limit" then LimitNode
|
111
|
-
when "distinctlimit" then DistinctLimitNode
|
112
|
-
when "topn" then TopNNode
|
113
|
-
when "sample" then SampleNode
|
114
|
-
when "sort" then SortNode
|
115
|
-
when "remoteSource" then RemoteSourceNode
|
116
|
-
when "join" then JoinNode
|
117
|
-
when "semijoin" then SemiJoinNode
|
118
|
-
when "spatialjoin" then SpatialJoinNode
|
119
|
-
when "indexjoin" then IndexJoinNode
|
120
|
-
when "indexsource" then IndexSourceNode
|
121
|
-
when "tablewriter" then TableWriterNode
|
122
|
-
when "delete" then DeleteNode
|
123
|
-
when "metadatadelete" then MetadataDeleteNode
|
124
|
-
when "tablecommit" then TableFinishNode
|
125
|
-
when "unnest" then UnnestNode
|
126
|
-
when "exchange" then ExchangeNode
|
127
|
-
when "union" then UnionNode
|
128
|
-
when "intersect" then IntersectNode
|
129
|
-
when "scalar" then EnforceSingleRowNode
|
130
|
-
when "groupid" then GroupIdNode
|
131
|
-
when "explainAnalyze" then ExplainAnalyzeNode
|
132
|
-
when "apply" then ApplyNode
|
133
|
-
when "assignUniqueId" then AssignUniqueId
|
134
|
-
when "correlatedJoin" then CorrelatedJoinNode
|
135
|
-
when "statisticsWriterNode" then StatisticsWriterNode
|
136
|
-
end
|
137
|
-
if model_class
|
138
|
-
node = model_class.decode(hash)
|
139
|
-
class << node
|
140
|
-
attr_accessor :plan_node_type
|
141
|
-
end
|
142
|
-
node.plan_node_type = hash['@type']
|
143
|
-
node
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# io.airlift.stats.Distribution.DistributionSnapshot
|
149
|
-
class << DistributionSnapshot =
|
150
|
-
Base.new(:max_error, :count, :total, :p01, :p05, :p10, :p25, :p50, :p75, :p90, :p95, :p99, :min, :max)
|
151
|
-
def decode(hash)
|
152
|
-
unless hash.is_a?(Hash)
|
153
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
154
|
-
end
|
155
|
-
obj = allocate
|
156
|
-
obj.send(:initialize_struct,
|
157
|
-
hash["maxError"],
|
158
|
-
hash["count"],
|
159
|
-
hash["total"],
|
160
|
-
hash["p01"],
|
161
|
-
hash["p05"],
|
162
|
-
hash["p10"],
|
163
|
-
hash["p25"],
|
164
|
-
hash["p50"],
|
165
|
-
hash["p75"],
|
166
|
-
hash["p90"],
|
167
|
-
hash["p95"],
|
168
|
-
hash["p99"],
|
169
|
-
hash["min"],
|
170
|
-
hash["max"],
|
171
|
-
)
|
172
|
-
obj
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
# This is a hybrid of JoinNode.EquiJoinClause and IndexJoinNode.EquiJoinClause
|
177
|
-
class << EquiJoinClause =
|
178
|
-
Base.new(:left, :right, :probe, :index)
|
179
|
-
def decode(hash)
|
180
|
-
unless hash.is_a?(Hash)
|
181
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
182
|
-
end
|
183
|
-
obj = allocate
|
184
|
-
obj.send(:initialize_struct,
|
185
|
-
hash["left"],
|
186
|
-
hash["right"],
|
187
|
-
hash["probe"],
|
188
|
-
hash["index"],
|
189
|
-
)
|
190
|
-
obj
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
class << WriterTarget =
|
195
|
-
Base.new(:type, :handle)
|
196
|
-
def decode(hash)
|
197
|
-
unless hash.is_a?(Hash)
|
198
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
199
|
-
end
|
200
|
-
obj = allocate
|
201
|
-
model_class = case hash["@type"]
|
202
|
-
when "CreateTarget" then CreateTarget
|
203
|
-
when "InsertTarget" then InsertTarget
|
204
|
-
when "DeleteTarget" then DeleteTarget
|
205
|
-
end
|
206
|
-
if model_class
|
207
|
-
model_class.decode(hash)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
class << WriteStatisticsTarget =
|
213
|
-
Base.new(:type, :handle)
|
214
|
-
def decode(hash)
|
215
|
-
unless hash.is_a?(Hash)
|
216
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
217
|
-
end
|
218
|
-
obj = allocate
|
219
|
-
model_class = case hash["@type"]
|
220
|
-
when "WriteStatisticsHandle" then WriteStatisticsHandle
|
221
|
-
end
|
222
|
-
if model_class
|
223
|
-
model_class.decode(hash)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
# Inner classes
|
229
|
-
module OperatorInfo
|
230
|
-
def self.decode(hash)
|
231
|
-
unless hash.is_a?(Hash)
|
232
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
233
|
-
end
|
234
|
-
model_class = case hash["@type"]
|
235
|
-
when "exchangeClientStatus" then ExchangeClientStatus
|
236
|
-
when "localExchangeBuffer" then LocalExchangeBufferInfo
|
237
|
-
when "tableFinish" then TableFinishInfo
|
238
|
-
when "splitOperator" then SplitOperatorInfo
|
239
|
-
when "hashCollisionsInfo" then HashCollisionsInfo
|
240
|
-
when "partitionedOutput" then PartitionedOutputInfo
|
241
|
-
when "joinOperatorInfo" then JoinOperatorInfo
|
242
|
-
when "windowInfo" then WindowInfo
|
243
|
-
when "tableWriter" then TableWriterInfo
|
244
|
-
end
|
245
|
-
if model_class
|
246
|
-
model_class.decode(hash)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
class << HashCollisionsInfo =
|
252
|
-
Base.new(:weighted_hash_collisions, :weighted_sum_squared_hash_collisions, :weighted_expectedHash_collisions)
|
253
|
-
def decode(hash)
|
254
|
-
unless hash.is_a?(Hash)
|
255
|
-
raise TypeError, "Can't convert #{hash.class} to Hash"
|
256
|
-
end
|
257
|
-
obj = allocate
|
258
|
-
obj.send(:initialize_struct,
|
259
|
-
hash["weighted_hash_collisions"],
|
260
|
-
hash["weighted_sum_squared_hash_collisions"],
|
261
|
-
hash["weighted_expectedHash_collisions"]
|
262
|
-
)
|
263
|
-
obj
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
class ResourceGroupId < Array
|
268
|
-
def initialize(array)
|
269
|
-
super()
|
270
|
-
concat(array)
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
##
|
275
|
-
# Those model classes are automatically generated
|
276
|
-
#
|
277
|
-
|
278
|
-
<%= @contents %>
|
279
|
-
end
|
280
|
-
end
|
data/modelgen/modelgen.rb
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
if ARGV.length != 4
|
2
|
-
puts "usage: <model-version> <trino-source-dir> <template.erb> <output.rb>"
|
3
|
-
exit 1
|
4
|
-
end
|
5
|
-
|
6
|
-
model_version, source_dir, template_path, output_path = *ARGV
|
7
|
-
|
8
|
-
require_relative 'trino_models'
|
9
|
-
|
10
|
-
require 'erb'
|
11
|
-
erb = ERB.new(File.read(template_path))
|
12
|
-
|
13
|
-
source_path = source_dir
|
14
|
-
|
15
|
-
predefined_simple_classes = %w[StageId TaskId Lifespan ConnectorSession ResourceGroupId]
|
16
|
-
predefined_models = %w[DistributionSnapshot PlanNode EquiJoinClause WriterTarget WriteStatisticsTarget OperatorInfo HashCollisionsInfo]
|
17
|
-
|
18
|
-
assume_primitive = %w[Object Type Long Symbol QueryId PlanNodeId PlanFragmentId MemoryPoolId TransactionId URI Duration DataSize DateTime ColumnHandle ConnectorTableHandle ConnectorOutputTableHandle ConnectorIndexHandle ConnectorColumnHandle ConnectorInsertTableHandle ConnectorTableLayoutHandle Expression FunctionCall TimeZoneKey Locale TypeSignature Frame TupleDomain<ColumnHandle> SerializableNativeValue ConnectorTransactionHandle OutputBufferId ConnectorPartitioningHandle NullableValue ConnectorId HostAddress JsonNode Node CatalogName QualifiedObjectName FunctionId DynamicFilterId Instant]
|
19
|
-
enum_types = %w[QueryState StageState TaskState QueueState PlanDistribution OutputPartitioning Step SortOrder BufferState NullPartitioning BlockedReason ParameterKind FunctionKind PartitionFunctionHandle Scope ErrorType DistributionType PipelineExecutionStrategy JoinType ExchangeNode.Type ColumnStatisticType TableStatisticType StageExecutionStrategy SemanticErrorCode QueryType]
|
20
|
-
|
21
|
-
root_models = %w[QueryResults QueryInfo BasicQueryInfo] + %w[
|
22
|
-
OutputNode
|
23
|
-
ProjectNode
|
24
|
-
TableScanNode
|
25
|
-
ValuesNode
|
26
|
-
AggregationNode
|
27
|
-
MarkDistinctNode
|
28
|
-
FilterNode
|
29
|
-
WindowNode
|
30
|
-
RowNumberNode
|
31
|
-
TopNRowNumberNode
|
32
|
-
LimitNode
|
33
|
-
DistinctLimitNode
|
34
|
-
TopNNode
|
35
|
-
SampleNode
|
36
|
-
SortNode
|
37
|
-
RemoteSourceNode
|
38
|
-
JoinNode
|
39
|
-
SemiJoinNode
|
40
|
-
SpatialJoinNode
|
41
|
-
IndexJoinNode
|
42
|
-
IndexSourceNode
|
43
|
-
TableWriterNode
|
44
|
-
DeleteNode
|
45
|
-
TableFinishNode
|
46
|
-
UnnestNode
|
47
|
-
ExchangeNode
|
48
|
-
UnionNode
|
49
|
-
IntersectNode
|
50
|
-
EnforceSingleRowNode
|
51
|
-
GroupIdNode
|
52
|
-
ExplainAnalyzeNode
|
53
|
-
ApplyNode
|
54
|
-
AssignUniqueId
|
55
|
-
CorrelatedJoinNode
|
56
|
-
StatisticsWriterNode
|
57
|
-
] + %w[
|
58
|
-
ExchangeClientStatus
|
59
|
-
LocalExchangeBufferInfo
|
60
|
-
TableFinishInfo
|
61
|
-
SplitOperatorInfo
|
62
|
-
PartitionedOutputInfo
|
63
|
-
JoinOperatorInfo
|
64
|
-
WindowInfo
|
65
|
-
TableWriterInfo
|
66
|
-
]
|
67
|
-
|
68
|
-
name_mapping = Hash[*%w[
|
69
|
-
StatementStats StageStats ClientStageStats
|
70
|
-
ClientStageStats StageStats ClientStageStats
|
71
|
-
QueryResults Column ClientColumn
|
72
|
-
].each_slice(3).map { |x, y, z| [[x, y], z] }.flatten(1)]
|
73
|
-
|
74
|
-
path_mapping = Hash[*%w[
|
75
|
-
ClientColumn client/trino-client/src/main/java/io/trino/client/Column.java
|
76
|
-
ClientStageStats client/trino-client/src/main/java/io/trino/client/StageStats.java
|
77
|
-
Column core/trino-main/src/main/java/io/trino/execution/Column.java
|
78
|
-
QueryStats core/trino-main/src/main/java/io/trino/execution/QueryStats.java
|
79
|
-
StageStats core/trino-main/src/main/java/io/trino/execution/StageStats.java
|
80
|
-
PartitionedOutputInfo core/trino-main/src/main/java/io/trino/operator/PartitionedOutputOperator.java
|
81
|
-
TableWriterInfo core/trino-main/src/main/java/io/trino/operator/TableWriterOperator.java
|
82
|
-
TableInfo core/trino-main/src/main/java/io/trino/execution/TableInfo.java
|
83
|
-
DynamicFiltersStats core/trino-main/src/main/java/io/trino/server/DynamicFilterService.java
|
84
|
-
].map.with_index { |v, i| (i % 2 == 0) ? v : (source_path + "/" + v) }]
|
85
|
-
|
86
|
-
# model => [ [key,nullable,type], ... ]
|
87
|
-
extra_fields = {
|
88
|
-
'QueryInfo' => [['finalQueryInfo', nil, 'boolean']]
|
89
|
-
}
|
90
|
-
|
91
|
-
analyzer = TrinoModels::ModelAnalyzer.new(
|
92
|
-
source_path,
|
93
|
-
skip_models: predefined_models + predefined_simple_classes + assume_primitive + enum_types,
|
94
|
-
path_mapping: path_mapping,
|
95
|
-
name_mapping: name_mapping,
|
96
|
-
extra_fields: extra_fields
|
97
|
-
)
|
98
|
-
analyzer.analyze(root_models)
|
99
|
-
models = analyzer.models
|
100
|
-
skipped_models = analyzer.skipped_models
|
101
|
-
|
102
|
-
formatter = TrinoModels::ModelFormatter.new(
|
103
|
-
base_indent_count: 2,
|
104
|
-
struct_class: "Base",
|
105
|
-
special_struct_initialize_method: "initialize_struct",
|
106
|
-
primitive_types: assume_primitive,
|
107
|
-
skip_types: skipped_models,
|
108
|
-
simple_classes: predefined_simple_classes,
|
109
|
-
enum_types: enum_types
|
110
|
-
)
|
111
|
-
formatter.format(models)
|
112
|
-
|
113
|
-
@contents = formatter.contents
|
114
|
-
@model_version = model_version
|
115
|
-
|
116
|
-
data = erb.result
|
117
|
-
File.write(output_path, data)
|
data/modelgen/models.rb
DELETED
@@ -1,31 +0,0 @@
|
|
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
|
-
<% @versions.each do |ver| %>
|
27
|
-
require 'trino/client/model_versions/<%= ver %>.rb'<% end %>
|
28
|
-
|
29
|
-
Models = ModelVersions::V<%= @latest_version.gsub(".", "_") %>
|
30
|
-
|
31
|
-
end
|
data/modelgen/trino_models.rb
DELETED
@@ -1,268 +0,0 @@
|
|
1
|
-
module TrinoModels
|
2
|
-
require 'find'
|
3
|
-
require 'stringio'
|
4
|
-
|
5
|
-
PRIMITIVE_TYPES = %w[String boolean long int short byte double float Integer Double Boolean]
|
6
|
-
ARRAY_PRIMITIVE_TYPES = PRIMITIVE_TYPES.map { |t| "#{t}[]" }
|
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, :base_type_alias)
|
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 + ARRAY_PRIMITIVE_TYPES + (options[:skip_models] || [])
|
28
|
-
@path_mapping = options[:path_mapping] || {}
|
29
|
-
@name_mapping = options[:name_mapping] || {}
|
30
|
-
@extra_fields = options[:extra_fields] || {}
|
31
|
-
@models = {}
|
32
|
-
@skipped_models = []
|
33
|
-
end
|
34
|
-
|
35
|
-
attr_reader :skipped_models
|
36
|
-
|
37
|
-
def models
|
38
|
-
@models.values.sort_by { |model| model.name }
|
39
|
-
end
|
40
|
-
|
41
|
-
def analyze(root_models)
|
42
|
-
root_models.each { |model_name|
|
43
|
-
analyze_model(model_name)
|
44
|
-
}
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
PROPERTY_PATTERN = /@JsonProperty\(\"(\w+)\"\)\s+(@Nullable\s+)?([\w\<\>\[\]\,\s\.]+)\s+\w+/
|
50
|
-
CREATOR_PATTERN = /@JsonCreator[\s]+public[\s]+(static\s+)?(\w+)[\w\s]*\((?:\s*#{PROPERTY_PATTERN}\s*,?)+\)/
|
51
|
-
GENERIC_PATTERN = /(\w+)\<(\w+)\>/
|
52
|
-
|
53
|
-
def analyze_fields(model_name, creator_block, generic: nil)
|
54
|
-
model_name = "#{model_name}_#{generic}" if generic
|
55
|
-
extra = @extra_fields[model_name] || []
|
56
|
-
fields = creator_block.scan(PROPERTY_PATTERN).concat(extra).map do |key, nullable, type|
|
57
|
-
map = false
|
58
|
-
array = false
|
59
|
-
nullable = !!nullable
|
60
|
-
if m = /(?:List|Set)<(\w+)>/.match(type)
|
61
|
-
base_type = m[1]
|
62
|
-
array = true
|
63
|
-
elsif m = /(?:Map|ListMultimap)<(\w+),\s*(\w+)>/.match(type)
|
64
|
-
base_type = m[1]
|
65
|
-
map_value_base_type = m[2]
|
66
|
-
map = true
|
67
|
-
elsif m = /Optional<([\w\[\]\<\>]+)>/.match(type)
|
68
|
-
base_type = m[1]
|
69
|
-
nullable = true
|
70
|
-
elsif m = /OptionalInt/.match(type)
|
71
|
-
base_type = 'Integer'
|
72
|
-
nullable = true
|
73
|
-
elsif m = /OptionalLong/.match(type)
|
74
|
-
base_type = 'Long'
|
75
|
-
nullable = true
|
76
|
-
elsif m = /OptionalDouble/.match(type)
|
77
|
-
base_type = 'Double'
|
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
|
-
|
87
|
-
if generic
|
88
|
-
base_type = generic if base_type == 'T'
|
89
|
-
map_value_base_type = generic if map_value_base_type == 'T'
|
90
|
-
end
|
91
|
-
if m = GENERIC_PATTERN.match(base_type)
|
92
|
-
base_type_alias = "#{m[1]}_#{m[2]}"
|
93
|
-
end
|
94
|
-
|
95
|
-
Field.new(key, !!nullable, array, map, type, base_type, map_value_base_type, base_type_alias)
|
96
|
-
end
|
97
|
-
|
98
|
-
@models[model_name] = Model.new(model_name, fields)
|
99
|
-
# recursive call
|
100
|
-
fields.each do |field|
|
101
|
-
analyze_model(field.base_type, model_name)
|
102
|
-
analyze_model(field.map_value_base_type, model_name) if field.map_value_base_type
|
103
|
-
end
|
104
|
-
|
105
|
-
fields
|
106
|
-
end
|
107
|
-
|
108
|
-
def analyze_model(model_name, parent_model = nil, generic: nil)
|
109
|
-
return if @models[model_name] || @ignore_types.include?(model_name)
|
110
|
-
|
111
|
-
if m = GENERIC_PATTERN.match(model_name)
|
112
|
-
analyze_model(m[1], generic: m[2])
|
113
|
-
analyze_model(m[2])
|
114
|
-
return
|
115
|
-
end
|
116
|
-
|
117
|
-
path = find_class_file(model_name, parent_model)
|
118
|
-
java = File.read(path)
|
119
|
-
|
120
|
-
m = CREATOR_PATTERN.match(java)
|
121
|
-
unless m
|
122
|
-
raise ModelAnalysisError, "Can't find JsonCreator of a model class #{model_name} of #{parent_model} at #{path}"
|
123
|
-
end
|
124
|
-
|
125
|
-
body = m[0]
|
126
|
-
# check inner class first
|
127
|
-
while true
|
128
|
-
offset = m.end(0)
|
129
|
-
m = CREATOR_PATTERN.match(java, offset)
|
130
|
-
break unless m
|
131
|
-
inner_model_name = m[2]
|
132
|
-
next if @models[inner_model_name] || @ignore_types.include?(inner_model_name)
|
133
|
-
fields = analyze_fields(inner_model_name, m[0])
|
134
|
-
end
|
135
|
-
|
136
|
-
fields = analyze_fields(model_name, body, generic: generic)
|
137
|
-
|
138
|
-
rescue => e
|
139
|
-
puts "Skipping model #{parent_model}/#{model_name}: #{e}"
|
140
|
-
@skipped_models << model_name
|
141
|
-
end
|
142
|
-
|
143
|
-
def find_class_file(model_name, parent_model)
|
144
|
-
return @path_mapping[model_name] if @path_mapping.has_key? model_name
|
145
|
-
|
146
|
-
@source_files ||= Find.find(@source_path).to_a
|
147
|
-
pattern = /\/#{model_name}.java$/
|
148
|
-
matched = @source_files.find_all { |path| path =~ pattern && !path.include?('/test/') && !path.include?('/verifier/') }
|
149
|
-
if matched.empty?
|
150
|
-
raise ModelAnalysisError, "Model class #{model_name} is not found"
|
151
|
-
end
|
152
|
-
if matched.size == 1
|
153
|
-
matched.first
|
154
|
-
else
|
155
|
-
raise ModelAnalysisError, "Model class #{model_name} of #{parent_model} found multiple match #{matched}"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
class ModelFormatter
|
161
|
-
def initialize(options = {})
|
162
|
-
@indent = options[:indent] || ' '
|
163
|
-
@base_indent_count = options[:base_indent_count] || 0
|
164
|
-
@struct_class = options[:struct_class] || 'Struct'
|
165
|
-
@special_struct_initialize_method = options[:special_struct_initialize_method]
|
166
|
-
@primitive_types = PRIMITIVE_TYPES + ARRAY_PRIMITIVE_TYPES + (options[:primitive_types] || [])
|
167
|
-
@skip_types = options[:skip_types] || []
|
168
|
-
@simple_classes = options[:simple_classes]
|
169
|
-
@enum_types = options[:enum_types]
|
170
|
-
@special_types = options[:special_types] || {}
|
171
|
-
@data = StringIO.new
|
172
|
-
end
|
173
|
-
|
174
|
-
def contents
|
175
|
-
@data.string
|
176
|
-
end
|
177
|
-
|
178
|
-
def format(models)
|
179
|
-
@models = models
|
180
|
-
models.each do |model|
|
181
|
-
@model = model
|
182
|
-
|
183
|
-
puts_with_indent 0, "class << #{model.name} ="
|
184
|
-
puts_with_indent 2, "#{@struct_class}.new(#{model.fields.map { |f| ":#{f.name}" }.join(', ')})"
|
185
|
-
format_decode
|
186
|
-
puts_with_indent 0, "end"
|
187
|
-
line
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
private
|
192
|
-
|
193
|
-
def line
|
194
|
-
@data.puts ""
|
195
|
-
end
|
196
|
-
|
197
|
-
def puts_with_indent(n, str)
|
198
|
-
@data.puts "#{@indent * (@base_indent_count + n)}#{str}"
|
199
|
-
end
|
200
|
-
|
201
|
-
def format_decode
|
202
|
-
puts_with_indent 1, "def decode(hash)"
|
203
|
-
|
204
|
-
puts_with_indent 2, "unless hash.is_a?(Hash)"
|
205
|
-
puts_with_indent 3, "raise TypeError, \"Can't convert \#{hash.class} to Hash\""
|
206
|
-
puts_with_indent 2, "end"
|
207
|
-
|
208
|
-
if @special_struct_initialize_method
|
209
|
-
puts_with_indent 2, "obj = allocate"
|
210
|
-
puts_with_indent 2, "obj.send(:#{@special_struct_initialize_method},"
|
211
|
-
else
|
212
|
-
puts_with_indent 2, "new("
|
213
|
-
end
|
214
|
-
|
215
|
-
@model.fields.each do |field|
|
216
|
-
next if @skip_types.include?(field.base_type) || @skip_types.include?(field.map_value_base_type)
|
217
|
-
|
218
|
-
if @primitive_types.include?(field.base_type) && !field.map?
|
219
|
-
expr = "hash[\"#{field.key}\"]"
|
220
|
-
else
|
221
|
-
expr = ""
|
222
|
-
expr << "hash[\"#{field.key}\"] && " # if field.nullable?
|
223
|
-
|
224
|
-
if field.map?
|
225
|
-
key_expr = convert_expression(field.base_type, field.base_type, "k")
|
226
|
-
value_expr = convert_expression(field.map_value_base_type, field.map_value_base_type, "v")
|
227
|
-
if key_expr == 'k' && value_expr == 'v'
|
228
|
-
expr = "hash[\"#{field.key}\"]"
|
229
|
-
else
|
230
|
-
expr << "Hash[hash[\"#{field.key}\"].to_a.map! {|k,v| [#{key_expr}, #{value_expr}] }]"
|
231
|
-
end
|
232
|
-
elsif field.array?
|
233
|
-
elem_expr = convert_expression(field.base_type, field.base_type, "h")
|
234
|
-
expr << "hash[\"#{field.key}\"].map {|h| #{elem_expr} }"
|
235
|
-
else
|
236
|
-
expr << convert_expression(field.type, field.base_type_alias || field.base_type, "hash[\"#{field.key}\"]")
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
# comment = "# #{field.base_type}#{field.array? ? '[]' : ''} #{field.key}"
|
241
|
-
# puts_with_indent 3, "#{expr}, #{comment}"
|
242
|
-
puts_with_indent 3, "#{expr},"
|
243
|
-
end
|
244
|
-
|
245
|
-
puts_with_indent 2, ")"
|
246
|
-
|
247
|
-
if @special_struct_initialize_method
|
248
|
-
puts_with_indent 2, "obj"
|
249
|
-
end
|
250
|
-
|
251
|
-
puts_with_indent 1, "end"
|
252
|
-
end
|
253
|
-
|
254
|
-
def convert_expression(type, base_type, key)
|
255
|
-
if @special_types[type]
|
256
|
-
special.call(key)
|
257
|
-
elsif @enum_types.include?(type) || @enum_types.include?(base_type)
|
258
|
-
"#{key}.downcase.to_sym"
|
259
|
-
elsif @primitive_types.include?(base_type)
|
260
|
-
key
|
261
|
-
elsif @simple_classes.include?(base_type)
|
262
|
-
"#{base_type}.new(#{key})"
|
263
|
-
else # model class
|
264
|
-
"#{base_type}.decode(#{key})"
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|