arcadedb 0.3.1 → 0.4
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/.gitignore +57 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +186 -0
- data/Guardfile +30 -0
- data/LICENSE +21 -0
- data/README.md +242 -0
- data/Rakefile +11 -0
- data/arcade.yml +23 -0
- data/arcadedb.gemspec +32 -0
- data/bin/+ +106 -0
- data/bin/console +126 -0
- data/examples/books.rb +139 -0
- data/examples/relation_1__1.rb +149 -0
- data/examples/relation_1__n.rb +56 -0
- data/examples/relation_n_n.rb +194 -0
- data/lib/arcade/api/operations.rb +257 -0
- data/lib/arcade/api/primitives.rb +98 -0
- data/lib/arcade/base.rb +454 -0
- data/lib/arcade/database.rb +367 -0
- data/lib/arcade/errors.rb +71 -0
- data/lib/arcade/logging.rb +38 -0
- data/lib/arcade/version.rb +3 -0
- data/lib/arcade.rb +36 -0
- data/lib/config.rb +72 -0
- data/lib/init.rb +50 -0
- data/lib/match.rb +216 -0
- data/lib/model/basicdocument.rb +7 -0
- data/lib/model/basicedge.rb +6 -0
- data/lib/model/basicvertex.rb +6 -0
- data/lib/model/document.rb +10 -0
- data/lib/model/edge.rb +47 -0
- data/lib/model/vertex.rb +238 -0
- data/lib/models.rb +6 -0
- data/lib/query.rb +384 -0
- data/lib/support/class.rb +13 -0
- data/lib/support/conversions.rb +295 -0
- data/lib/support/model.rb +87 -0
- data/lib/support/object.rb +20 -0
- data/lib/support/sql.rb +74 -0
- data/lib/support/string.rb +116 -0
- data/rails/arcade.rb +20 -0
- data/rails/config.yml +10 -0
- data/rails/connect.yml +17 -0
- data/rails.md +147 -0
- metadata +64 -5
@@ -0,0 +1,194 @@
|
|
1
|
+
##
|
2
|
+
## This example realises a bidirectional 1:n relation using Edges & Vertices
|
3
|
+
#
|
4
|
+
## The schema is implemented in modelfiles located in spec/model
|
5
|
+
## /spec/models/ex/human.rb # Vertex
|
6
|
+
## /spec/models/ex/depend_on.rb # Edge
|
7
|
+
#
|
8
|
+
# This script runs in the test environment.
|
9
|
+
##
|
10
|
+
require 'bundler/setup'
|
11
|
+
require 'zeitwerk'
|
12
|
+
require 'arcade'
|
13
|
+
|
14
|
+
include Arcade
|
15
|
+
## require modelfiles
|
16
|
+
loader = Zeitwerk::Loader.new
|
17
|
+
loader.push_dir ("#{__dir__}/../spec/model")
|
18
|
+
loader.setup
|
19
|
+
|
20
|
+
## clear test database
|
21
|
+
|
22
|
+
databases = Arcade::Api.databases
|
23
|
+
if databases.include?(Arcade::Config.database[:test])
|
24
|
+
Arcade::Api.drop_database Arcade::Config.database[:test]
|
25
|
+
end
|
26
|
+
Arcade::Api.create_database Arcade::Config.database[:test]
|
27
|
+
|
28
|
+
## Universal Database handle
|
29
|
+
DB = Arcade::Init.connect 'test'
|
30
|
+
|
31
|
+
## ------------------------------------------------------ End Setup ------------------------------------- ##
|
32
|
+
##
|
33
|
+
## We are realising a self referencing relation
|
34
|
+
## parent <--> children
|
35
|
+
#
|
36
|
+
|
37
|
+
|
38
|
+
Ex::Human.create_type # initialize the database
|
39
|
+
Ex::DependOn.create_type
|
40
|
+
|
41
|
+
nodes = %w( Guthorn Fulkerson Sniezek Tomasulo Portwine Keala Revelli Jacks Gorby Alcaoa ).map do | name |
|
42
|
+
Ex::Human.insert name: name, birth: 2022 - rand(99), married: rand(2)==1
|
43
|
+
end
|
44
|
+
|
45
|
+
puts Ex::Human.count.to_s + " Human Vertices created"
|
46
|
+
|
47
|
+
puts "------------------------------ get a sorted list of married humans ---------------------------------"
|
48
|
+
puts
|
49
|
+
|
50
|
+
merried = Ex::Human.query( where: { married: true })
|
51
|
+
merried.order 'birth'
|
52
|
+
new_merried = Query.new projection: 'name, 2022-birth as age ', from: merried # merge two queries
|
53
|
+
puts new_merried.query
|
54
|
+
|
55
|
+
puts "------------------------------ and one for not married humans ---------------------------------"
|
56
|
+
puts
|
57
|
+
|
58
|
+
singles = Ex::Human.query( where: { married: false })
|
59
|
+
singles.order 'birth'
|
60
|
+
new_singles = Query.new projection: 'name, 2022-birth as age ', from: singles # merge two queries
|
61
|
+
puts new_singles.query
|
62
|
+
|
63
|
+
|
64
|
+
puts "------------------------------ connect married humans with children ------------------------------"
|
65
|
+
children = singles.query.allocate_model
|
66
|
+
|
67
|
+
begin
|
68
|
+
children_enumerator = children.each
|
69
|
+
merried.query.allocate_model.map do | parent |
|
70
|
+
parent.assign via: Ex::DependOn, vertex: children_enumerator.next
|
71
|
+
end
|
72
|
+
rescue StopIteration
|
73
|
+
puts "No more children"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Ex::Human.parents is essential
|
77
|
+
# EX::Human.query projection: 'out()' , whee: { married: true }
|
78
|
+
# Ex::Human.children is essential
|
79
|
+
# EX::Human.query projection: 'out()' , whee: { married: true }
|
80
|
+
puts "--------------------------- Parent and Children ---------------------------------------------------"
|
81
|
+
puts
|
82
|
+
puts "%10s %7s %10s %30s " % ["Parent", "Age", "Child", "sorted by Child"]
|
83
|
+
puts "- " * 50
|
84
|
+
Ex::Human.parents( order: 'name' ).each do |parent| # note: order: 'name' is included
|
85
|
+
# in the query
|
86
|
+
puts "%10s %7d %10s " % [parent.name, 2022 - parent.birth, parent.out.first.name]
|
87
|
+
end
|
88
|
+
|
89
|
+
puts "--------------------------- child and parent -----------------------------------------------------"
|
90
|
+
puts
|
91
|
+
puts "%10s %7s %10s %30s " % ["Child", "Age", "Parent", "sorted by Parent"]
|
92
|
+
puts "- " * 50
|
93
|
+
Ex::Human.children( order: 'name' ).each do |child|
|
94
|
+
puts "%10s %7d %10s " % [child.name, 2022 - child.birth, child.in.first.name]
|
95
|
+
end
|
96
|
+
|
97
|
+
puts "--------------------------- Add child to a parent -----------------------------------------------"
|
98
|
+
puts
|
99
|
+
|
100
|
+
Ex::Human.parents.first.assign via: Ex::DependOn, vertex: Ex::Human.insert( name: "TestBaby", birth: 2022, married: false)
|
101
|
+
|
102
|
+
puts "Parent: " + Ex::Human.parents.first.to_human
|
103
|
+
puts "Children: \n " + Ex::Human.parents.first.out.to_human.join("\n ")
|
104
|
+
|
105
|
+
## Expected output
|
106
|
+
__END__
|
107
|
+
|
108
|
+
Using default database credentials and settings fron /home/ubuntu/workspace/arcadedb
|
109
|
+
27.04.(18:35:05) INFO->Q: create vertex type ex_human
|
110
|
+
27.04.(18:35:05) INFO->Q: CREATE PROPERTY ex_human.name STRING
|
111
|
+
27.04.(18:35:05) INFO->Q: CREATE PROPERTY ex_human.married BOOLEAN
|
112
|
+
27.04.(18:35:05) INFO->Q: CREATE INDEX `Example[human]` ON ex_human ( name ) UNIQUE
|
113
|
+
27.04.(18:35:05) INFO->Q: create edge type ex_depend_on
|
114
|
+
27.04.(18:35:05) INFO->Q: CREATE INDEX depends_out_in ON ex_depend_on (`@out`, `@in`) UNIQUE
|
115
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Guthorn","birth":1962,"married":false}
|
116
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Fulkerson","birth":1962,"married":true}
|
117
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Sniezek","birth":1972,"married":true}
|
118
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Tomasulo","birth":1953,"married":false}
|
119
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Portwine","birth":1975,"married":true}
|
120
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Keala","birth":1961,"married":false}
|
121
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Revelli","birth":1948,"married":true}
|
122
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Jacks","birth":1993,"married":true}
|
123
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Gorby","birth":1979,"married":false}
|
124
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"Alcaoa","birth":1960,"married":false}
|
125
|
+
27.04.(18:35:05) INFO->Q: select count(*) from ex_human
|
126
|
+
10 Human Vertices created
|
127
|
+
------------------------------ get a sorted list of married humans ---------------------------------
|
128
|
+
|
129
|
+
27.04.(18:35:05) INFO->Q: select name, 2022-birth as age from ( select from ex_human where married = true order by birth )
|
130
|
+
{:name=>"Revelli", :age=>74}
|
131
|
+
{:name=>"Fulkerson", :age=>60}
|
132
|
+
{:name=>"Sniezek", :age=>50}
|
133
|
+
{:name=>"Portwine", :age=>47}
|
134
|
+
{:name=>"Jacks", :age=>29}
|
135
|
+
------------------------------ and one for not married humans ---------------------------------
|
136
|
+
|
137
|
+
27.04.(18:35:05) INFO->Q: select name, 2022-birth as age from ( select from ex_human where married = false order by birth )
|
138
|
+
{:name=>"Tomasulo", :age=>69}
|
139
|
+
{:name=>"Alcaoa", :age=>62}
|
140
|
+
{:name=>"Keala", :age=>61}
|
141
|
+
{:name=>"Guthorn", :age=>60}
|
142
|
+
{:name=>"Gorby", :age=>43}
|
143
|
+
------------------------------ connect married humans with children ------------------------------
|
144
|
+
27.04.(18:35:05) INFO->Q: select from ex_human where married = false order by birth
|
145
|
+
27.04.(18:35:05) INFO->Q: select from ex_human where married = true order by birth
|
146
|
+
27.04.(18:35:05) INFO->Q: create edge ex_depend_on from #19:0 to #10:0 CONTENT {"set":{}}
|
147
|
+
27.04.(18:35:05) INFO->Q: create edge ex_depend_on from #4:0 to #4:1 CONTENT {"set":{}}
|
148
|
+
27.04.(18:35:05) INFO->Q: create edge ex_depend_on from #7:0 to #16:0 CONTENT {"set":{}}
|
149
|
+
27.04.(18:35:05) INFO->Q: create edge ex_depend_on from #13:0 to #1:0 CONTENT {"set":{}}
|
150
|
+
27.04.(18:35:05) INFO->Q: create edge ex_depend_on from #22:0 to #1:1 CONTENT {"set":{}}
|
151
|
+
--------------------------- Parent and Children ---------------------------------------------------
|
152
|
+
|
153
|
+
Parent Age Child sorted by Child
|
154
|
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
155
|
+
27.04.(18:35:05) INFO->Q: select in() from ex_human where married = false order by name
|
156
|
+
27.04.(18:35:05) INFO->Q: select out() from #4:0
|
157
|
+
Fulkerson 60 Alcaoa
|
158
|
+
27.04.(18:35:05) INFO->Q: select out() from #22:0
|
159
|
+
Jacks 29 Gorby
|
160
|
+
27.04.(18:35:05) INFO->Q: select out() from #13:0
|
161
|
+
Portwine 47 Guthorn
|
162
|
+
27.04.(18:35:05) INFO->Q: select out() from #7:0
|
163
|
+
Sniezek 50 Keala
|
164
|
+
27.04.(18:35:05) INFO->Q: select out() from #19:0
|
165
|
+
Revelli 74 Tomasulo
|
166
|
+
--------------------------- child and parent -----------------------------------------------------
|
167
|
+
|
168
|
+
Child Age Parent sorted by Parent
|
169
|
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
170
|
+
27.04.(18:35:05) INFO->Q: select out() from ex_human where married = true order by name
|
171
|
+
27.04.(18:35:05) INFO->Q: select in() from #4:1
|
172
|
+
Alcaoa 62 Fulkerson
|
173
|
+
27.04.(18:35:05) INFO->Q: select in() from #1:1
|
174
|
+
Gorby 43 Jacks
|
175
|
+
27.04.(18:35:05) INFO->Q: select in() from #1:0
|
176
|
+
Guthorn 60 Portwine
|
177
|
+
27.04.(18:35:05) INFO->Q: select in() from #10:0
|
178
|
+
Tomasulo 69 Revelli
|
179
|
+
27.04.(18:35:05) INFO->Q: select in() from #16:0
|
180
|
+
Keala 61 Sniezek
|
181
|
+
--------------------------- Add child to a parent -----------------------------------------------
|
182
|
+
|
183
|
+
27.04.(18:35:05) INFO->Q: select in() from ex_human where married = false
|
184
|
+
27.04.(18:35:05) INFO->Q: INSERT INTO ex_human CONTENT {"name":"TestBaby","birth":2022,"married":false}
|
185
|
+
27.04.(18:35:05) INFO->Q: create edge ex_depend_on from #13:0 to #7:1 CONTENT {"set":{}}
|
186
|
+
27.04.(18:35:05) INFO->Q: select in() from ex_human where married = false
|
187
|
+
Parent: <ex_human[#13:0]: {0->}{->2}}, birth: 1975, married: true, name: Portwine>
|
188
|
+
27.04.(18:35:05) INFO->Q: select in() from ex_human where married = false
|
189
|
+
27.04.(18:35:05) INFO->Q: select out() from #13:0
|
190
|
+
Children:
|
191
|
+
<ex_human[#1:0]: {->}{->}}, birth: 1962, married: false, name: Guthorn>
|
192
|
+
<ex_human[#7:1]: {->}{->}}, birth: 2022, married: false, name: TestBaby>
|
193
|
+
|
194
|
+
|
@@ -0,0 +1,257 @@
|
|
1
|
+
module Arcade
|
2
|
+
module Api
|
3
|
+
extend Primitives
|
4
|
+
=begin
|
5
|
+
This is a simple admin interface
|
6
|
+
|
7
|
+
$ Arcade::Api.databases # returns an Array of known databases
|
8
|
+
$ Arcade::Api.create_database <a string> # returns true if succesfull
|
9
|
+
$ Arcade::Api.drop_database <a string> # returns true if successfull
|
10
|
+
|
11
|
+
$ Arcade::Api.create_document <database>, <type>, attributes
|
12
|
+
$ Arcade::Api.execute( <database> ) { <query> }
|
13
|
+
$ Arcade::Api.query( <database> ) { <query> }
|
14
|
+
$ Arcade::Api.get_record <database>, rid # returns a hash
|
15
|
+
|
16
|
+
|
17
|
+
<query> is either a string
|
18
|
+
or a hash { :query => " ",
|
19
|
+
:language => one of :sql, :cypher, :gmelion: :neo4j ,
|
20
|
+
:params => a hash of parameters,
|
21
|
+
:limit => a number ,
|
22
|
+
:serializer: one of :graph, :record }
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
# ------------------------------ Service methods ------------------------------------------------- #
|
27
|
+
# ------------------------------ ------------------------------------------------- #
|
28
|
+
# ------------------------------ databases ------------------------------------------------- #
|
29
|
+
# returns an array of databases present on the database-server #
|
30
|
+
|
31
|
+
def self.databases
|
32
|
+
get_data 'databases'
|
33
|
+
end
|
34
|
+
|
35
|
+
# ------------------------------ create database ------------------------------------------------- #
|
36
|
+
# creates a database if not present #
|
37
|
+
def self.create_database name
|
38
|
+
return if databases.include?( name.to_s )
|
39
|
+
payload = { "command" => "create database #{name}" }
|
40
|
+
post_data "server", payload
|
41
|
+
rescue HTTPX::HTTPError => e
|
42
|
+
logger.fatal "Create database #{name} through \"POST create/#{name}\" failed"
|
43
|
+
logger.fatal e
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
|
47
|
+
# ------------------------------ drop database ------------------------------------------------- #
|
48
|
+
# deletes the given database #
|
49
|
+
def self.drop_database name
|
50
|
+
return unless databases.include?( name.to_s )
|
51
|
+
payload = {"command" => "drop database #{name}" }
|
52
|
+
post_data "server", payload
|
53
|
+
rescue HTTPX::HTTPError => e
|
54
|
+
logger.fatal "Drop database #{name} through \"POST drop database/#{name}\" failed"
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
# ------------------------------ create document ------------------------------------------------- #
|
58
|
+
# adds a document to the specified database table
|
59
|
+
#
|
60
|
+
# specify database-fields as hash-type parameters
|
61
|
+
#
|
62
|
+
# i.e Arcade::Api.create_document 'devel', 'documents', name: 'herta meyer', age: 56, sex: 'f'
|
63
|
+
#
|
64
|
+
# returns the rid of the inserted dataset
|
65
|
+
#
|
66
|
+
def self.create_document database, type, **attributes
|
67
|
+
payload = { "@type" => type }.merge( attributes )
|
68
|
+
logger.debug "C: #{payload}"
|
69
|
+
options = if session.nil?
|
70
|
+
payload
|
71
|
+
else
|
72
|
+
payload.merge headers: { "arcadedb-session-id" => session }
|
73
|
+
end
|
74
|
+
post_data "document/#{database}", options
|
75
|
+
end
|
76
|
+
|
77
|
+
# ------------------------------ execute ------------------------------------------------- #
|
78
|
+
# executes a sql-query in the specified database
|
79
|
+
#
|
80
|
+
# the query is provided as block
|
81
|
+
#
|
82
|
+
# returns an Array of results (if propriate)
|
83
|
+
# i.e
|
84
|
+
# Arcade::Api.execute( "devel" ) { 'select from test ' }
|
85
|
+
# =y [{"@rid"=>"#57:0", "@type"=>"test", "name"=>"Hugo"}, {"@rid"=>"#60:0", "@type"=>"test", "name"=>"Hubert"}]
|
86
|
+
#
|
87
|
+
def self.execute database, query=nil, session_id= nil
|
88
|
+
pl = query.nil? ? provide_payload(yield) : provide_payload(query)
|
89
|
+
if session_id.nil? && session.nil?
|
90
|
+
post_data "command/#{database}" , pl
|
91
|
+
else
|
92
|
+
post_transaction "command/#{database}" , pl, session_id || session
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# ------------------------------ query ------------------------------------------------- #
|
97
|
+
# same for idempotent queries
|
98
|
+
def self.query database, query
|
99
|
+
post_data "query/#{database}" , provide_payload(query)
|
100
|
+
end
|
101
|
+
|
102
|
+
# ------------------------------ get_record ------------------------------------------------- #
|
103
|
+
# fetches a record by providing database and rid
|
104
|
+
# and returns the result as hash
|
105
|
+
#
|
106
|
+
# > Api.get_record 'devel', '225:6'
|
107
|
+
# > Api.get_record 'devel', 225, 6
|
108
|
+
# > Api.get_record 'devel', '#225:6'
|
109
|
+
# => {:@out=>0, :@rid=>"#225:6", :@in=>0, :@type=>"my_names", :name=>"Zaber", :@cat=>"v"}
|
110
|
+
|
111
|
+
def self.get_record database, *rid
|
112
|
+
rid = rid.join(':')
|
113
|
+
rid = rid[1..-1] if rid[0]=="#"
|
114
|
+
if rid.rid?
|
115
|
+
get_data "document/#{database}/#{rid}"
|
116
|
+
else
|
117
|
+
raise Error "Get requires a rid input"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# ------------------------------ property ------------------------------------------------- #
|
122
|
+
# Adds properties to the type
|
123
|
+
#
|
124
|
+
# call via
|
125
|
+
# Api.property <database>, <type>, name1: a_format , name2: a_format
|
126
|
+
#
|
127
|
+
# Format is one of
|
128
|
+
# Boolean, Integer, Short, Long, Float, Double, String
|
129
|
+
# Datetime, Binary, Byte, Decimal, Link
|
130
|
+
# Embedded, EmbeddedList, EmbeddedMap
|
131
|
+
#
|
132
|
+
# In case of an Error, anything is rolled back and nil is returned
|
133
|
+
#
|
134
|
+
def self.property database, type, **args
|
135
|
+
|
136
|
+
begin_transaction database
|
137
|
+
success = args.map do | name, format |
|
138
|
+
r= execute(database) {" create property #{type.to_s}.#{name.to_s} #{format.to_s} " } &.first
|
139
|
+
puts "R: #{r.inspect}"
|
140
|
+
if r.nil?
|
141
|
+
false
|
142
|
+
else
|
143
|
+
r[:operation] == 'create property'
|
144
|
+
end
|
145
|
+
end.uniq
|
146
|
+
if success == [true]
|
147
|
+
commit database
|
148
|
+
true
|
149
|
+
else
|
150
|
+
rollback database
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
# ------------------------------ index ------------------------------------------------- #
|
157
|
+
def self.index database, type, name , *properties
|
158
|
+
properties = properties.map( &:to_s )
|
159
|
+
unique_requested = "unique" if properties.delete("unique")
|
160
|
+
unique_requested = "notunique" if properties.delete("notunique" )
|
161
|
+
automatic = true if
|
162
|
+
properties << name if properties.empty?
|
163
|
+
# puts " create index #{type.to_s}[#{name.to_s}] on #{type} ( #{properties.join(',')} ) #{unique_requested}"
|
164
|
+
# VV 22.10: providing an index-name raises an Error ( Encountered " "(" "( "" at line 1, column 44. Was expecting one of: <EOF> <SCHEMA> ... <NULL_STRATEGY> ... ";" ... "," ... )) )
|
165
|
+
# named indices droped for now
|
166
|
+
success = execute(database) {" create index IF NOT EXISTS on #{type} (#{properties.join(', ')}) #{unique_requested}" } &.first
|
167
|
+
# puts "success: #{success}"
|
168
|
+
success[:operation] == 'create index'
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def self.logger
|
176
|
+
Database.logger
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.session
|
180
|
+
@session_id
|
181
|
+
end
|
182
|
+
|
183
|
+
def self. provide_payload( the_yield, action: :post )
|
184
|
+
unless the_yield.is_a? Hash
|
185
|
+
logger.info "Q: #{the_yield}"
|
186
|
+
the_yield = { :query => the_yield }
|
187
|
+
end
|
188
|
+
{ language: 'sql' }.merge(
|
189
|
+
the_yield.map do | key, value |
|
190
|
+
case key
|
191
|
+
when :query
|
192
|
+
action == :post ? [ :command, value ] : [ :query, value ]
|
193
|
+
when :limit
|
194
|
+
[ :limit , value ]
|
195
|
+
when :params
|
196
|
+
if value.is_a? Hash
|
197
|
+
[ :params, value ]
|
198
|
+
end
|
199
|
+
# serializer (optional) specify the serializer used for the result:
|
200
|
+
# graph: returns as a graph separating vertices from edges
|
201
|
+
# record: returns everything as records
|
202
|
+
# by default it’s like record but with additional metadata for vertex records,
|
203
|
+
# such as the number of outgoing edges in @out property and total incoming edges
|
204
|
+
# in @in property. This serialzier is used by Studio
|
205
|
+
when :serializer
|
206
|
+
if [:graph, :record].include? value.to_sym
|
207
|
+
[ :serializer, value.to_sym ]
|
208
|
+
end
|
209
|
+
when :language
|
210
|
+
if [:sql, :cypher, :gremlin, :neo4j ].include? value.to_sym
|
211
|
+
[ :language, value.to_sym ]
|
212
|
+
end
|
213
|
+
end # case
|
214
|
+
end .to_h ) # map
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
|
219
|
+
# returns the json-response ## retiered
|
220
|
+
def self.analyse_result r, command
|
221
|
+
if r.success?
|
222
|
+
return nil if r.status == 204 # no content
|
223
|
+
result = JSON.parse( r.response_body, symbolize_names: true )[:result]
|
224
|
+
if result == [{}]
|
225
|
+
[]
|
226
|
+
else
|
227
|
+
result
|
228
|
+
end
|
229
|
+
elsif r.timed_out?
|
230
|
+
raise Error "Timeout Error", caller
|
231
|
+
[]
|
232
|
+
elsif r.response_code > 0
|
233
|
+
logger.error "Execution Failure – Code: #{ r.response_code } – #{r.status_message} "
|
234
|
+
error_message = JSON.parse( r.response_body, symbolize_names: true )
|
235
|
+
logger.error "ErrorMessage: #{ error_message[:detail]} "
|
236
|
+
if error_message[:detail] =~ /Duplicated key/
|
237
|
+
raise IndexError, error_message[:detail]
|
238
|
+
else
|
239
|
+
# available fields: :detail, :exception, error
|
240
|
+
puts error_message[:detail]
|
241
|
+
#raise error_message[:detail], caller
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
def self.auth
|
246
|
+
@a ||= { httpauth: :basic,
|
247
|
+
username: Config.admin[:user],
|
248
|
+
password: Config.admin[:pass] }
|
249
|
+
end
|
250
|
+
|
251
|
+
# not tested
|
252
|
+
def self.delete_data command
|
253
|
+
result = HTTPX.delete Config.base_uri + command, auth
|
254
|
+
analyse_result(result, command)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Arcade
|
2
|
+
module Api
|
3
|
+
module Primitives
|
4
|
+
|
5
|
+
# This module handles the interaction with the database through HTTPX
|
6
|
+
#
|
7
|
+
# ------------------------------ http ------------------------------------------------------------ #
|
8
|
+
# persistent http handle to the database
|
9
|
+
|
10
|
+
def http
|
11
|
+
break_on = -> (response) { response.status == 500 }
|
12
|
+
@http ||= HTTPX.plugin(:basic_auth).basic_auth(auth[:username], auth[:password])
|
13
|
+
.plugin(:persistent)
|
14
|
+
.plugin(:circuit_breaker)
|
15
|
+
# .plugin(:circuit_breaker, circuit_breaker_break_on: break_on)
|
16
|
+
end
|
17
|
+
|
18
|
+
# ------------------------------ get data -------------------------------------------------------- #
|
19
|
+
def get_data command
|
20
|
+
response = http.get( Config.base_uri + command )
|
21
|
+
response.raise_for_status
|
22
|
+
|
23
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
24
|
+
# alternative to `raise for status `
|
25
|
+
# case response = http.basic_auth(auth[:username], auth[:password]).get( Config.base_uri + command )
|
26
|
+
# in { status: 200..203, body: }
|
27
|
+
# puts "success: #{JSON.parse(body, symbolize_names: true)[:result]}"
|
28
|
+
# in { status: 400..499, body: }
|
29
|
+
# puts "client error: #{body.json}"
|
30
|
+
# in { status: 500.., body: }
|
31
|
+
# puts "server error: #{body.to_s}"
|
32
|
+
# in { error: error }
|
33
|
+
# puts "error: #{error.message}"
|
34
|
+
# else
|
35
|
+
# raise "unexpected: #{response}"
|
36
|
+
# end
|
37
|
+
# puts "result : #{response}"
|
38
|
+
# puts "code: #{response.status}"
|
39
|
+
# analyse_result(response, command)
|
40
|
+
end
|
41
|
+
|
42
|
+
# ------------------------------ post data -------------------------------------------------------- #
|
43
|
+
def post_data command, payload
|
44
|
+
# http = HTTPX.plugin(:basic_auth)
|
45
|
+
# .basic_auth(auth[:username], auth[:password])
|
46
|
+
response = http.post( Config.base_uri + command, json: payload )
|
47
|
+
response.raise_for_status
|
48
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
49
|
+
end
|
50
|
+
|
51
|
+
# ------------------------------ transaction ------------------------------------------------- #
|
52
|
+
#
|
53
|
+
def begin_transaction database
|
54
|
+
result = http.post Config.base_uri + "begin/#{database}"
|
55
|
+
@session_id = result.headers["arcadedb-session-id"]
|
56
|
+
# returns the session-id
|
57
|
+
end
|
58
|
+
|
59
|
+
# ------------------------------ post transaction ------------------------------------------------- #
|
60
|
+
def post_transaction command, params, session_id= @session_id
|
61
|
+
# http = HTTPX.plugin(:basic_auth)
|
62
|
+
# .basic_auth(auth[:username], auth[:password])
|
63
|
+
# .with( headers: { "arcadedb-session-id"=>session }, debug_level: 1)
|
64
|
+
http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
|
65
|
+
response = http_a.post( Config.base_uri + command, json: params )
|
66
|
+
response.raise_for_status
|
67
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
# ------------------------------ commit ------------------------------------------------- #
|
72
|
+
def commit database, session_id = @session_id
|
73
|
+
http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
|
74
|
+
response = http_a.post( Config.base_uri + "commit/#{database}" )
|
75
|
+
response.raise_for_status
|
76
|
+
@session_id = nil
|
77
|
+
response.status # returns 204 --> success
|
78
|
+
# 403 --> incalid credentials
|
79
|
+
# 500 --> Transaction not begun
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# ------------------------------ rollback ------------------------------------------------- #
|
84
|
+
def rollback database, session_id = @session_id, publish_error=true
|
85
|
+
# http = HTTPX.plugin(:basic_auth)
|
86
|
+
# .basic_auth(auth[:username], auth[:password])
|
87
|
+
# .with( headers: { "arcadedb-session-id"=>session_id }, debug_level: 1)
|
88
|
+
http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
|
89
|
+
response = http_a.post( Config.base_uri + "rollback/#{database}" )
|
90
|
+
response.raise_for_status
|
91
|
+
@session_id = nil
|
92
|
+
logger.error "A Transaction has been rolled back" # if publish_error
|
93
|
+
response.status
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|