active-orient 0.4 → 0.80

Sign up to get free protection for your applications and to get access to all the features.
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