arcadedb 0.3.3 → 0.5.0

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/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