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.
@@ -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