arcadedb 0.4 → 0.5.2
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 +32 -1
- data/Gemfile.lock +2 -36
- data/README.md +191 -41
- data/arcade.yml +1 -1
- data/bin/console +17 -16
- 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 +48 -78
- data/lib/arcade/api/primitives.rb +64 -29
- data/lib/arcade/base.rb +105 -51
- data/lib/{config.rb → arcade/config.rb} +5 -6
- data/lib/arcade/database.rb +107 -156
- data/lib/arcade/errors.rb +8 -0
- data/lib/{init.rb → arcade/init.rb} +5 -2
- data/lib/arcade/match.rb +210 -0
- data/lib/{query.rb → arcade/query.rb} +40 -29
- data/lib/{support → arcade/support}/conversions.rb +1 -0
- data/lib/{support → arcade/support}/model.rb +23 -22
- data/lib/{support → arcade/support}/sql.rb +1 -1
- data/lib/{support → arcade/support}/string.rb +11 -14
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +16 -15
- data/lib/model/document.rb +22 -0
- data/lib/model/edge.rb +29 -0
- data/lib/model/revision.rb +41 -0
- data/lib/model/revision_record.rb +29 -0
- data/lib/model/vertex.rb +118 -48
- data/lib/models.rb +2 -0
- data/lib/types.rb +35 -0
- metadata +22 -16
- data/lib/match.rb +0 -216
- /data/lib/{support → arcade/support}/class.rb +0 -0
- /data/lib/{support → arcade/support}/object.rb +0 -0
@@ -27,7 +27,8 @@ module Arcade
|
|
27
27
|
[], # aliases
|
28
28
|
'', # database
|
29
29
|
[], #set,
|
30
|
-
[] # remove
|
30
|
+
[], # remove
|
31
|
+
[] # group by
|
31
32
|
args.each{|k,v| send k, v}
|
32
33
|
@fill = block_given? ? yield : 'and'
|
33
34
|
end
|
@@ -62,12 +63,10 @@ module Arcade
|
|
62
63
|
@q[:kind]
|
63
64
|
end
|
64
65
|
end
|
65
|
-
|
66
|
-
Output the compiled query
|
67
|
-
Parameter: destination (rest, batch )
|
68
|
-
If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
|
69
|
-
=end
|
70
|
-
|
66
|
+
# ------------------ compose---------------------------------------
|
67
|
+
# Output the compiled query
|
68
|
+
# Parameter: destination (rest, batch )
|
69
|
+
# If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
|
71
70
|
def compose(destination: :batch)
|
72
71
|
if kind.to_sym == :update
|
73
72
|
return_statement = "return after " + ( @q[:aliases].empty? ? "$current" : @q[:aliases].first.to_s)
|
@@ -110,21 +109,24 @@ module Arcade
|
|
110
109
|
' ( '+ the_argument.compose + ' ) '
|
111
110
|
when Class
|
112
111
|
the_argument.database_name
|
113
|
-
|
112
|
+
when Arcade::Match
|
113
|
+
'(' + the_argument.to_s + ')'
|
114
|
+
when String
|
114
115
|
if the_argument.to_s.rid? # a string with "#ab:cd"
|
115
116
|
the_argument
|
116
|
-
|
117
|
+
else
|
118
|
+
'(' + the_argument + ')'
|
119
|
+
end
|
120
|
+
else # a database-class-name
|
117
121
|
the_argument.to_s
|
118
|
-
end
|
119
122
|
end
|
120
123
|
else
|
121
124
|
raise "cannot complete until a target is specified"
|
122
125
|
end
|
123
126
|
end
|
124
127
|
|
125
|
-
|
126
|
-
|
127
|
-
=end
|
128
|
+
# -------------------- from ---------------------------------------
|
129
|
+
# arg can either be a Database class to operate on or a Subquery providing data to query further
|
128
130
|
def from arg = nil
|
129
131
|
if arg.present?
|
130
132
|
@q[:database] = arg
|
@@ -311,27 +313,36 @@ end # class << self
|
|
311
313
|
self
|
312
314
|
end
|
313
315
|
|
314
|
-
|
315
|
-
|
316
|
-
argument = " #{in_or_out}(#{via.to_or if via.present?})"
|
317
|
-
end
|
316
|
+
|
317
|
+
# ------------------ nodes ---------------------------------------
|
318
318
|
# adds a connection
|
319
|
-
# in_or_out: :out
|
320
|
-
# :in
|
319
|
+
# in_or_out: :out ---> out('edgeClass')[where-condition]
|
320
|
+
# :in ---> in('edgeClass')[where-condition]
|
321
|
+
# :inE ---> inE('edgeClass')[where-condition].outV()
|
322
|
+
# :outE ---> outE('edgeClass')[where-condition].inV()
|
323
|
+
#
|
324
|
+
# via: Edge-Class
|
325
|
+
# where: Condition to be applied on the targed vertex (in_or_out = :in, :out, :both)
|
326
|
+
# or on the intermitted edge (in_or_out = :inE, :outE, :bothE)
|
327
|
+
# Condition is inserted as "in_or_out[ condition ]"
|
328
|
+
# Attention: ranges have to be included as array, ie [ 2..4 ]
|
329
|
+
#
|
321
330
|
|
322
331
|
def nodes in_or_out = :out, via: nil, where: nil, expand: false
|
323
332
|
|
324
|
-
condition = where.present? ?
|
333
|
+
condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
|
325
334
|
via = resolve_edge_name(via) unless via.nil?
|
326
335
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
336
|
+
argument = if in_or_out.to_s[-1] == 'E'
|
337
|
+
case in_or_out.to_s[0..-2]
|
338
|
+
when 'in'
|
339
|
+
"inE(#{via})#{condition}.outV()"
|
340
|
+
when 'out'
|
341
|
+
"outE(#{via})#{condition}.inV()"
|
342
|
+
end
|
343
|
+
else
|
344
|
+
"#{in_or_out.to_s}(#{via})#{condition}"
|
345
|
+
end
|
335
346
|
|
336
347
|
if expand.present?
|
337
348
|
send :expand, argument
|
@@ -349,7 +360,7 @@ end # class << self
|
|
349
360
|
# returns nil if the query was not sucessfully executed
|
350
361
|
def execute(reduce: false, autoload: true )
|
351
362
|
# unless projection.nil? || projection.empty?
|
352
|
-
result = db.
|
363
|
+
result = db.transmit { compose }
|
353
364
|
return nil unless result.is_a?(Array)
|
354
365
|
block_given? ? result.map{|x| yield x } : result
|
355
366
|
# return result.first if reduce && result.size == 1
|
@@ -15,42 +15,42 @@ module Arcade
|
|
15
15
|
end.join(',')
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
18
|
# used by array#allocate_model
|
20
19
|
def _allocate_model response=nil, auto = Config.autoload
|
21
20
|
|
22
|
-
|
23
21
|
if response.is_a? Hash
|
24
22
|
# save rid to a safe place
|
25
|
-
temp_rid = response.delete :"@rid"
|
26
|
-
type
|
27
|
-
cat
|
23
|
+
temp_rid = response.delete( :"@rid" ) || response.delete( :rid ) || "#0:0"
|
24
|
+
type = response.delete( :"@type" ) || response.delete( :type ) || nil
|
25
|
+
cat = response.delete( :"@cat" ) || response.delete( :cat ) || "d"
|
28
26
|
|
29
27
|
return response if type.nil?
|
30
|
-
temp_rid = "#0:0" if temp_rid.nil?
|
31
|
-
type = "d" if type.nil?
|
32
28
|
# extract type infos and convert to database-name
|
33
|
-
|
34
|
-
|
29
|
+
namespace, type_name = type.camelcase_and_namespace
|
30
|
+
namespace = self.namespace if namespace.nil?
|
35
31
|
# autoconvert rid's in attributes to model-records (exclude edges!)
|
36
32
|
if auto && !(cat.to_s =='e')
|
37
|
-
response.transform_values
|
33
|
+
response.transform_values do |x|
|
38
34
|
case x
|
39
35
|
when String
|
40
|
-
x.rid? ? x.load_rid : x
|
36
|
+
x.rid? ? x.load_rid : x # follow links
|
41
37
|
when Array
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
a =[]
|
39
|
+
x.map do | y |
|
40
|
+
# Thread.new do ## using thread or fiber decreases the performance significantly.
|
41
|
+
if y.is_a?(Hash) && y.include?(:@type) # if embedded documents are present, load them
|
42
|
+
_allocate_model(y,false)
|
43
|
+
elsif y.rid? # if links are present, load the object
|
44
|
+
y.load_rid(false) # do not autoload further records, prevents from recursive locking
|
45
|
+
else
|
46
|
+
y
|
47
|
+
end
|
48
|
+
# end
|
50
49
|
end
|
51
50
|
when Hash
|
52
51
|
if x.include?(:@type)
|
53
|
-
x.allocate_model(false)
|
52
|
+
#x.allocate_model(false)
|
53
|
+
_allocate_model(x,false)
|
54
54
|
else
|
55
55
|
x.transform_values!{|z| z.rid? ? z.load_rid(false) : z }
|
56
56
|
end
|
@@ -60,7 +60,7 @@ module Arcade
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
# choose the appropriate class
|
63
|
-
klass= Dry::Core::ClassBuilder.new( name: type_name, parent:
|
63
|
+
klass= Dry::Core::ClassBuilder.new( name: type_name, parent: nil, namespace: namespace ).call
|
64
64
|
#
|
65
65
|
begin
|
66
66
|
# create a new object of that class with the appropriate attributes
|
@@ -74,10 +74,11 @@ module Arcade
|
|
74
74
|
elsif response.is_a? Array
|
75
75
|
puts "Allocate_model..detected array"
|
76
76
|
## recursive behavior, just in case
|
77
|
-
response.map{ | y | _allocate_model y }
|
77
|
+
response.map{ | y | _allocate_model y, auto }
|
78
78
|
elsif response.rid?
|
79
79
|
# Autoload rid's
|
80
80
|
response.load_rid
|
81
|
+
# auto ? response.load_rid : response
|
81
82
|
else
|
82
83
|
response
|
83
84
|
end
|
@@ -11,9 +11,12 @@ module Arcade
|
|
11
11
|
delimiters = Regexp.union(['-', '_'])
|
12
12
|
n,c= self.split(delimiters).then { |first, *rest| [first.tap {|s| s[0] = s[0].upcase}, rest.map(&:capitalize).join] }
|
13
13
|
## if base is the first part of the type-name, probably Arcade::Base is choosen a namespace. thats wrong
|
14
|
-
|
14
|
+
# Arcade::Base descendants are prefetched in Init.models
|
15
|
+
# They have to be fetched to load both `Arcade::Ge` and `Arcade::GeSomething`
|
16
|
+
# (otherwise: Arcade::Ge and Arcade::Ge::Something)
|
17
|
+
namespace_present = unless n == 'Base' || Arcade::Init.models.include?(n)
|
15
18
|
Object.const_get(n) rescue false # Database.namespace
|
16
|
-
else
|
19
|
+
else
|
17
20
|
false
|
18
21
|
end
|
19
22
|
# if no namespace is found, leave it empty and return the capitalized string as class name
|
@@ -22,6 +25,10 @@ module Arcade
|
|
22
25
|
[ Database.namespace, self.capitalize ]
|
23
26
|
end
|
24
27
|
end
|
28
|
+
def camelcase
|
29
|
+
# Split the string into words, capitalize each word, and join them together
|
30
|
+
self.split('_').map(&:capitalize).join
|
31
|
+
end
|
25
32
|
|
26
33
|
def snake_case
|
27
34
|
n= if split('::').first == Database.namespace.to_s
|
@@ -51,7 +58,7 @@ module Arcade
|
|
51
58
|
end
|
52
59
|
# return a valid rid (format: "nn:mm") or nil
|
53
60
|
def rid
|
54
|
-
self["#"].nil? ? "#"+ self : self if rid?
|
61
|
+
self["#"].nil? ? "#"+ self : self if rid?
|
55
62
|
end
|
56
63
|
|
57
64
|
def where **args
|
@@ -80,7 +87,7 @@ module Arcade
|
|
80
87
|
## Load the database object if the string is a rid
|
81
88
|
# (Default: No Autoloading of rid-links)
|
82
89
|
def load_rid autocomplete = false
|
83
|
-
db.get( self ){ autocomplete } if rid? rescue nil
|
90
|
+
db.get( self.strip ){ autocomplete } if rid? rescue nil
|
84
91
|
end
|
85
92
|
|
86
93
|
# updates the record ### retired in favour of Arcade::Base.update
|
@@ -104,13 +111,3 @@ module Arcade
|
|
104
111
|
end
|
105
112
|
|
106
113
|
String.include Arcade::Support::String
|
107
|
-
|
108
|
-
module Types
|
109
|
-
include Dry.Types()
|
110
|
-
|
111
|
-
# include in attribute definitions
|
112
|
-
Rid = String.constrained( format: /\A[#]{1}[0-9]{1,}:[0-9]{1,}\z/ )
|
113
|
-
Blockchain = String.constrained( format: /^(algo|eth|btc)$/ ) # add other blockchain symbols here
|
114
|
-
Email = String.constrained( format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i )
|
115
|
-
|
116
|
-
end
|
data/lib/arcade/version.rb
CHANGED
data/lib/arcade.rb
CHANGED
@@ -9,28 +9,29 @@ require "dry/core/class_builder"
|
|
9
9
|
require "dry/core/class_attributes"
|
10
10
|
require 'json'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
require_relative '../lib/types'
|
13
|
+
#module Types
|
14
|
+
# include Dry.Types()
|
15
|
+
#end
|
15
16
|
require 'yaml'
|
16
17
|
require 'securerandom'
|
17
18
|
require 'httpx'
|
18
|
-
|
19
|
-
require_relative '../lib/support/
|
20
|
-
require_relative '../lib/support/
|
21
|
-
require_relative '../lib/support/
|
22
|
-
require_relative '../lib/support/
|
23
|
-
require_relative '../lib/support/
|
24
|
-
require_relative '../lib/arcade/logging'
|
25
|
-
require_relative '../lib/config'
|
26
|
-
require_relative '../lib/support/conversions'
|
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'
|
27
25
|
require_relative '../lib/arcade/api/primitives'
|
28
26
|
require_relative '../lib/arcade/api/operations'
|
27
|
+
require_relative '../lib/arcade/errors'
|
29
28
|
require_relative '../lib/arcade/base'
|
29
|
+
require_relative '../lib/arcade/logging'
|
30
|
+
require_relative '../lib/arcade/config'
|
30
31
|
require_relative '../lib/arcade/database'
|
31
|
-
require_relative '../lib/
|
32
|
+
require_relative '../lib/arcade/match'
|
33
|
+
require_relative '../lib/arcade/query'
|
34
|
+
require_relative '../lib/arcade/init'
|
32
35
|
require_relative "../lib/models"
|
33
|
-
require_relative '../lib/query'
|
34
|
-
require_relative '../lib/match'
|
35
36
|
require_relative '../lib/railtie' if defined? Rails::Railtie
|
36
37
|
|
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" 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,8 +13,37 @@ 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
|
|
30
|
+
|
31
|
+
## class-method: Delete Edge between two vertices
|
32
|
+
##
|
33
|
+
## returns the count of deleted edges
|
34
|
+
##
|
35
|
+
## todo: add conditions
|
36
|
+
def self.delete from:, to:
|
37
|
+
return 0 if from.nil? || to.nil?
|
38
|
+
raise "parameter ( from: ) must be a String or a Vertex" unless from.is_a?(String) || from.is_a?( Arcade::Vertex)
|
39
|
+
raise "parameter ( to: ) must be a String or a Vertex" unless to.is_a?(String) || to.is_a?( Arcade::Vertex)
|
40
|
+
raise "parameters (from: + to:) must respond to `.rid`" unless from.respond_to?( :rid) && to.respond_to?(:rid)
|
41
|
+
|
42
|
+
db.execute{ "delete edge from #{from.rid} to #{to.rid} " } &.select_result &.first
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
## instance method: Delete specified edge
|
18
47
|
def delete
|
19
48
|
db.execute{ "delete edge #{ rid }" }.select_result
|
20
49
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Arcade
|
2
|
+
class Revision < Arcade::Vertex
|
3
|
+
attribute :protocol?, Types::Nominal::Array
|
4
|
+
|
5
|
+
@@u='root' # default user
|
6
|
+
@@i = 'Record initiated'
|
7
|
+
|
8
|
+
def self.set_user u
|
9
|
+
@@u = u
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.set_initial_message m
|
13
|
+
@@i = m
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.insert **attr
|
17
|
+
|
18
|
+
action = block_given? ? yield : @@i
|
19
|
+
i= super **attr
|
20
|
+
i.update_list :protocol,
|
21
|
+
Arcade::RevisionRecord.create( user: @@u, action: action ),
|
22
|
+
modus: :first
|
23
|
+
i.refresh
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def update **attr
|
29
|
+
hist_state = attr.keys.map do |k|
|
30
|
+
[ k, send( k ) ]
|
31
|
+
end.to_h
|
32
|
+
super **attr
|
33
|
+
revision = if block_given?
|
34
|
+
Arcade::RevisionRecord.create( user: @@u, action: yield, fields: hist_state )
|
35
|
+
else
|
36
|
+
Arcade::RevisionRecord.create( fields: hist_state )
|
37
|
+
end
|
38
|
+
update_list :protocol, revision, modus: :append
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Arcade
|
2
|
+
class RevisionRecord < Document
|
3
|
+
attribute :user, Types::Nominal::String.default( 'root'.freeze )
|
4
|
+
attribute :action, Types::Nominal::String.default( "Property changed".freeze )
|
5
|
+
attribute :date, Types::Nominal::Date.default( Date.today.freeze )
|
6
|
+
attribute :fields?, Types::Nominal::Hash
|
7
|
+
|
8
|
+
# Revision Records are always embedded.
|
9
|
+
# create and insert methods return an new ruby record
|
10
|
+
def self.create **attr
|
11
|
+
new **( { rid: '#0:0', date: Date.today}.merge attr )
|
12
|
+
end
|
13
|
+
def self.insert **attr
|
14
|
+
new **( { rid: '#0:0', date: Date.today}.merge **attr )
|
15
|
+
end
|
16
|
+
|
17
|
+
=begin
|
18
|
+
Its not allowed to delete records.
|
19
|
+
=end
|
20
|
+
def self.delete where: {} , **args
|
21
|
+
raise ArcadeQueryError, "Its not possible to delete revision records"
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete
|
25
|
+
raise ArcadeQueryError, "Its not possible to delete revision records"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|