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