arcadedb 0.3.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/arcade/errors.rb CHANGED
@@ -22,6 +22,14 @@ module Arcade
22
22
  end
23
23
 
24
24
  class QueryError < RuntimeError
25
+ attr_reader :error, :args
26
+ def initialize error: "", detail: "", exception: "", **args
27
+ @error = error
28
+ # @detail = detail
29
+ @args = args
30
+ @exception = exception
31
+ super detail
32
+ end
25
33
  end
26
34
 
27
35
  # used by Dry::Validation, not covered by "error"
@@ -0,0 +1,162 @@
1
+ module Arcade
2
+ class Match
3
+
4
+ include Arcade::Support::Sql
5
+
6
+ =begin
7
+ This is a very simple wrapper for the match statement
8
+
9
+ Initialize: a= Arcade::Match.new type: Arcade::DatabaseType, where: { property: 'value' }, as: :alias
10
+ Complete: b = a.out( Arcade::EdgeType ).node( while: true, as: item )[ .in.node ... ]
11
+ Inspect b.to_s
12
+ Query DB b.execute [.allocate_model]
13
+ [.analyse_result]
14
+
15
+ =end
16
+
17
+ def initialize type: , **args
18
+
19
+ @args = args
20
+ @as = []
21
+
22
+ @stack = [ "MATCH { type: #{type.database_name}, #{ assigned_parameters } }" ]
23
+
24
+ return self
25
+ end
26
+
27
+ # Inspect the generated match statement
28
+ def to_s &b
29
+ r = ""
30
+ r = "DISTINCT " if @distinct
31
+ r << @as.join(",")
32
+ r= yield(r) if block_given?
33
+ @stack.join("") + " RETURN #{r} "
34
+ end
35
+
36
+
37
+ # Execute the @stack
38
+ # generally followed by `select_result` to convert json-hashes to arcade objects
39
+ #
40
+ # The optional block modifies the result-statement
41
+ # i.e
42
+ # TG::TimeGraph.grid( 2023, 2..9 ).out(HasPosition)
43
+ # .node( as: :contract )
44
+ # .execute { "contract.symbol" }
45
+ # .select_result
46
+ # gets all associated contracts connected to the month-grid
47
+ def execute &b
48
+ Arcade::Init.db.query( to_s( &b ) )
49
+ end
50
+
51
+
52
+ # todo : metaprogramming!
53
+ def out edge=""
54
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
55
+ @stack << ".out(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
56
+ return self
57
+ end
58
+ def in edge=""
59
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
60
+ @stack << ".in(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
61
+ return self
62
+ end
63
+ def both edge=""
64
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
65
+ @stack << ".both(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
66
+ return self
67
+ end
68
+
69
+ # add conditions on edges to the match statement
70
+ def inE edge="", **args
71
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
72
+ @stack << ".inE(#{edge.is_a?(Class) ? edge.database_name.to_or : ''}) #{assigned_parameters}.outV()"
73
+ return self
74
+ end
75
+ def outE edge="", **args
76
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
77
+ @stack << ".outE(#{edge.is_a?(Class) ? edge.database_name.to_or : ''}) #{assigned_parameters}.inV()"
78
+ return self
79
+ end
80
+ def bothE edge="", **args
81
+ raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
82
+ @stack << ".bothE(#{edge.is_a?(Class) ? edge.database_name : ''}) #{assigned_parameters}.bothV()"
83
+ return self
84
+ end
85
+
86
+
87
+ # general declation of a node (ie. vertex)
88
+ def node **args
89
+ @args = args
90
+ @stack << if args.empty?
91
+ "{}"
92
+ else
93
+ "{ #{ assigned_parameters } }"
94
+ end
95
+ return self
96
+ end
97
+
98
+
99
+
100
+ ### ---------------- end of public api ---------------------------------------------------------------- ###
101
+ private
102
+
103
+
104
+ def assigned_parameters
105
+ @args.map do | k, v |
106
+ # unless k == :while # mask ruby keyword
107
+ send k, v
108
+ # else
109
+ # the_while v
110
+ # end
111
+ end.compact.join(', ')
112
+
113
+ end
114
+
115
+ ## Metastatement ---------- last --------------------
116
+ ##
117
+ ## generates a node declaration for fetching the last element of a traversal on the previous edge
118
+ ##
119
+ ## ie: Arcade::Match.new( type: self.class, where: { symbol: symbol } ).out( Arcade::HasContract )
120
+ # .node( as: :c, where: where )
121
+ # .in( Arcade::IsOrder )
122
+ # .node( last: true, as: :o )
123
+ #
124
+ # --> ... }.in('is_order'){ while: (in('is_order').size() > 0), where: (in('is_order').size() == 0), as: o }
125
+ def last arg
126
+ in_or_out = @stack[-1][1..-1] # use the last statement
127
+ "while: ( #{in_or_out}.size() > 0 ), where: (#{in_or_out}.size() == 0)"
128
+ end
129
+
130
+ def distinct arg
131
+ @distinct = true
132
+ end
133
+
134
+
135
+ def where arg
136
+ "where: ( #{ generate_sql_list( arg ) } )" unless arg.empty?
137
+ end
138
+
139
+ def while arg
140
+ if arg.is_a? TrueClass
141
+ "while: ( true )"
142
+ else
143
+ "while: ( #{ generate_sql_list( arg ) } )"
144
+ end
145
+ end
146
+
147
+ def maxdepth arg
148
+ "maxDepth: #{arg}"
149
+ end
150
+
151
+ def as arg
152
+ @as << arg
153
+ "as: " + arg.to_s
154
+ end
155
+
156
+ def type klassname
157
+ raise "type must be a Database-class" unless klassname.is_a? Class
158
+ "type: #{klassname.database_name}"
159
+ end
160
+
161
+ end
162
+ end
@@ -104,18 +104,22 @@ module Arcade
104
104
  elsif @q[:database].present?
105
105
  the_argument = @q[:database]
106
106
  case @q[:database]
107
- when Arcade::Base # a single record
107
+ when Base # a single record
108
108
  the_argument.rid
109
109
  when self.class # result of a query
110
110
  ' ( '+ the_argument.compose + ' ) '
111
111
  when Class
112
112
  the_argument.database_name
113
- else
113
+ when Arcade::Match
114
+ '(' + the_argument.to_s + ')'
115
+ when String
114
116
  if the_argument.to_s.rid? # a string with "#ab:cd"
115
117
  the_argument
116
- else # a database-class-name
118
+ else
119
+ '(' + the_argument + ')'
120
+ end
121
+ else # a database-class-name
117
122
  the_argument.to_s
118
- end
119
123
  end
120
124
  else
121
125
  raise "cannot complete until a target is specified"
@@ -311,27 +315,39 @@ end # class << self
311
315
  self
312
316
  end
313
317
 
314
- # connects by adding {in_or_out}('edgeClass')
315
- def connect_with in_or_out, via: nil
316
- argument = " #{in_or_out}(#{via.to_or if via.present?})"
317
- end
318
+ # # connects by adding {in_or_out}('edgeClass')
319
+ # def connect_with in_or_out, via: nil
320
+ # argument = " #{in_or_out}(#{via.to_or if via.present?})"
321
+ # end
322
+
318
323
  # adds a connection
319
- # in_or_out: :out ---> outE('edgeClass').in[where-condition]
320
- # :in ---> inE('edgeClass').out[where-condition]
324
+ # in_or_out: :out ---> out('edgeClass')[where-condition]
325
+ # :in ---> in('edgeClass')[where-condition]
326
+ # :inE ---> inE('edgeClass')[where-condition].outV()
327
+ # :outE ---> outE('edgeClass')[where-condition].inV()
328
+ #
329
+ # via: Edge-Class
330
+ # where: Condition to be applied on the targed vertex (in_or_out = :in, :out, :both)
331
+ # or on the intermitted edge (in_or_out = :inE, :outE, :bothE)
332
+ # Condition is inserted as "in_or_out[ condition ]"
333
+ # Attention: ranges have to be included as array, ie [ 2..4 ]
334
+ #
321
335
 
322
336
  def nodes in_or_out = :out, via: nil, where: nil, expand: false
323
337
 
324
338
  condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
325
339
  via = resolve_edge_name(via) unless via.nil?
326
340
 
327
- start = if in_or_out.is_a? Symbol
328
- in_or_out.to_s
329
- elsif in_or_out.is_a? String
330
- in_or_out
331
- else
332
- "both"
333
- end
334
- argument = " #{start}(#{via})#{condition} "
341
+ argument = if in_or_out.to_s[-1] == 'E'
342
+ case in_or_out.to_s[0..-2]
343
+ when 'in'
344
+ "inE(#{via})#{condition}.outV()"
345
+ when 'out'
346
+ "outE(#{via})#{condition}.inV()"
347
+ end
348
+ else
349
+ "#{in_or_out.to_s}(#{via})#{condition}"
350
+ end
335
351
 
336
352
  if expand.present?
337
353
  send :expand, argument
@@ -349,7 +365,7 @@ end # class << self
349
365
  # returns nil if the query was not sucessfully executed
350
366
  def execute(reduce: false, autoload: true )
351
367
  # unless projection.nil? || projection.empty?
352
- result = db.execute { compose }
368
+ result = db.transmit { compose }
353
369
  return nil unless result.is_a?(Array)
354
370
  block_given? ? result.map{|x| yield x } : result
355
371
  # return result.first if reduce && result.size == 1
@@ -370,7 +386,7 @@ end # class << self
370
386
  end
371
387
  :protected
372
388
  def resolve_target
373
- if @q[:database].is_a? Arcade::Query
389
+ if @q[:database].is_a? Query
374
390
  @q[:database].resolve_target
375
391
  else
376
392
  @q[:database]
@@ -63,6 +63,7 @@ module Arcade
63
63
  #
64
64
 
65
65
  def select_result condition=nil
66
+ return [] if self.empty?
66
67
  condition = first.keys.first if condition.nil?
67
68
  map{|x| x[condition.to_sym]}.flatten.allocate_model
68
69
  end
@@ -195,7 +196,7 @@ module Arcade
195
196
  when Symbol, String
196
197
  k.to_s
197
198
  else
198
- raise "not supported key: #[k} -- must a sting, symbol or number"
199
+ raise "not supported key: #{k} -- must a sting, symbol or number"
199
200
  end
200
201
  [orient_k, v.to_db]
201
202
  end.to_h
@@ -19,35 +19,42 @@ module Arcade
19
19
  # used by array#allocate_model
20
20
  def _allocate_model response=nil, auto = Config.autoload
21
21
 
22
+
22
23
  if response.is_a? Hash
23
24
  # save rid to a safe place
24
25
  temp_rid = response.delete :"@rid"
26
+ type = response.delete :"@type"
27
+ cat = response.delete :"@cat"
25
28
 
26
- return response if temp_rid.rid?.nil?
29
+ return response if type.nil?
30
+ temp_rid = "#0:0" if temp_rid.nil?
31
+ type = "d" if type.nil?
27
32
  # extract type infos and convert to database-name
28
- type = response.delete :"@type"
29
- cat = response.delete :"@cat"
30
- n, type_name = type.camelcase_and_namespace
31
- n = self.namespace if n.nil?
33
+ namespace, type_name = type.camelcase_and_namespace
34
+ namespace = self.namespace if namespace.nil?
32
35
  # autoconvert rid's in attributes to model-records (exclude edges!)
33
36
  if auto && !(cat.to_s =='e')
34
37
  response.transform_values! do |x|
35
38
  case x
36
39
  when String
37
- x.rid? ? x.load_rid : x
40
+ x.rid? ? x.load_rid : x # follow links
38
41
  when Array
42
+ a =[]
39
43
  x.map do| y |
40
- if y.is_a?(Hash) && y.include?(:@type) # if embedded documents are present, load them
41
- y.merge( rid: '#0:0' ).allocate_model(false)
42
- elsif y.rid? # if links are present, load the object
43
- y.load_rid(false) # do not autoload further records, prevents from recursive locking
44
- else
45
- y
46
- end
44
+ # Thread.new do ## thread or fiber decrease the performance significantly.
45
+ if y.is_a?(Hash) && y.include?(:@type) # if embedded documents are present, load them
46
+ y.allocate_model(false)
47
+ elsif y.rid? # if links are present, load the object
48
+ y.load_rid(false) # do not autoload further records, prevents from recursive locking
49
+ else
50
+ y
51
+ end
52
+ # end
47
53
  end
48
54
  when Hash
49
55
  if x.include?(:@type)
50
- x.merge( rid: '#0:0' ).allocate_model(false)
56
+ #x.allocate_model(false)
57
+ _allocate_model(x,false)
51
58
  else
52
59
  x.transform_values!{|z| z.rid? ? z.load_rid(false) : z }
53
60
  end
@@ -57,11 +64,11 @@ module Arcade
57
64
  end
58
65
  end
59
66
  # choose the appropriate class
60
- klass= Dry::Core::ClassBuilder.new( name: type_name, parent: nil, namespace: n).call
67
+ klass= Dry::Core::ClassBuilder.new( name: type_name, parent: nil, namespace: namespace ).call
61
68
  #
62
69
  begin
63
70
  # create a new object of that class with the appropriate attributes
64
- new = klass.new **response.merge( rid: temp_rid || "#0:0" ) # #0:0 --> embedded model records
71
+ new = klass.new **response.merge( rid: temp_rid )
65
72
  rescue ::ArgumentError => e
66
73
  raise "Allocation of class #{klass.to_s} failed"
67
74
  end
@@ -71,10 +78,11 @@ module Arcade
71
78
  elsif response.is_a? Array
72
79
  puts "Allocate_model..detected array"
73
80
  ## recursive behavior, just in case
74
- response.map{ | y | _allocate_model y }
81
+ response.map{ | y | _allocate_model y, auto }
75
82
  elsif response.rid?
76
83
  # Autoload rid's
77
84
  response.load_rid
85
+ # auto ? response.load_rid : response
78
86
  else
79
87
  response
80
88
  end
@@ -1,3 +1,3 @@
1
1
  module Arcade
2
- VERSION = "0.3.3"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/arcade.rb CHANGED
@@ -2,7 +2,7 @@ module Arcade
2
2
 
3
3
  end
4
4
 
5
- require "arcade/api/version"
5
+ require "arcade/version"
6
6
  require "dry/configurable"
7
7
  require "dry/struct"
8
8
  require "dry/core/class_builder"
@@ -12,28 +12,25 @@ require 'json'
12
12
  module Types
13
13
  include Dry.Types()
14
14
  end
15
- #require 'pg' # ruby postgres driver
16
- #require 'mini_sql'
17
- #require 'sequel'
18
- #require 'httparty'
19
15
  require 'yaml'
20
16
  require 'securerandom'
21
- require 'typhoeus' # curl library
22
- require_relative '../lib/arcade/errors'
23
- require_relative '../lib/support/object'
24
- require_relative '../lib/support/string'
25
- require_relative '../lib/support/class'
26
- require_relative '../lib/support/sql'
27
- require_relative '../lib/support/model'
28
- require_relative '../lib/arcade/logging'
29
- require_relative '../lib/config'
30
- require_relative '../lib/support/conversions'
17
+ require 'httpx'
18
+ require 'arcade/errors'
19
+ require_relative '../lib/arcade/support/object'
20
+ require_relative '../lib/arcade/support/string'
21
+ require_relative '../lib/arcade/support/class'
22
+ require_relative '../lib/arcade/support/sql'
23
+ require_relative '../lib/arcade/support/model'
24
+ require_relative '../lib/arcade/support/conversions'
25
+ require_relative '../lib/arcade/api/primitives'
31
26
  require_relative '../lib/arcade/api/operations'
32
27
  require_relative '../lib/arcade/base'
28
+ require_relative '../lib/arcade/logging'
29
+ require_relative '../lib/arcade/config'
33
30
  require_relative '../lib/arcade/database'
34
- require_relative '../lib/init'
31
+ require_relative '../lib/arcade/match'
32
+ require_relative '../lib/arcade/query'
33
+ require_relative '../lib/arcade/init'
35
34
  require_relative "../lib/models"
36
- require_relative '../lib/query'
37
- require_relative '../lib/match'
38
35
  require_relative '../lib/railtie' if defined? Rails::Railtie
39
36
 
@@ -6,5 +6,27 @@ module Arcade
6
6
  # def accepted_methods
7
7
  #
8
8
  # end
9
+ def self.create **attributes
10
+ Api.create_document db.database, database_name, session_id: db.session, **attributes
11
+ end
12
+
13
+ =begin
14
+ Document.delete fires a "delete vertex" command to the database.
15
+ To remove all records use »all: true« as argument
16
+
17
+ The "where" parameter is optional
18
+ =end
19
+ def self.delete where: {} , **args
20
+ if args[:all] == true
21
+ where = {}
22
+ else
23
+ where.merge!(args) if where.is_a?(Hash)
24
+ return 0 if where.empty?
25
+ end
26
+ # query returns [{count => n }]
27
+ # puts "delete from #{database_name} #{compose_where(where)}"
28
+ db.transmit { "delete from `#{database_name}` #{compose_where(where)}" } &.first[:count] rescue 0
29
+ end
30
+
9
31
  end
10
32
  end
data/lib/model/edge.rb CHANGED
@@ -13,6 +13,18 @@ module Arcade
13
13
  #
14
14
  def self.create from:, to:, **attr
15
15
  db.create_edge database_name, from: from, to: to, **attr
16
+ rescue Arcade::QueryError => e
17
+ if e.message =~ /Duplicated key\s+.+\sfound on index/
18
+ if e.args.keys.first == :exceptionArgs
19
+ # return the previously assigned edge
20
+ e.args[:exceptionArgs].split('|').last.load_rid
21
+ else
22
+ raise
23
+ end
24
+ else
25
+ raise
26
+ end
27
+
16
28
  end
17
29
 
18
30
  def delete