active-orient 0.79 → 0.80
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.
- checksums.yaml +4 -4
- data/.graphs.txt.swp +0 -0
- data/Gemfile +2 -6
- data/README.md +29 -27
- data/VERSION +1 -1
- data/active-orient.gemspec +4 -3
- data/bin/active-orient-console +18 -6
- data/changelog.md +60 -0
- data/config/connect.yml +8 -8
- data/examples/books.rb +134 -97
- data/graphs.txt +70 -0
- data/lib/active-orient.rb +2 -0
- data/lib/base.rb +38 -17
- data/lib/base_properties.rb +15 -14
- data/lib/class_utils.rb +11 -50
- data/lib/database_utils.rb +23 -22
- data/lib/init.rb +4 -3
- data/lib/model/custom.rb +7 -4
- data/lib/model/e.rb +6 -0
- data/lib/model/edge.rb +74 -30
- data/lib/model/the_class.rb +181 -131
- data/lib/model/the_record.rb +115 -68
- data/lib/model/vertex.rb +261 -126
- data/lib/other.rb +93 -41
- data/lib/rest/change.rb +23 -20
- data/lib/rest/create.rb +71 -63
- data/lib/rest/delete.rb +80 -64
- data/lib/rest/operations.rb +79 -68
- data/lib/rest/read.rb +42 -24
- data/lib/rest/rest.rb +38 -30
- data/lib/support/conversions.rb +42 -0
- data/lib/support/default_formatter.rb +7 -0
- data/lib/support/errors.rb +41 -0
- data/lib/support/orient.rb +167 -58
- data/lib/support/orientquery.rb +526 -348
- data/lib/support/query.rb +92 -0
- metadata +34 -18
- data/examples/test_commands.rb +0 -97
- data/examples/test_commands_2.rb +0 -59
- data/examples/test_commands_3.rb +0 -55
- data/examples/test_commands_4.rb +0 -33
- data/examples/time_graph.md +0 -162
data/lib/support/orientquery.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/inflector'
|
2
|
+
|
2
3
|
module OrientSupport
|
3
4
|
module Support
|
4
5
|
|
@@ -16,10 +17,13 @@ _Usecase:_
|
|
16
17
|
=end
|
17
18
|
|
18
19
|
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
def compose_where *arg , &b
|
21
|
+
arg = arg.flatten.compact
|
22
|
+
|
23
|
+
unless arg.blank?
|
24
|
+
g= generate_sql_list( arg , &b)
|
25
|
+
"where #{g}" unless g.empty?
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
=begin
|
@@ -28,442 +32,616 @@ designs a list of "Key = Value" pairs combined by "and" or the binding provide
|
|
28
32
|
=> "where = 25 and upper = '65'"
|
29
33
|
ORD.generate_sql_list( con_id: 25 , symbol: :G) { ',' }
|
30
34
|
=> "con_id = 25 , symbol = 'G'"
|
35
|
+
|
36
|
+
If »NULL« should be addressed, { key: nil } is translated to "key = NULL" (used by set: in update and upsert),
|
37
|
+
{ key: [nil] } is translated to "key is NULL" ( used by where )
|
38
|
+
|
31
39
|
=end
|
32
|
-
def generate_sql_list attributes = {},
|
40
|
+
def generate_sql_list attributes = {}, &b
|
33
41
|
fill = block_given? ? yield : 'and'
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
when Numeric
|
41
|
-
"#{key} = #{value}"
|
42
|
-
when ::Array
|
43
|
-
"#{key} in [#{value.to_orient}]"
|
44
|
-
when Range
|
45
|
-
"#{key} between #{value.first} and #{value.last} "
|
46
|
-
when DateTime
|
47
|
-
"#{key} = date(\'#{value.strftime("%Y%m%d%H%M%S")}\',\'yyyyMMddHHmmss\')"
|
48
|
-
when Date
|
49
|
-
"#{key} = date(\'#{value.to_s}\',\'yyyy-MM-dd\')"
|
50
|
-
else # String, Symbol, Time, Trueclass, Falseclass ...
|
51
|
-
"#{key} = \'#{value.to_s}\'"
|
52
|
-
end
|
53
|
-
end.join(" #{fill} ")
|
42
|
+
case attributes
|
43
|
+
when ::Hash
|
44
|
+
attributes.map do |key, value|
|
45
|
+
case value
|
46
|
+
when nil
|
47
|
+
"#{key} = NULL"
|
54
48
|
when ::Array
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
49
|
+
if value == [nil]
|
50
|
+
"#{key} is NULL"
|
51
|
+
else
|
52
|
+
"#{key} in #{value.to_orient}"
|
53
|
+
end
|
54
|
+
when Range
|
55
|
+
"#{key} between #{value.first} and #{value.last} "
|
56
|
+
else # String, Symbol, Time, Trueclass, Falseclass ...
|
57
|
+
"#{key} = #{value.to_or}"
|
58
|
+
end
|
59
|
+
end.join(" #{fill} ")
|
60
|
+
when ::Array
|
61
|
+
attributes.map{|y| generate_sql_list y, &b }.join( " #{fill} " )
|
62
|
+
when String
|
63
|
+
attributes
|
64
|
+
when Symbol, Numeric
|
65
|
+
attributes.to_s
|
66
|
+
end
|
59
67
|
end
|
60
|
-
end
|
61
68
|
|
62
69
|
|
70
|
+
# used both in OrientQuery and MatchConnect
|
71
|
+
# while and where depend on @q, a struct
|
72
|
+
def while_s value=nil # :nodoc:
|
73
|
+
if value.present?
|
74
|
+
@q[:while] << value
|
75
|
+
self
|
76
|
+
elsif @q[:while].present?
|
77
|
+
"while #{ generate_sql_list( @q[:while] ) }"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
def where value=nil # :nodoc:
|
81
|
+
if value.present?
|
82
|
+
if value.is_a?( Hash ) && value.size >1
|
83
|
+
value.each {| a,b| where( {a => b} ) }
|
84
|
+
else
|
85
|
+
@q[:where] << value
|
86
|
+
end
|
87
|
+
self
|
88
|
+
elsif @q[:where].present?
|
89
|
+
"where #{ generate_sql_list( @q[:where] ){ @fill || 'and' } }"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def as a=nil
|
94
|
+
if a
|
95
|
+
@q[:as] = a # subsequent calls overwrite older entries
|
96
|
+
else
|
97
|
+
if @q[:as].blank?
|
98
|
+
nil
|
99
|
+
else
|
100
|
+
"as: #{ @q[:as] }"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end # module
|
105
|
+
|
106
|
+
######################## MatchConnection ###############################
|
107
|
+
|
108
|
+
MatchAttributes = Struct.new(:edge, :direction, :as, :count, :where, :while, :max_depth , :depth_alias, :path_alias, :optional )
|
109
|
+
|
110
|
+
# where and while can be composed incremental
|
111
|
+
# direction, as, connect and edge cannot be changed after initialisation
|
63
112
|
class MatchConnection
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
113
|
+
include Support
|
114
|
+
|
115
|
+
def initialize edge= nil, direction: :both, as: nil, count: 1, **args
|
116
|
+
|
117
|
+
the_edge = edge.is_a?( Class ) ? edge.ref_name : edge.to_s unless edge.nil? || edge == E
|
118
|
+
@q = MatchAttributes.new the_edge , # class
|
119
|
+
direction, # may be :both, :in, :out
|
120
|
+
as, # a string
|
121
|
+
count, # a number
|
122
|
+
args[:where],
|
123
|
+
args[:while],
|
124
|
+
args[:max_depth],
|
125
|
+
args[:depth_alias], # not implemented
|
126
|
+
args[:path_alias], # not implemented
|
127
|
+
args[:optional] # not implemented
|
70
128
|
end
|
71
129
|
|
72
130
|
def direction= dir
|
73
|
-
@direction = dir
|
131
|
+
@q[:direction] = dir
|
74
132
|
end
|
75
133
|
|
76
134
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
135
|
+
def direction
|
136
|
+
fillup = @q[:edge].present? ? @q[:edge] : ''
|
137
|
+
case @q[:direction]
|
138
|
+
when :both
|
139
|
+
".both(#{fillup})"
|
140
|
+
when :in
|
141
|
+
".in(#{fillup})"
|
142
|
+
when :out
|
143
|
+
".out(#{fillup})"
|
144
|
+
when :both_vertex, :bothV
|
145
|
+
".bothV()"
|
146
|
+
when :out_vertex, :outV
|
147
|
+
".outV()"
|
148
|
+
when :in_vertex, :inV
|
149
|
+
".inV()"
|
150
|
+
when :both_edge, :bothE
|
151
|
+
".bothE(#{fillup})"
|
152
|
+
when :out_edge, :outE
|
153
|
+
".outE(#{fillup})"
|
154
|
+
when :in_edge, :outE
|
155
|
+
".inE(#{fillup})"
|
156
|
+
end
|
89
157
|
|
90
|
-
|
91
|
-
ministatement = @as.present? ? "{ as: #{@as} } " : ""
|
92
|
-
(1 .. @count).map{|x| direction }.join("{}") << ministatement
|
158
|
+
end
|
93
159
|
|
94
|
-
|
95
|
-
|
96
|
-
|
160
|
+
def count c=nil
|
161
|
+
if c
|
162
|
+
@q[:count] = c
|
163
|
+
else
|
164
|
+
@q[:count]
|
165
|
+
end
|
166
|
+
end
|
97
167
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
@match_class = match_class
|
111
|
-
@as = match_class.pluralize if match_class.is_a? String
|
112
|
-
|
113
|
-
args.each do |k, v|
|
114
|
-
case k
|
115
|
-
when :as
|
116
|
-
@as = v
|
117
|
-
when :while
|
118
|
-
@while << v
|
119
|
-
when :where
|
120
|
-
@where << v
|
121
|
-
when :class
|
122
|
-
@match_class = v
|
123
|
-
@as = v.pluralize
|
124
|
-
else
|
125
|
-
self.send k, v
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def while_s
|
131
|
-
compose_where( @while ).gsub( /where/, 'while:(' )<< ")" unless @while.blank?
|
132
|
-
end
|
168
|
+
def max_depth d=nil
|
169
|
+
if d.nil?
|
170
|
+
@q[:max_depth].present? ? "maxDepth: #{@q[:max_depth] }" : nil
|
171
|
+
else
|
172
|
+
@q[:max_depth] = d
|
173
|
+
end
|
174
|
+
end
|
175
|
+
def edge
|
176
|
+
@q[:edge]
|
177
|
+
end
|
133
178
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
def maxdepth=x
|
142
|
-
@maxdepth = x
|
143
|
-
end
|
179
|
+
def compose
|
180
|
+
where_statement =( where.nil? || where.size <5 ) ? nil : "where: ( #{ generate_sql_list( @q[:where] ) })"
|
181
|
+
while_statement =( while_s.nil? || while_s.size <5) ? nil : "while: ( #{ generate_sql_list( @q[:while] )})"
|
182
|
+
|
183
|
+
ministatement = "{"+ [ as, where_statement, while_statement, max_depth].compact.join(', ') + "}"
|
184
|
+
ministatement = "" if ministatement=="{}"
|
144
185
|
|
145
|
-
|
146
|
-
@misc << method.to_s << generate_sql_list(arg)
|
147
|
-
end
|
186
|
+
(1 .. count).map{|x| direction }.join("") + ministatement
|
148
187
|
|
149
|
-
def misc
|
150
|
-
@misc.join(' ') unless @misc.empty?
|
151
|
-
end
|
152
|
-
# used for the first compose-statement of a compose-query
|
153
|
-
def compose_simple
|
154
|
-
'{'+ [ "class: #{@match_class}",
|
155
|
-
"as: #{@as}" ,
|
156
|
-
where_s ].compact.join(', ') + '}'
|
157
188
|
end
|
189
|
+
alias :to_s :compose
|
190
|
+
|
191
|
+
end # class
|
158
192
|
|
159
|
-
def compose
|
160
193
|
|
161
|
-
|
162
|
-
"as: #{@as}" ,
|
163
|
-
where_s,
|
164
|
-
while_s,
|
165
|
-
@maxdepth >0 ? "maxdepth: #{maxdepth}": nil ].compact.join(', ')+'}'
|
166
|
-
end
|
167
|
-
alias :to_s :compose
|
168
|
-
end
|
194
|
+
######################## MatchStatement ################################
|
169
195
|
|
170
|
-
|
196
|
+
MatchSAttributes = Struct.new(:match_class, :as, :where )
|
197
|
+
class MatchStatement
|
171
198
|
include Support
|
199
|
+
def initialize match_class, as: 0, **args
|
200
|
+
reduce_class = ->(c){ c.is_a?(Class) ? c.ref_name : c.to_s }
|
172
201
|
|
202
|
+
@q = MatchSAttributes.new( reduce_class[match_class], # class
|
203
|
+
as.respond_to?(:zero?) && as.zero? ? reduce_class[match_class].pluralize : as ,
|
204
|
+
args[ :where ])
|
173
205
|
|
174
|
-
|
175
|
-
attr_accessor :let
|
176
|
-
attr_accessor :projection
|
177
|
-
attr_accessor :order
|
178
|
-
attr_accessor :db
|
179
|
-
attr_accessor :match_statements
|
180
|
-
|
181
|
-
def initialize **args
|
182
|
-
@projection = []
|
183
|
-
@misc = []
|
184
|
-
@let = []
|
185
|
-
@where = []
|
186
|
-
@order = []
|
187
|
-
@aliases = []
|
188
|
-
@match_statements = []
|
189
|
-
@class = nil
|
190
|
-
@return = nil
|
191
|
-
@db = nil
|
192
|
-
@kind = 'select'
|
193
|
-
args.each do |k, v|
|
194
|
-
case k
|
195
|
-
when :projection
|
196
|
-
@projection << v
|
197
|
-
when :let
|
198
|
-
@let << v
|
199
|
-
when :order
|
200
|
-
@order << v
|
201
|
-
when :where
|
202
|
-
@where << v
|
203
|
-
when :kind
|
204
|
-
@kind = v
|
205
|
-
when :start
|
206
|
-
@match_statements[0] = MatchStatement.new **v
|
207
|
-
# @match_statements[1] = MatchConnection.new
|
208
|
-
when :connection
|
209
|
-
@match_statements[1] = MatchConnection.new **v
|
210
|
-
when :return
|
211
|
-
@aliases << v
|
212
|
-
else
|
213
|
-
self.send k, v
|
214
|
-
end
|
215
|
-
end
|
206
|
+
@query_stack = [ self ]
|
216
207
|
end
|
217
208
|
|
209
|
+
def match_alias
|
210
|
+
"as: #{@q[:as]}"
|
211
|
+
end
|
218
212
|
|
219
|
-
=begin
|
220
|
-
where: "r > 9" --> where r > 9
|
221
|
-
where: {a: 9, b: 's'} --> where a = 9 and b = 's'
|
222
|
-
where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
|
223
|
-
=end
|
224
|
-
def method_missing method, *arg, &b # :nodoc:
|
225
|
-
@misc << method.to_s << generate_sql_list(arg)
|
226
|
-
self.to_s # return compiled result
|
227
|
-
end
|
228
213
|
|
229
214
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
215
|
+
# used for the first compose-statement of a compose-query
|
216
|
+
def compose_simple
|
217
|
+
where_statement = where.is_a?(String) && where.size <3 ? nil : "where: ( #{ generate_sql_list( @q[:where] ) })"
|
218
|
+
'{'+ [ "class: #{@q[:match_class]}", as , where_statement].compact.join(', ') + '}'
|
219
|
+
end
|
234
220
|
|
235
|
-
def subquery # :nodoc:
|
236
|
-
nil
|
237
|
-
end
|
238
221
|
|
222
|
+
def << connection
|
223
|
+
@query_stack << connection
|
224
|
+
self # return MatchStatement
|
225
|
+
end
|
226
|
+
#
|
227
|
+
def compile &b
|
228
|
+
"match " + @query_stack.map( &:to_s ).join + return_statement( &b )
|
229
|
+
end
|
239
230
|
|
240
231
|
|
241
|
-
|
242
|
-
|
232
|
+
# executes the standard-case.
|
233
|
+
# returns
|
234
|
+
# * as: :hash : an array of hashes
|
235
|
+
# * as: :array : an array of hash-values
|
236
|
+
# * as :flatten: a simple array of hash-values
|
237
|
+
#
|
238
|
+
# The optional block is used to customize the output.
|
239
|
+
# All previously defiend »as«-Statements are provided though the control variable.
|
240
|
+
#
|
241
|
+
# Background
|
242
|
+
# A match query "Match {class aaa, as: 'aa'} return aa "
|
243
|
+
#
|
244
|
+
# returns [ aa: { result of the query, a Vertex or a value-item }, aa: {}...}, ...] ]
|
245
|
+
# (The standard case)
|
246
|
+
#
|
247
|
+
# A match query "Match {class aaa, as: 'aa'} return aa.name "
|
248
|
+
# returns [ aa.name: { name }, aa.name: { name }., ...] ]
|
249
|
+
#
|
250
|
+
# Now, execute( as: :flatten){ "aa.name" } returns
|
251
|
+
# [name1, name2 ,. ...]
|
252
|
+
#
|
253
|
+
#
|
254
|
+
# Return statements (examples from https://orientdb.org/docs/3.0.x/sql/SQL-Match.html)
|
255
|
+
# "person.name as name, friendship.since as since, friend.name as friend"
|
256
|
+
#
|
257
|
+
# " person.name + \" is a friend of \" + friend.name as friends"
|
258
|
+
#
|
259
|
+
# "$matches"
|
260
|
+
# "$elements"
|
261
|
+
# "$paths"
|
262
|
+
# "$pathElements"
|
263
|
+
#
|
264
|
+
#
|
265
|
+
#
|
266
|
+
def execute as: :hash, &b
|
267
|
+
r = V.db.execute{ compile &b }
|
268
|
+
case as
|
269
|
+
when :hash
|
270
|
+
r
|
271
|
+
when :array
|
272
|
+
r.map{|y| y.values}
|
273
|
+
when :flatten
|
274
|
+
r.map{|y| y.values}.orient_flatten
|
275
|
+
else
|
276
|
+
raise ArgumentError, "Specify parameter «as:» with :hash, :array, :flatten"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
# def compose
|
280
|
+
#
|
281
|
+
# '{'+ [ "class: #{@q[:match_class]}",
|
282
|
+
# "as: #{@as}" , where, while_s,
|
283
|
+
# @maxdepth >0 ? "maxdepth: #{maxdepth}": nil ].compact.join(', ')+'}'
|
284
|
+
# end
|
285
|
+
|
286
|
+
alias :to_s :compose_simple
|
287
|
+
|
288
|
+
|
289
|
+
## return_statement
|
290
|
+
#
|
291
|
+
# summarizes defined as-statements ready to be included as last parameter
|
292
|
+
# in the match-statement-stack
|
293
|
+
#
|
294
|
+
# They can be modified through a block.
|
295
|
+
#
|
296
|
+
# i.e
|
297
|
+
#
|
298
|
+
# t= TestQuery.match( where: {a: 9, b: 's'}, as: nil ) << E.connect("<-", as: :test)
|
299
|
+
# t.return_statement{|y| "#{y.last}.name"}
|
300
|
+
#
|
301
|
+
# =>> " return test.name"
|
302
|
+
#
|
303
|
+
#return_statement is always called through compile
|
304
|
+
#
|
305
|
+
# t.compile{|y| "#{y.last}.name"}
|
306
|
+
|
307
|
+
private
|
308
|
+
def return_statement
|
309
|
+
resolve_as = ->{ @query_stack.map{|s| s.as.split(':').last unless s.as.nil? }.compact }
|
310
|
+
" return " + statement = if block_given?
|
311
|
+
a= yield resolve_as[]
|
312
|
+
a.is_a?(Array) ? a.join(', ') : a
|
313
|
+
else
|
314
|
+
resolve_as[].join(', ')
|
315
|
+
end
|
243
316
|
|
244
|
-
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
end # class
|
245
321
|
|
246
|
-
A Match-Query alwas has an Entry-Stratement and maybe other Statements.
|
247
|
-
They are connected via " -> " (outE), "<-" (inE) or "--" (both).
|
248
322
|
|
249
|
-
|
323
|
+
######################## OrientQuery ###################################
|
250
324
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
325
|
+
QueryAttributes = Struct.new( :kind, :projection, :where, :let, :order, :while, :misc,
|
326
|
+
:class, :return, :aliases, :database,
|
327
|
+
:set, :remove, :group, :skip, :limit, :unwind )
|
328
|
+
|
329
|
+
class OrientQuery
|
330
|
+
include Support
|
257
331
|
|
258
|
-
The method returns the OrientSupport::MatchConnection object, which can be modified further.
|
259
|
-
It is compiled by calling compose
|
260
|
-
=end
|
261
332
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
333
|
+
#
|
334
|
+
def initialize **args
|
335
|
+
@q = QueryAttributes.new args[:kind] || 'select' ,
|
336
|
+
[], # :projection
|
337
|
+
[], # :where ,
|
338
|
+
[], # :let ,
|
339
|
+
[], # :order,
|
340
|
+
[], # :while,
|
341
|
+
[] , # misc
|
342
|
+
'', # class
|
343
|
+
'', # return
|
344
|
+
[], # aliases
|
345
|
+
'', # database
|
346
|
+
[], #set,
|
347
|
+
[] # remove
|
348
|
+
args.each{|k,v| send k, v}
|
349
|
+
@fill = block_given? ? yield : 'and'
|
266
350
|
end
|
351
|
+
|
267
352
|
|
268
353
|
=begin
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
354
|
+
where: "r > 9" --> where r > 9
|
355
|
+
where: {a: 9, b: 's'} --> where a = 9 and b = 's'
|
356
|
+
where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
|
357
|
+
=end
|
358
|
+
def method_missing method, *arg, &b # :nodoc:
|
359
|
+
if method ==:while || method=='while'
|
360
|
+
while_s arg.first
|
361
|
+
else
|
362
|
+
@q[:misc] << method.to_s << generate_sql_list(arg)
|
363
|
+
end
|
364
|
+
self
|
365
|
+
end
|
278
366
|
|
279
|
-
|
367
|
+
def misc # :nodoc:
|
368
|
+
@q[:misc].join(' ') unless @q[:misc].empty?
|
369
|
+
end
|
280
370
|
|
281
|
-
|
282
|
-
|
371
|
+
def subquery # :nodoc:
|
372
|
+
nil
|
373
|
+
end
|
283
374
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
375
|
+
|
376
|
+
def kind value=nil
|
377
|
+
if value.present?
|
378
|
+
@q[:kind] = value
|
379
|
+
self
|
380
|
+
else
|
381
|
+
@q[:kind]
|
382
|
+
end
|
383
|
+
end
|
289
384
|
=begin
|
290
385
|
Output the compiled query
|
291
386
|
Parameter: destination (rest, batch )
|
292
387
|
If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
|
293
388
|
=end
|
294
389
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
390
|
+
def compose(destination: :batch)
|
391
|
+
if kind.to_sym == :update
|
392
|
+
return_statement = "return after " + ( @q[:aliases].empty? ? "$current" : @q[:aliases].first.to_s)
|
393
|
+
[ 'update', target, set, remove, return_statement , where, limit ].compact.join(' ')
|
394
|
+
elsif kind.to_sym == :update!
|
395
|
+
[ 'update', target, set, where, limit, misc ].compact.join(' ')
|
396
|
+
elsif kind.to_sym == :create
|
397
|
+
[ "CREATE VERTEX", target, set ].compact.join(' ')
|
398
|
+
# [ kind, target, set, return_statement ,where, limit, misc ].compact.join(' ')
|
399
|
+
elsif kind.to_sym == :upsert
|
400
|
+
return_statement = "return after " + ( @q[:aliases].empty? ? "$current" : @q[:aliases].first.to_s)
|
401
|
+
[ "update", target, set,"upsert", return_statement , where, limit, misc ].compact.join(' ')
|
402
|
+
#[ kind, where, return_statement ].compact.join(' ')
|
403
|
+
elsif destination == :rest
|
404
|
+
[ kind, projection, from, let, where, subquery, misc, order, group_by, unwind, skip].compact.join(' ')
|
405
|
+
else
|
406
|
+
[ kind, projection, from, let, where, subquery, while_s, misc, order, group_by, limit, unwind, skip].compact.join(' ')
|
301
407
|
end
|
302
|
-
elsif @kind.to_sym == :update
|
303
|
-
return_statement = "return after " + ( @aliases.empty? ? "$this" : @aliases.first.to_s)
|
304
|
-
[ @kind, @database, misc, where_s, return_statement ].compact.join(' ')
|
305
|
-
elsif destination == :rest
|
306
|
-
[@kind, projection_s, from, let_s, where_s, subquery, misc, order_s, group_by, unwind, skip].compact.join(' ')
|
307
|
-
else
|
308
|
-
[@kind, projection_s, from, let_s, where_s, subquery, misc, order_s, group_by, limit, unwind, skip].compact.join(' ')
|
309
408
|
end
|
310
|
-
|
311
|
-
alias :to_s :compose
|
409
|
+
alias :to_s :compose
|
312
410
|
|
313
|
-
=begin
|
314
|
-
from can either be a Databaseclass to operate on or a Subquery providing data to query further
|
315
|
-
=end
|
316
411
|
|
412
|
+
def to_or
|
413
|
+
compose.to_or
|
414
|
+
end
|
317
415
|
|
318
|
-
|
319
|
-
|
320
|
-
|
416
|
+
def target arg = nil
|
417
|
+
if arg.present?
|
418
|
+
@q[:database] = arg
|
419
|
+
self # return query-object
|
420
|
+
elsif @q[:database].present?
|
421
|
+
the_argument = @q[:database]
|
422
|
+
case @q[:database]
|
321
423
|
when ActiveOrient::Model # a single record
|
322
|
-
|
323
|
-
when
|
324
|
-
' ( '+
|
424
|
+
the_argument.rrid
|
425
|
+
when self.class # result of a query
|
426
|
+
' ( '+ the_argument.compose + ' ) '
|
325
427
|
when Class
|
326
|
-
|
428
|
+
the_argument.ref_name
|
327
429
|
else
|
328
|
-
if
|
329
|
-
|
430
|
+
if the_argument.to_s.rid? # a string with "#ab:cd"
|
431
|
+
the_argument
|
330
432
|
else # a database-class-name
|
331
|
-
|
433
|
+
the_argument.to_s
|
332
434
|
end
|
333
435
|
end
|
334
|
-
|
335
|
-
|
336
|
-
|
436
|
+
else
|
437
|
+
raise "cannot complete until a target is specified"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
=begin
|
442
|
+
from can either be a Databaseclass to operate on or a Subquery providing data to query further
|
443
|
+
=end
|
444
|
+
def from arg = nil
|
445
|
+
if arg.present?
|
446
|
+
@q[:database] = arg
|
447
|
+
self # return query-object
|
448
|
+
elsif @q[:database].present? # read from
|
449
|
+
"from #{ target }"
|
450
|
+
end
|
337
451
|
end
|
338
|
-
|
339
|
-
|
452
|
+
|
453
|
+
|
454
|
+
def order value = nil
|
455
|
+
if value.present?
|
456
|
+
@q[:order] << value
|
457
|
+
self
|
458
|
+
elsif @q[:order].present?
|
459
|
+
|
460
|
+
"order by " << @q[:order].compact.flatten.map do |o|
|
461
|
+
case o
|
462
|
+
when String, Symbol, Array
|
463
|
+
o.to_s
|
464
|
+
else
|
465
|
+
o.map{|x,y| "#{x} #{y}"}.join(" ")
|
466
|
+
end # case
|
467
|
+
end.join(', ')
|
468
|
+
else
|
469
|
+
''
|
470
|
+
end # unless
|
471
|
+
end # def
|
472
|
+
|
340
473
|
|
341
474
|
def database_class # :nodoc:
|
342
|
-
|
343
|
-
@database
|
344
|
-
elsif @from.is_a? OrientQuery
|
345
|
-
@from.database_class
|
346
|
-
else
|
347
|
-
nil
|
348
|
-
end
|
475
|
+
@q[:database]
|
349
476
|
end
|
350
477
|
|
351
478
|
def database_class= arg # :nodoc:
|
352
|
-
@database = arg
|
353
|
-
if @from.is_a? OrientQuery
|
354
|
-
@from.database_class= arg
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
|
-
def where_s # :nodoc:
|
359
|
-
compose_where @where
|
479
|
+
@q[:database] = arg
|
360
480
|
end
|
361
481
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
when String
|
367
|
-
s
|
368
|
-
when Array
|
369
|
-
s.join(', ')
|
370
|
-
# when Hash ### is not recognized in jruby
|
371
|
-
else
|
372
|
-
s.map{|x,y| "$#{x} = (#{y})"}.join(', ')
|
373
|
-
end
|
374
|
-
end.join(', ')
|
375
|
-
end
|
376
|
-
end
|
482
|
+
def distinct d
|
483
|
+
@q[:projection] << "distinct " + generate_sql_list( d ){ ' as ' }
|
484
|
+
self
|
485
|
+
end
|
377
486
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
487
|
+
class << self
|
488
|
+
def mk_simple_setter *m
|
489
|
+
m.each do |def_m|
|
490
|
+
define_method( def_m ) do | value=nil |
|
491
|
+
if value.present?
|
492
|
+
@q[def_m] = value
|
493
|
+
self
|
494
|
+
elsif @q[def_m].present?
|
495
|
+
"#{def_m.to_s} #{generate_sql_list(@q[def_m]){' ,'}}"
|
496
|
+
end
|
497
|
+
end
|
385
498
|
end
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
499
|
+
end
|
500
|
+
def mk_std_setter *m
|
501
|
+
m.each do |def_m|
|
502
|
+
define_method( def_m ) do | value = nil |
|
503
|
+
if value.present?
|
504
|
+
@q[def_m] << case value
|
505
|
+
when String
|
506
|
+
value
|
507
|
+
when ::Hash
|
508
|
+
value.map{|k,v| "#{k} = #{v.to_or}"}.join(", ")
|
509
|
+
else
|
510
|
+
raise "Only String or Hash allowed in #{def_m} statement"
|
511
|
+
end
|
512
|
+
self
|
513
|
+
elsif @q[def_m].present?
|
514
|
+
"#{def_m.to_s} #{@q[def_m].join(',')}"
|
515
|
+
end # branch
|
516
|
+
end # def_method
|
517
|
+
end # each
|
518
|
+
end # def
|
519
|
+
end # class << self
|
520
|
+
mk_simple_setter :limit, :skip, :unwind
|
521
|
+
mk_std_setter :set, :remove
|
522
|
+
|
523
|
+
def let value = nil
|
524
|
+
if value.present?
|
525
|
+
@q[:let] << value
|
526
|
+
self
|
527
|
+
elsif @q[:let].present?
|
528
|
+
"let " << @q[:let].map do |s|
|
529
|
+
case s
|
530
|
+
when String
|
531
|
+
s
|
532
|
+
when ::Hash
|
533
|
+
s.map do |x,y|
|
534
|
+
# if the symbol: value notation of Hash is used, add "$" to the key
|
535
|
+
x = "$#{x.to_s}" unless x.is_a?(String) && x[0] == "$"
|
536
|
+
"#{x} = #{ case y
|
537
|
+
when self.class
|
538
|
+
"(#{y.compose})"
|
539
|
+
else
|
540
|
+
y.to_orient
|
541
|
+
end }"
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end.join(', ')
|
545
|
+
end
|
546
|
+
end
|
547
|
+
#
|
548
|
+
def projection value= nil # :nodoc:
|
549
|
+
if value.present?
|
550
|
+
@q[:projection] << value
|
551
|
+
self
|
552
|
+
elsif @q[:projection].present?
|
553
|
+
@q[:projection].compact.map do | s |
|
554
|
+
case s
|
555
|
+
when ::Array
|
556
|
+
s.join(', ')
|
557
|
+
when String, Symbol
|
558
|
+
s.to_s
|
559
|
+
when ::Hash
|
560
|
+
s.map{ |x,y| "#{x} as #{y}"}.join( ', ')
|
561
|
+
end
|
562
|
+
end.join( ', ' )
|
563
|
+
end
|
564
|
+
end
|
402
565
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
566
|
+
|
567
|
+
|
568
|
+
def group value = nil
|
569
|
+
if value.present?
|
570
|
+
@q[:group] << value
|
571
|
+
self
|
572
|
+
elsif @q[:group].present?
|
573
|
+
"group by #{@q[:group].join(', ')}"
|
574
|
+
end
|
407
575
|
end
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
576
|
+
|
577
|
+
alias order_by order
|
578
|
+
alias group_by group
|
579
|
+
|
580
|
+
def get_limit # :nodoc:
|
581
|
+
@q[:limit].nil? ? -1 : @q[:limit].to_i
|
412
582
|
end
|
413
583
|
|
414
584
|
def expand item
|
415
|
-
@projection =[ " expand ( #{item.to_s} )" ]
|
416
|
-
|
585
|
+
@q[:projection] =[ " expand ( #{item.to_s} )" ]
|
586
|
+
self
|
417
587
|
end
|
418
588
|
|
589
|
+
# connects by adding {in_or_out}('edgeClass')
|
590
|
+
def connect_with in_or_out, via: nil
|
591
|
+
argument = " #{in_or_out}(#{via.to_or if via.present?})"
|
592
|
+
end
|
593
|
+
# adds a connection
|
594
|
+
# in_or_out: :out ---> outE('edgeClass').in[where-condition]
|
595
|
+
# :in ---> inE('edgeClass').out[where-condition]
|
419
596
|
|
420
|
-
def nodes in_or_out = :out, via:
|
597
|
+
def nodes in_or_out = :out, via: nil, where: nil, expand: true
|
421
598
|
condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
|
422
|
-
start = in_or_out
|
423
|
-
|
424
|
-
|
599
|
+
start = if in_or_out == :in
|
600
|
+
'inE'
|
601
|
+
elsif in_or_out == :out
|
602
|
+
'outE'
|
603
|
+
else
|
604
|
+
"both"
|
605
|
+
end
|
606
|
+
the_end = if in_or_out == :in
|
607
|
+
'.out'
|
608
|
+
elsif in_or_out == :out
|
609
|
+
'.in'
|
610
|
+
else
|
611
|
+
''
|
612
|
+
end
|
613
|
+
argument = " #{start}(#{[via].flatten.map(&:to_or).join(',') if via.present?})#{the_end}#{condition} "
|
425
614
|
|
426
615
|
if expand.present?
|
427
616
|
send :expand, argument
|
428
617
|
else
|
429
|
-
|
618
|
+
@q[:projection] << argument
|
430
619
|
end
|
431
|
-
|
620
|
+
self
|
432
621
|
end
|
433
622
|
|
434
|
-
def group_by g = nil
|
435
|
-
@group = "group by #{g.to_s}" if g.present?
|
436
|
-
# only a string is allowed
|
437
|
-
@group
|
438
|
-
end
|
439
|
-
|
440
|
-
def unwind u = nil
|
441
|
-
@unwind = "unwind #{u.to_s}" if u.present?
|
442
|
-
# only a string is allowed
|
443
|
-
@unwind
|
444
|
-
end
|
445
623
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
624
|
+
# returns nil if the query was not sucessfully executed
|
625
|
+
def execute(reduce: false)
|
626
|
+
#puts "Compose: #{compose}"
|
627
|
+
result = V.orientdb.execute{ compose }
|
628
|
+
return nil unless result.is_a?(::Array)
|
629
|
+
result = result.map{|x| yield x } if block_given?
|
630
|
+
return result.first if reduce && result.size == 1
|
631
|
+
## standard case: return Array
|
632
|
+
OrientSupport::Array.new( work_on: resolve_target, work_with: result.orient_flatten)
|
633
|
+
end
|
634
|
+
:protected
|
635
|
+
def resolve_target
|
636
|
+
if @q[:database].is_a? OrientSupport::OrientQuery
|
637
|
+
@q[:database].resolve_target
|
638
|
+
else
|
639
|
+
@q[:database]
|
640
|
+
end
|
641
|
+
end
|
450
642
|
|
451
|
-
|
452
|
-
|
453
|
-
# the [@order] is nessesary to enable query.order= "..." oder query.order= { a: :b }
|
454
|
-
"order by " << [@order].flatten.map do |o|
|
455
|
-
case o
|
456
|
-
when String, Symbol, Array
|
457
|
-
o.to_s
|
458
|
-
else
|
459
|
-
o.map{|x,y| "#{x} #{y}"}.join(" ")
|
460
|
-
end # case
|
461
|
-
end.join(', ')
|
462
|
-
else
|
463
|
-
''
|
464
|
-
end # unless
|
465
|
-
end # def
|
466
|
-
end # class
|
643
|
+
# end
|
644
|
+
end # class
|
467
645
|
|
468
646
|
|
469
647
|
end # module
|