active-orient 0.4 → 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.
Files changed (61) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.graphs.txt.swp +0 -0
  4. data/Gemfile +9 -5
  5. data/Guardfile +12 -4
  6. data/README.md +70 -281
  7. data/VERSION +1 -1
  8. data/active-orient.gemspec +9 -7
  9. data/bin/active-orient-0.6.gem +0 -0
  10. data/bin/active-orient-console +97 -0
  11. data/changelog.md +60 -0
  12. data/config/boot.rb +70 -17
  13. data/config/config.yml +10 -0
  14. data/config/connect.yml +11 -6
  15. data/examples/books.rb +154 -65
  16. data/examples/streets.rb +89 -85
  17. data/graphs.txt +70 -0
  18. data/lib/active-orient.rb +78 -6
  19. data/lib/base.rb +266 -168
  20. data/lib/base_properties.rb +76 -65
  21. data/lib/class_utils.rb +187 -0
  22. data/lib/database_utils.rb +99 -0
  23. data/lib/init.rb +80 -0
  24. data/lib/java-api.rb +442 -0
  25. data/lib/jdbc.rb +211 -0
  26. data/lib/model/custom.rb +29 -0
  27. data/lib/model/e.rb +6 -0
  28. data/lib/model/edge.rb +114 -0
  29. data/lib/model/model.rb +134 -0
  30. data/lib/model/the_class.rb +657 -0
  31. data/lib/model/the_record.rb +313 -0
  32. data/lib/model/vertex.rb +371 -0
  33. data/lib/orientdb_private.rb +48 -0
  34. data/lib/other.rb +423 -0
  35. data/lib/railtie.rb +68 -0
  36. data/lib/rest/change.rb +150 -0
  37. data/lib/rest/create.rb +287 -0
  38. data/lib/rest/delete.rb +150 -0
  39. data/lib/rest/operations.rb +222 -0
  40. data/lib/rest/read.rb +189 -0
  41. data/lib/rest/rest.rb +120 -0
  42. data/lib/rest_disabled.rb +24 -0
  43. data/lib/support/conversions.rb +42 -0
  44. data/lib/support/default_formatter.rb +7 -0
  45. data/lib/support/errors.rb +41 -0
  46. data/lib/support/logging.rb +38 -0
  47. data/lib/support/orient.rb +305 -0
  48. data/lib/support/orientquery.rb +647 -0
  49. data/lib/support/query.rb +92 -0
  50. data/rails.md +154 -0
  51. data/rails/activeorient.rb +32 -0
  52. data/rails/config.yml +10 -0
  53. data/rails/connect.yml +17 -0
  54. metadata +89 -30
  55. data/lib/model.rb +0 -461
  56. data/lib/orient.rb +0 -98
  57. data/lib/query.rb +0 -88
  58. data/lib/rest.rb +0 -1036
  59. data/lib/support.rb +0 -347
  60. data/test.rb +0 -4
  61. data/usecase.md +0 -91
@@ -0,0 +1,647 @@
1
+ require 'active_support/inflector'
2
+
3
+ module OrientSupport
4
+ module Support
5
+
6
+ =begin
7
+ supports
8
+ where: 'string'
9
+ where: { property: 'value', property: value, ... }
10
+ where: ['string, { property: value, ... }, ... ]
11
+
12
+ Used by update and select
13
+
14
+ _Usecase:_
15
+ ORD.compose_where 'z=34', {u:6}
16
+ => "where z=34 and u = 6"
17
+ =end
18
+
19
+ #
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
27
+ end
28
+
29
+ =begin
30
+ designs a list of "Key = Value" pairs combined by "and" or the binding provided by the block
31
+ ORD.generate_sql_list where: 25 , upper: '65'
32
+ => "where = 25 and upper = '65'"
33
+ ORD.generate_sql_list( con_id: 25 , symbol: :G) { ',' }
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
+
39
+ =end
40
+ def generate_sql_list attributes = {}, &b
41
+ fill = block_given? ? yield : 'and'
42
+ case attributes
43
+ when ::Hash
44
+ attributes.map do |key, value|
45
+ case value
46
+ when nil
47
+ "#{key} = NULL"
48
+ when ::Array
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
67
+ end
68
+
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
112
+ class MatchConnection
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
128
+ end
129
+
130
+ def direction= dir
131
+ @q[:direction] = dir
132
+ end
133
+
134
+
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
157
+
158
+ end
159
+
160
+ def count c=nil
161
+ if c
162
+ @q[:count] = c
163
+ else
164
+ @q[:count]
165
+ end
166
+ end
167
+
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
178
+
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=="{}"
185
+
186
+ (1 .. count).map{|x| direction }.join("") + ministatement
187
+
188
+ end
189
+ alias :to_s :compose
190
+
191
+ end # class
192
+
193
+
194
+ ######################## MatchStatement ################################
195
+
196
+ MatchSAttributes = Struct.new(:match_class, :as, :where )
197
+ class MatchStatement
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 }
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 ])
205
+
206
+ @query_stack = [ self ]
207
+ end
208
+
209
+ def match_alias
210
+ "as: #{@q[:as]}"
211
+ end
212
+
213
+
214
+
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
220
+
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
230
+
231
+
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
316
+
317
+
318
+ end
319
+
320
+ end # class
321
+
322
+
323
+ ######################## OrientQuery ###################################
324
+
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
331
+
332
+
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'
350
+ end
351
+
352
+
353
+ =begin
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
366
+
367
+ def misc # :nodoc:
368
+ @q[:misc].join(' ') unless @q[:misc].empty?
369
+ end
370
+
371
+ def subquery # :nodoc:
372
+ nil
373
+ end
374
+
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
384
+ =begin
385
+ Output the compiled query
386
+ Parameter: destination (rest, batch )
387
+ If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
388
+ =end
389
+
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(' ')
407
+ end
408
+ end
409
+ alias :to_s :compose
410
+
411
+
412
+ def to_or
413
+ compose.to_or
414
+ end
415
+
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]
423
+ when ActiveOrient::Model # a single record
424
+ the_argument.rrid
425
+ when self.class # result of a query
426
+ ' ( '+ the_argument.compose + ' ) '
427
+ when Class
428
+ the_argument.ref_name
429
+ else
430
+ if the_argument.to_s.rid? # a string with "#ab:cd"
431
+ the_argument
432
+ else # a database-class-name
433
+ the_argument.to_s
434
+ end
435
+ end
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
451
+ end
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
+
473
+
474
+ def database_class # :nodoc:
475
+ @q[:database]
476
+ end
477
+
478
+ def database_class= arg # :nodoc:
479
+ @q[:database] = arg
480
+ end
481
+
482
+ def distinct d
483
+ @q[:projection] << "distinct " + generate_sql_list( d ){ ' as ' }
484
+ self
485
+ end
486
+
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
498
+ 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
565
+
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
575
+ end
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
582
+ end
583
+
584
+ def expand item
585
+ @q[:projection] =[ " expand ( #{item.to_s} )" ]
586
+ self
587
+ end
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]
596
+
597
+ def nodes in_or_out = :out, via: nil, where: nil, expand: true
598
+ condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
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} "
614
+
615
+ if expand.present?
616
+ send :expand, argument
617
+ else
618
+ @q[:projection] << argument
619
+ end
620
+ self
621
+ end
622
+
623
+
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
642
+
643
+ # end
644
+ end # class
645
+
646
+
647
+ end # module