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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -1
- data/Gemfile +2 -7
- data/Gemfile.lock +59 -75
- data/README.md +105 -30
- data/arcade.yml +4 -8
- data/arcadedb.gemspec +3 -6
- data/bin/console +19 -19
- data/iruby/.ipynb_checkpoints/01-start-and-end-of-datarows-checkpoint.ipynb +258 -0
- data/iruby/01-start-and-end-of-datarows.ipynb +203 -0
- data/iruby/db-console.rb +73 -0
- data/lib/arcade/api/operations.rb +56 -144
- data/lib/arcade/api/primitives.rb +126 -0
- data/lib/arcade/base.rb +88 -56
- data/lib/{config.rb → arcade/config.rb} +14 -13
- data/lib/arcade/database.rb +110 -131
- data/lib/arcade/errors.rb +8 -0
- data/lib/arcade/match.rb +162 -0
- data/lib/{query.rb → arcade/query.rb} +36 -20
- data/lib/{support → arcade/support}/conversions.rb +2 -1
- data/lib/{support → arcade/support}/model.rb +25 -17
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +15 -18
- data/lib/model/document.rb +22 -0
- data/lib/model/edge.rb +12 -0
- data/lib/model/vertex.rb +70 -51
- metadata +31 -14
- data/lib/arcade/api/version.rb +0 -5
- data/lib/match.rb +0 -216
- /data/lib/{init.rb → arcade/init.rb} +0 -0
- /data/lib/{support → arcade/support}/class.rb +0 -0
- /data/lib/{support → arcade/support}/object.rb +0 -0
- /data/lib/{support → arcade/support}/sql.rb +0 -0
- /data/lib/{support → arcade/support}/string.rb +0 -0
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"
|
data/lib/arcade/match.rb
ADDED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
320
|
-
# :in
|
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
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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.
|
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?
|
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: #
|
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
|
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
|
-
|
29
|
-
|
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
|
-
|
41
|
-
y.
|
42
|
-
|
43
|
-
y.
|
44
|
-
|
45
|
-
|
46
|
-
|
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.
|
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:
|
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
|
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
|
data/lib/arcade/version.rb
CHANGED
data/lib/arcade.rb
CHANGED
@@ -2,7 +2,7 @@ module Arcade
|
|
2
2
|
|
3
3
|
end
|
4
4
|
|
5
|
-
require "arcade/
|
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 '
|
22
|
-
|
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/
|
29
|
-
require_relative '../lib/
|
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/
|
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
|
|
data/lib/model/document.rb
CHANGED
@@ -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
|