arcadedb 0.3.1 → 0.4

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.
data/lib/match.rb ADDED
@@ -0,0 +1,216 @@
1
+ #require 'active_support/inflector'
2
+
3
+ module Arcade
4
+ ######################## MatchConnection ###############################
5
+
6
+ MatchAttributes = Struct.new(:edge, :direction, :as, :count, :where, :while, :max_depth , :depth_alias, :path_alias, :optional )
7
+
8
+ # where and while can be composed incremental
9
+ # direction, as, connect and edge cannot be changed after initialisation
10
+ class MatchConnection
11
+ include Support::Sql
12
+
13
+ def initialize edge= nil, direction: :both, as: nil, count: 1, **args
14
+
15
+ the_edge = edge.is_a?( Class ) ? edge.ref_name : edge.to_s unless edge.nil? || edge == E
16
+ @q = MatchAttributes.new the_edge , # class
17
+ direction, # may be :both, :in, :out
18
+ as, # a string
19
+ count, # a number
20
+ args[:where],
21
+ args[:while],
22
+ args[:max_depth],
23
+ args[:depth_alias], # not implemented
24
+ args[:path_alias], # not implemented
25
+ args[:optional] # not implemented
26
+ end
27
+
28
+ def direction= dir
29
+ @q[:direction] = dir
30
+ end
31
+
32
+
33
+ def direction
34
+ fillup = @q[:edge].present? ? @q[:edge] : ''
35
+ case @q[:direction]
36
+ when :both
37
+ ".both(#{fillup})"
38
+ when :in
39
+ ".in(#{fillup})"
40
+ when :out
41
+ ".out(#{fillup})"
42
+ when :both_vertex, :bothV
43
+ ".bothV()"
44
+ when :out_vertex, :outV
45
+ ".outV()"
46
+ when :in_vertex, :inV
47
+ ".inV()"
48
+ when :both_edge, :bothE
49
+ ".bothE(#{fillup})"
50
+ when :out_edge, :outE
51
+ ".outE(#{fillup})"
52
+ when :in_edge, :outE
53
+ ".inE(#{fillup})"
54
+ end
55
+
56
+ end
57
+
58
+ def count c=nil
59
+ if c
60
+ @q[:count] = c
61
+ else
62
+ @q[:count]
63
+ end
64
+ end
65
+
66
+ def max_depth d=nil
67
+ if d.nil?
68
+ @q[:max_depth].present? ? "maxDepth: #{@q[:max_depth] }" : nil
69
+ else
70
+ @q[:max_depth] = d
71
+ end
72
+ end
73
+ def edge
74
+ @q[:edge]
75
+ end
76
+
77
+ def compose
78
+ where_statement =( where.nil? || where.size <5 ) ? nil : "where: ( #{ generate_sql_list( @q[:where] ) })"
79
+ while_statement =( while_s.nil? || while_s.size <5) ? nil : "while: ( #{ generate_sql_list( @q[:while] )})"
80
+
81
+ ministatement = "{"+ [ as, where_statement, while_statement, max_depth].compact.join(', ') + "}"
82
+ ministatement = "" if ministatement=="{}"
83
+
84
+ (1 .. count).map{|x| direction }.join("") + ministatement
85
+
86
+ end
87
+ alias :to_s :compose
88
+
89
+ end # class
90
+
91
+
92
+ ######################## MatchStatement ################################
93
+
94
+ MatchSAttributes = Struct.new(:match_class, :as, :where )
95
+ class MatchStatement
96
+ include Support
97
+ def initialize match_class, as: 0, **args
98
+ reduce_class = ->(c){ c.is_a?(Class) ? c.ref_name : c.to_s }
99
+
100
+ @q = MatchSAttributes.new( reduce_class[match_class], # class
101
+ as.respond_to?(:zero?) && as.zero? ? reduce_class[match_class].pluralize : as ,
102
+ args[ :where ])
103
+
104
+ @query_stack = [ self ]
105
+ end
106
+
107
+ def match_alias
108
+ "as: #{@q[:as]}"
109
+ end
110
+
111
+
112
+
113
+ # used for the first compose-statement of a compose-query
114
+ def compose_simple
115
+ where_statement = where.is_a?(String) && where.size <3 ? nil : "where: ( #{ generate_sql_list( @q[:where] ) })"
116
+ '{'+ [ "class: #{@q[:match_class]}", as , where_statement].compact.join(', ') + '}'
117
+ end
118
+
119
+
120
+ def << connection
121
+ @query_stack << connection
122
+ self # return MatchStatement
123
+ end
124
+ #
125
+ def compile &b
126
+ "match " + @query_stack.map( &:to_s ).join + return_statement( &b )
127
+ end
128
+
129
+
130
+ # executes the standard-case.
131
+ # returns
132
+ # * as: :hash : an array of hashes
133
+ # * as: :array : an array of hash-values
134
+ # * as :flatten: a simple array of hash-values
135
+ #
136
+ # The optional block is used to customize the output.
137
+ # All previously defiend »as«-Statements are provided though the control variable.
138
+ #
139
+ # Background
140
+ # A match query "Match {class aaa, as: 'aa'} return aa "
141
+ #
142
+ # returns [ aa: { result of the query, a Vertex or a value-item }, aa: {}...}, ...] ]
143
+ # (The standard case)
144
+ #
145
+ # A match query "Match {class aaa, as: 'aa'} return aa.name "
146
+ # returns [ aa.name: { name }, aa.name: { name }., ...] ]
147
+ #
148
+ # Now, execute( as: :flatten){ "aa.name" } returns
149
+ # [name1, name2 ,. ...]
150
+ #
151
+ #
152
+ # Return statements (examples from https://orientdb.org/docs/3.0.x/sql/SQL-Match.html)
153
+ # "person.name as name, friendship.since as since, friend.name as friend"
154
+ #
155
+ # " person.name + \" is a friend of \" + friend.name as friends"
156
+ #
157
+ # "$matches"
158
+ # "$elements"
159
+ # "$paths"
160
+ # "$pathElements"
161
+ #
162
+ #
163
+ def execute as: :hash, &b
164
+ r = V.db.execute{ compile &b }
165
+ case as
166
+ when :hash
167
+ r
168
+ when :array
169
+ r.map{|y| y.values}
170
+ when :flatten
171
+ r.map{|y| y.values}.orient_flatten
172
+ else
173
+ raise ArgumentError, "Specify parameter «as:» with :hash, :array, :flatten"
174
+ end
175
+ end
176
+ # def compose
177
+ #
178
+ # '{'+ [ "class: #{@q[:match_class]}",
179
+ # "as: #{@as}" , where, while_s,
180
+ # @maxdepth >0 ? "maxdepth: #{maxdepth}": nil ].compact.join(', ')+'}'
181
+ # end
182
+
183
+ alias :to_s :compose_simple
184
+
185
+
186
+ ## return_statement
187
+ #
188
+ # summarizes defined as-statements ready to be included as last parameter
189
+ # in the match-statement-stack
190
+ #
191
+ # They can be modified through a block.
192
+ #
193
+ # i.e
194
+ #
195
+ # t= TestQuery.match( where: {a: 9, b: 's'}, as: nil ) << E.connect("<-", as: :test)
196
+ # t.return_statement{|y| "#{y.last}.name"}
197
+ #
198
+ # =>> " return test.name"
199
+ #
200
+ #return_statement is always called through compile
201
+ #
202
+ # t.compile{|y| "#{y.last}.name"}
203
+
204
+ private
205
+ def return_statement
206
+ resolve_as = ->{ @query_stack.map{|s| s.as.split(':').last unless s.as.nil? }.compact }
207
+ " return " + statement = if block_given?
208
+ a= yield resolve_as[]
209
+ a.is_a?(Array) ? a.join(', ') : a
210
+ else
211
+ resolve_as[].join(', ')
212
+ end
213
+ end
214
+ end # class
215
+
216
+ end # module
@@ -0,0 +1,7 @@
1
+ module Arcade
2
+ class Basicdocument < Document
3
+ # schema schema.strict # -- throws an error if specified keys are missing
4
+ attribute :values?, Types::Hash
5
+
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Arcade
2
+ class Basicedge < Edge
3
+ # schema schema.strict # -- throws an error if specified keys are missing
4
+ attribute :values?, Types::Hash
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Arcade
2
+ class Basicvertex < Vertex
3
+ # schema schema.strict # -- throws an error if specified keys are missing
4
+ attribute :values?, Types::Hash
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Arcade
2
+ class Document < Base
3
+ # schema schema.strict # -- throws an error if specified keys are missing
4
+
5
+ ## has to be overloaded
6
+ # def accepted_methods
7
+ #
8
+ # end
9
+ end
10
+ end
data/lib/model/edge.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Arcade
2
+ class Edge < Base
3
+ # schema schema.strict # -- throws an error if specified keys are missing
4
+ attribute :in, Types::Rid
5
+ attribute :out, Types::Rid
6
+
7
+ def accepted_methods
8
+ super + [ :vertices, :in, :out, :inV, :outV ]
9
+ end
10
+ #
11
+ # Add Contrains to the edge
12
+ # CREATE INDEX Watched_out_in ON <edge type« (`@out`, `@in`) UNIQUE
13
+ #
14
+ def self.create from:, to:, **attr
15
+ db.create_edge database_name, from: from, to: to, **attr
16
+ end
17
+
18
+ def delete
19
+ db.execute{ "delete edge #{ rid }" }.select_result
20
+ end
21
+ ## gets the adjacent Vertex
22
+ def inV
23
+ query( projection: "inV()").query.select_result
24
+ end
25
+ def outV
26
+ query( projection: "outV()").query.select_result
27
+ end
28
+ def vertices in_or_out = nil
29
+ case in_or_out
30
+ when :in
31
+ inV
32
+ when :out
33
+ outV
34
+ else
35
+ [inV, outV]
36
+ end
37
+ end
38
+
39
+ alias bothV vertices
40
+
41
+
42
+ def to_human
43
+ "<#{self.class.to_s.snake_case}[#{rid}] :.: #{ out }->#{invariant_attributes}->#{ attributes[:in] }>"
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,238 @@
1
+ module Arcade
2
+ class Vertex < Base
3
+
4
+ # include Arcade::Support::Sql
5
+ include Arcade::Support::Model # instance methods
6
+ extend Arcade::Support::Model # class methods
7
+
8
+ attribute :in?, Types::Nominal::Any
9
+ attribute :out?, Types::Nominal::Any
10
+ # #
11
+ def accepted_methods
12
+ super + [ :in, :out, :both, :edges, :inE, :outE, :bothE, :assign]
13
+ end
14
+ ## ----------------------------------------- Class Methods------------------------------------ ##
15
+ # #
16
+ =begin
17
+ Vertex.delete fires a "delete vertex" command to the database.
18
+
19
+ To remove all records use »all: true« as argument
20
+
21
+ To remove a specific rid, use rid: "#nn:mmm" as argument
22
+
23
+ "where" parameter is optional
24
+
25
+ ExtraNode.delete where: { item: 67 } == ExtraNode.delete item: 67
26
+
27
+ =end
28
+ def self.delete where: {} , **args
29
+ if args[:all] == true
30
+ where = {}
31
+ elsif args[:rid].present?
32
+ return db.execute { "delete vertex #{args[:rid]}" }.first["count"]
33
+ else
34
+ where.merge!(args) if where.is_a?(Hash)
35
+ return 0 if where.empty?
36
+ end
37
+ # query returns [{count => n }]
38
+ db.execute { "delete vertex #{database_name} #{compose_where(where)}" } &.first[:count] rescue 0
39
+ end
40
+
41
+ =begin
42
+ Creates a Vertex-Instance.
43
+ Similar to `Vertex#insert`.
44
+
45
+ Difference is the presence of a `created` property, a timestamp set to the time and date of creation.
46
+ =end
47
+
48
+ def self.create timestamp: true, **args
49
+ #t= timestamp ? ", created = Date(#{DateTime.now.to_i}) " : ""
50
+ t= timestamp ? ", created = sysdate() " : ""
51
+ db.execute { "create VERTEX #{database_name} set #{args.map{|x,y| [x,y.to_or].join("=")}.join(', ')+t}" } &.first.allocate_model(false)
52
+ end
53
+
54
+
55
+ ## get adjacent nodes based on a query on the actual model
56
+
57
+
58
+ def self.nodes in_or_out = :both, via: nil , **args
59
+
60
+ s = Query.new from: self
61
+ s.nodes in_or_out, via: via, **args
62
+ s.query &.select_result
63
+ end
64
+
65
+
66
+ # #
67
+ ## --------------------------------- Instance Methods --------------------------------- ##
68
+ #
69
+ # We need expand as fallback if a vertex, which is stored as link, is automatically loaded
70
+ #
71
+ def expand
72
+ self
73
+ end
74
+ # Supports where: --> Strategie.first nodes where: {size: 10}
75
+ # "select both()[ size = 10 ] from #113:8 "
76
+ def nodes in_or_out=:both, depth= 1, via: nil , execute: true, **args
77
+ s = Query.new from: rid
78
+ s.nodes in_or_out, via: via, **args
79
+ if execute
80
+ s.query &.select_result
81
+ else
82
+ s # just return the query
83
+ end
84
+ end
85
+
86
+ # Supports where: { where condition for edges }
87
+ def edges in_or_out = :both, depth= 1, via: nil , execute: true, **args
88
+ in_or_out = in_or_out.to_s + "E"
89
+ nodes in_or_out, depth, via: via , execute: execute, **args
90
+ end
91
+
92
+
93
+ # get Vertices through in by edges of type via
94
+ def in count=0, via:nil
95
+ if count.zero?
96
+ @bufferedin ||= nodes :in, 1, via: via
97
+ else
98
+ nodes :in, count, via: via # not cached
99
+ end
100
+ end
101
+
102
+ # get Vertices through out by edges of type via
103
+ def out count=0, via:nil
104
+ if count.zero?
105
+ @bufferedout ||= nodes :out, 1, via: via
106
+ else
107
+ nodes :out, count, via: via # not cached
108
+ end
109
+ end
110
+ #
111
+ # get all Vertices connected by edges of type via
112
+ def both count=0, via:nil
113
+ if count.zero?
114
+ @bufferedboth ||= nodes :both, 1, via: via
115
+ else
116
+ nodes :both, count, via: via # not cached
117
+ end
118
+ end
119
+
120
+
121
+ # get via-type-edges through in
122
+ def inE count=1, via:nil
123
+ nodes :inE, count, via: via
124
+ end
125
+ #
126
+ # get via-type-edges through out
127
+ def outE count=1, via:nil
128
+ nodes :outE, count, via: via
129
+ end
130
+
131
+ # get all via-type-edges
132
+ def bothE count=1, via:nil
133
+ nodes :bothE, count, via: via
134
+ end
135
+
136
+
137
+ # Returns a collection of all vertices passed during the traversal
138
+ #
139
+ # Includes the start_vertex (start_at =0 by default)
140
+ #
141
+ # If the vector should not include the start_vertex, call with `start_at: 1` and increase the depth by 1
142
+ #
143
+ # fires a query
144
+ #
145
+ # select from ( traverse outE('}#{via}').in from #{vertex} while $depth < #{depth} )
146
+ # where $depth >= #{start_at}
147
+ #
148
+ # If » execute: false « is specified, the traverse-statement is returned (as Arcade::Query object)
149
+ #
150
+ # Multiple Edges can be specifies in the via-parameter (use Array-notation)
151
+ # e.i.
152
+ # traverse( :in, via: [TG::DateOf, Arcade::HasOrder], depth: 4, start_at: 1 ).map(&:w).reverse
153
+ #
154
+ def traverse in_or_out = :out, via: nil, depth: 1, execute: true, start_at: 0, where: nil
155
+
156
+ the_query = query kind: 'traverse'
157
+ the_query.projection in_or_out.to_s + "(" + resolve_edge_name(*via) + ")"
158
+ the_query.where where if where.present?
159
+ the_query.while "$depth < #{depth} " unless depth <=0
160
+ outer_query = Query.new from: the_query, where: "$depth >= #{start_at}"
161
+ if execute
162
+ outer_query.execute.allocate_model
163
+ else
164
+ # the_query.from self # complete the query by assigning self
165
+ the_query # returns the Query -traverse object
166
+ end
167
+ end
168
+
169
+ =begin
170
+ Assigns another Vertex via an EdgeClass. If specified, puts attributes on the edge.
171
+
172
+ Returns the reloaded assigned vertex
173
+
174
+ Wrapper for
175
+ Edge.create in: self, out: a_vertex, some: attributes. on: the, edge: type }
176
+
177
+ returns the assigned vertex, thus enabling to chain vertices through
178
+
179
+ Vertex.assign() via: E , vertex: VertexClass.create()).assign( via: E, ... )
180
+ or
181
+ (1..100).each{|n| vertex = vertex.assign(via: E2, vertex: V2.create(item: n))}
182
+ =end
183
+
184
+ def assign vertex: , via: , **attributes
185
+
186
+ via.create from: self, to: vertex, **attributes
187
+
188
+ db.get vertex.rid # return the assigned vertex
189
+ rescue IndexError => e
190
+ db.logger.error "Edge not created, already present."
191
+ vertex # return the vertex (for chaining)
192
+ rescue ArgumentError => e
193
+ db.logger.error "ArgumentError: #{e.message}"
194
+ nil
195
+ end
196
+
197
+
198
+ def remove
199
+ db.execute{ "delete vertex #{rid}" }
200
+ end
201
+ =begin
202
+ Human readable representation of Vertices
203
+
204
+ Format: < Classname: Edges, Attributes >
205
+ =end
206
+ def to_human
207
+
208
+ in_and_out = -> { "{#{self.in.count}->}{->#{self.out.count }}, " }
209
+
210
+ #Default presentation of Arcade::Base::Model-Objects
211
+
212
+ "<#{self.class.to_s.snake_case}[#{rid}]:" +
213
+ in_and_out[] +
214
+ invariant_attributes.map do |attr, value|
215
+ v= case value
216
+ when Class
217
+ "< #{self.class.to_s.snake_case}: #{value.rid} >"
218
+ when Array
219
+ value.to_s
220
+ else
221
+ value.from_db
222
+ end
223
+ "%s: %s" % [ attr, v] unless v.nil?
224
+ end.compact.sort.join(', ') + ">".gsub('"' , ' ')
225
+ end
226
+
227
+
228
+ def refresh
229
+ # force reloading of edges and nodes
230
+ # edges are not cached (now)
231
+ @bufferedin, @bufferedout, @bufferedboth = nil
232
+ super
233
+ end
234
+ # expose class method to instances (as private)
235
+ # private define_method :resolve_edge_name, &method(:resolve_edge_name)
236
+ # private_class_method :resolve_edge_name
237
+ end
238
+ end
data/lib/models.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative "../lib/model/document.rb"
2
+ require_relative "../lib/model/vertex.rb"
3
+ require_relative "../lib/model/edge.rb"
4
+ require_relative "../lib/model/basicvertex.rb"
5
+ require_relative "../lib/model/basicdocument.rb"
6
+ require_relative "../lib/model/basicedge.rb"