active-orient 0.79 → 0.80

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- def compose_where *arg , &b
20
- arg = arg.flatten
21
- return "" if arg.blank? || arg.size == 1 && arg.first.blank?
22
- "where " + generate_sql_list( arg , &b)
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 = {}, &b
40
+ def generate_sql_list attributes = {}, &b
33
41
  fill = block_given? ? yield : 'and'
34
- a= case attributes
35
- when ::Hash
36
- attributes.map do |key, value|
37
- case value
38
- when ActiveOrient::Model
39
- "#{key} = #{value.rrid}"
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
- attributes.map{|y| generate_sql_list y, &b }.join( " #{fill} " )
56
- when String
57
- attributes
58
- end
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
- attr_accessor :as
65
- def initialize edge: nil, direction: :both, as: nil, count: 1
66
- @edge = edge
67
- @direction = direction # may be :both, :in, :out
68
- @as = as
69
- @count = count
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
- def direction
78
- fillup = @edge.present? ? @edge : ''
79
- case @direction
80
- when :both
81
- " -#{fillup}- "
82
- when :in
83
- " <-#{fillup}- "
84
- when :out
85
- " -#{fillup}-> "
86
- end
87
-
88
- end
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
- def compose
91
- ministatement = @as.present? ? "{ as: #{@as} } " : ""
92
- (1 .. @count).map{|x| direction }.join("{}") << ministatement
158
+ end
93
159
 
94
- end
95
-
96
- end
160
+ def count c=nil
161
+ if c
162
+ @q[:count] = c
163
+ else
164
+ @q[:count]
165
+ end
166
+ end
97
167
 
98
- class MatchStatement
99
- include Support
100
- attr_accessor :as
101
- attr_accessor :where
102
- def initialize match_class=nil, **args
103
- @misc = []
104
- @where = []
105
- @while = []
106
- @maxdepth = 0
107
- @as = nil
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
- def match_alias
135
- "as: #{@as }"
136
- end
137
- def where_s
138
- compose_where( @where ).gsub( /where/, 'where:(' )<< ")" unless @where.blank?
139
- end
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
- def method_missing method, *arg, &b
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
- '{'+ [ "class: #{@match_class}",
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
- class OrientQuery
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
- attr_accessor :where
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
- def misc # :nodoc:
231
- @misc.join(' ') unless @misc.empty?
232
- # self.to_s # return compiled result
233
- end
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
- =begin
242
- (only if kind == :match): connect
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
- Add a connection to the match-query
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
- The connection method adds a connection to the statement-stack.
323
+ ######################## OrientQuery ###################################
250
324
 
251
- Parameters:
252
- direction: :in, :out, :both
253
- edge_class: to restrict the Query on a certain Edge-Class
254
- count: To repeat the connection
255
- as: Includes a micro-statement to finalize the Match-Query
256
- as: defines a output-variablet, which is used later in the return-statement
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
- def connect direction, edge_class: nil, count: 1, as: nil
263
- direction= :both unless [ :in, :out].include? direction
264
- match_statements << m = OrientSupport::MatchConnection.new( direction: direction, count: count, as: as)
265
- m
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
- (only if kind == :match): statement
270
-
271
- A Match Query consists of a simple start-statement
272
- ( classname and where-condition ), a connection followd by other Statement-connection-pairs.
273
- It performs a sub-query starting at the given entry-point.
274
-
275
- Statement adds a statement to the statement-stack.
276
- Statement returns the created OrientSupport::MatchStatement-record for further modifications.
277
- It is compiled by calling »compose«.
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
- OrientSupport::OrientQuery collects any "as"-directive for inclusion in the return-statement
367
+ def misc # :nodoc:
368
+ @q[:misc].join(' ') unless @q[:misc].empty?
369
+ end
280
370
 
281
- Parameter (all optional)
282
- Class: classname, :where: {}, while: {}, as: string, maxdepth: >0 ,
371
+ def subquery # :nodoc:
372
+ nil
373
+ end
283
374
 
284
- =end
285
- def statement match_class= nil, **args
286
- match_statements << s = OrientSupport::MatchStatement.new( mattch_class, args )
287
- s
288
- end
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
- def compose(destination: :batch)
296
- if @kind == :match
297
- unless @match_statements.empty?
298
- match_query = @kind.to_s.upcase + " "+ @match_statements[0].compose_simple
299
- match_query << @match_statements[1..-1].map( &:compose ).join
300
- match_query << " RETURN "<< (@match_statements.map( &:as ).compact | @aliases).join(', ')
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
- end
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
- def from arg = nil
319
- if arg.present?
320
- @database = case arg
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
- arg.rrid
323
- when OrientQuery # result of a query
324
- ' ( '+ arg.to_s + ' ) '
424
+ the_argument.rrid
425
+ when self.class # result of a query
426
+ ' ( '+ the_argument.compose + ' ) '
325
427
  when Class
326
- arg.ref_name
428
+ the_argument.ref_name
327
429
  else
328
- if arg.to_s.rid? # a string with "#ab:cd"
329
- arg
430
+ if the_argument.to_s.rid? # a string with "#ab:cd"
431
+ the_argument
330
432
  else # a database-class-name
331
- arg.to_s
433
+ the_argument.to_s
332
434
  end
333
435
  end
334
- compose # return the complete query
335
- else # read from
336
- "from #{@database}" unless @database.nil?
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
- end
339
- alias :from= :from
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
- if @database.present?
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 if @database.present?
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
- def let_s # :nodoc:
363
- unless @let.empty?
364
- "let " << @let.map do |s|
365
- case s
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
- def distinct d
379
- @projection << case d
380
- when String, Symbol
381
- "distinct #{d.to_s} "
382
- else
383
- dd= d.to_a.flatten
384
- "distinct #{dd.first.to_s} as #{dd.last}"
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
- compose
387
- end
388
- alias :distinct= :distinct
389
-
390
- def projection_s # :nodoc:
391
- @projection.map do | s |
392
- case s
393
- when Array
394
- s.join(', ')
395
- when String, Symbol
396
- s.to_s
397
- else
398
- s.map{ |x,y| "#{x} as #{y}"}.join( ', ')
399
- end
400
- end.join( ', ' )
401
- end
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
- def limit l=nil
404
- @limit = "limit #{l.to_s}" if l.present?
405
- # only a string is allowed
406
- @limit
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
- alias :limit= :limit
409
-
410
- def get_limit # :nodoc:
411
- @limit.nil? ? -1 : @limit.split(' ').last.to_i
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
- compose
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: nil, where: nil, expand: true
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
- the_end = in_or_out == :in ? :out : :in
424
- argument = " #{start}E(#{via.to_or if via.present?}).#{the_end}#{condition} "
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
- @projection << argument
618
+ @q[:projection] << argument
430
619
  end
431
- compose
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
- def skip n = nil
447
- @skip = n if n.present?
448
- "skip #{@skip}" if @skip.present?
449
- end
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
- def order_s # :nodoc:
452
- unless @order.empty?
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