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
@@ -1,5 +1,6 @@
|
|
1
1
|
module Arcade
|
2
2
|
module Api
|
3
|
+
extend Primitives
|
3
4
|
=begin
|
4
5
|
This is a simple admin interface
|
5
6
|
|
@@ -8,7 +9,7 @@ module Arcade
|
|
8
9
|
$ Arcade::Api.drop_database <a string> # returns true if successfull
|
9
10
|
|
10
11
|
$ Arcade::Api.create_document <database>, <type>, attributes
|
11
|
-
$ Arcade::Api.execute( <database> ) { <query> }
|
12
|
+
$ Arcade::Api.execute( <database> [, session_id: some_session_id ]) { <query> }
|
12
13
|
$ Arcade::Api.query( <database> ) { <query> }
|
13
14
|
$ Arcade::Api.get_record <database>, rid # returns a hash
|
14
15
|
|
@@ -22,46 +23,53 @@ module Arcade
|
|
22
23
|
|
23
24
|
=end
|
24
25
|
|
26
|
+
# ------------------------------ Service methods ------------------------------------------------- #
|
27
|
+
# ------------------------------ ------------------------------------------------- #
|
28
|
+
# ------------------------------ databases ------------------------------------------------- #
|
29
|
+
# returns an array of databases present on the database-server #
|
25
30
|
|
26
31
|
def self.databases
|
27
32
|
get_data 'databases'
|
28
33
|
end
|
29
34
|
|
35
|
+
# ------------------------------ create database ------------------------------------------------- #
|
36
|
+
# creates a database if not present #
|
30
37
|
def self.create_database name
|
31
|
-
|
32
|
-
payload = { "command" => "create database #{name}" }
|
33
|
-
post_data "server",
|
34
|
-
|
35
|
-
rescue QueryError => e
|
38
|
+
return if databases.include?( name.to_s )
|
39
|
+
payload = { "command" => "create database #{name}" }
|
40
|
+
post_data "server", payload
|
41
|
+
rescue Arcade::QueryError => e
|
36
42
|
logger.fatal "Create database #{name} through \"POST create/#{name}\" failed"
|
37
43
|
logger.fatal e
|
38
44
|
raise
|
39
45
|
end
|
40
46
|
|
47
|
+
# ------------------------------ drop database ------------------------------------------------- #
|
48
|
+
# deletes the given database #
|
41
49
|
def self.drop_database name
|
42
|
-
|
43
|
-
payload = {
|
44
|
-
|
45
|
-
|
50
|
+
return unless databases.include?( name.to_s )
|
51
|
+
payload = {"command" => "drop database #{name}" }
|
52
|
+
post_data "server", payload
|
53
|
+
rescue Arcade::QueryError => e
|
54
|
+
logger.fatal "Drop database #{name} through \"POST drop database/#{name}\" failed"
|
55
|
+
raise
|
46
56
|
end
|
47
57
|
# ------------------------------ create document ------------------------------------------------- #
|
48
|
-
# adds a document to the database
|
58
|
+
# adds a document to the specified database table
|
49
59
|
#
|
50
60
|
# specify database-fields as hash-type parameters
|
51
61
|
#
|
52
62
|
# i.e Arcade::Api.create_document 'devel', 'documents', name: 'herta meyer', age: 56, sex: 'f'
|
53
63
|
#
|
54
64
|
# returns the rid of the inserted dataset
|
55
|
-
#
|
56
|
-
def self.create_document database, type, **attributes
|
57
|
-
payload = { "@type" => type }.merge( attributes )
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
post_data "document/#{database}", options
|
65
|
+
#
|
66
|
+
def self.create_document database, type, session_id: nil, **attributes
|
67
|
+
payload = { "@type" => type }.merge( attributes )
|
68
|
+
if session_id.nil?
|
69
|
+
post_data "document/#{database}", payload
|
70
|
+
else
|
71
|
+
post_transaction "document/#{database}", payload, session_id: session_id
|
72
|
+
end
|
65
73
|
end
|
66
74
|
|
67
75
|
# ------------------------------ execute ------------------------------------------------- #
|
@@ -71,32 +79,26 @@ module Arcade
|
|
71
79
|
#
|
72
80
|
# returns an Array of results (if propriate)
|
73
81
|
# i.e
|
74
|
-
# Arcade::Api.
|
82
|
+
# Arcade::Api.execute( "devel" ) { 'select from test ' }
|
75
83
|
# =y [{"@rid"=>"#57:0", "@type"=>"test", "name"=>"Hugo"}, {"@rid"=>"#60:0", "@type"=>"test", "name"=>"Hubert"}]
|
76
84
|
#
|
77
|
-
def self.execute database,
|
78
|
-
pl =
|
79
|
-
|
80
|
-
|
81
|
-
|
85
|
+
def self.execute database, session_id: nil
|
86
|
+
pl = provide_payload(yield)
|
87
|
+
if session_id.nil?
|
88
|
+
post_data "command/#{database}" , pl
|
89
|
+
else
|
90
|
+
post_transaction "command/#{database}" , pl, session_id: session_id
|
82
91
|
end
|
83
|
-
post_data "command/#{database}" , options
|
84
|
-
rescue Arcade::QueryError => e
|
85
|
-
# puts e.methods
|
86
|
-
#puts e.exception
|
87
|
-
# puts e.full_message
|
88
|
-
if e.message =~ /retry/
|
89
|
-
retry
|
90
|
-
else
|
91
|
-
raise e.message
|
92
|
-
end
|
93
92
|
end
|
94
93
|
|
95
94
|
# ------------------------------ query ------------------------------------------------- #
|
96
95
|
# same for idempotent queries
|
97
|
-
def self.query database, query
|
98
|
-
|
99
|
-
|
96
|
+
def self.query database, query, session_id: nil
|
97
|
+
if session_id.nil?
|
98
|
+
post_data "query/#{database}" , provide_payload(query)
|
99
|
+
else
|
100
|
+
post_transaction "query/#{database}" , provide_payload(query), session_id: session_id
|
101
|
+
end
|
100
102
|
end
|
101
103
|
|
102
104
|
# ------------------------------ get_record ------------------------------------------------- #
|
@@ -114,7 +116,7 @@ module Arcade
|
|
114
116
|
if rid.rid?
|
115
117
|
get_data "document/#{database}/#{rid}"
|
116
118
|
else
|
117
|
-
raise
|
119
|
+
raise Error "Get requires a rid input"
|
118
120
|
end
|
119
121
|
end
|
120
122
|
|
@@ -133,20 +135,17 @@ module Arcade
|
|
133
135
|
#
|
134
136
|
def self.property database, type, **args
|
135
137
|
|
136
|
-
begin_transaction database
|
138
|
+
s= begin_transaction database
|
137
139
|
success = args.map do | name, format |
|
138
|
-
r= execute(database) {" create property #{type.to_s}.#{name.to_s} #{format.to_s} " } &.first
|
139
|
-
|
140
|
-
false
|
141
|
-
else
|
142
|
-
r.keys == [ :propertyName, :typeName, :operation ] && r[:operation] == 'create property'
|
143
|
-
end
|
140
|
+
r= execute(database, session_id: s) {" create property #{type.to_s}.#{name.to_s} #{format.to_s} " } &.first
|
141
|
+
r.nil? ? false : r[:operation] == 'create property'
|
144
142
|
end.uniq
|
145
143
|
if success == [true]
|
146
|
-
commit database
|
144
|
+
commit database, session_id: s
|
147
145
|
true
|
148
146
|
else
|
149
|
-
rollback database
|
147
|
+
rollback database log: false, session_id: s
|
148
|
+
false
|
150
149
|
end
|
151
150
|
|
152
151
|
|
@@ -155,54 +154,23 @@ module Arcade
|
|
155
154
|
# ------------------------------ index ------------------------------------------------- #
|
156
155
|
def self.index database, type, name , *properties
|
157
156
|
properties = properties.map( &:to_s )
|
158
|
-
unique_requested = "unique" if properties.delete("unique")
|
157
|
+
unique_requested = "unique" if properties.delete("unique")
|
159
158
|
unique_requested = "notunique" if properties.delete("notunique" )
|
160
159
|
automatic = true if
|
161
160
|
properties << name if properties.empty?
|
162
|
-
|
163
|
-
# VV 22.10: providing an index-name raises an Error ( Encountered " "(" "( "" at line 1, column 44. Was expecting one of: <EOF> <SCHEMA> ... <NULL_STRATEGY> ... ";" ... "," ... )) )
|
164
|
-
# named indices droped for now
|
165
|
-
success = execute(database) {" create index on #{type} ( #{properties.join(',')} ) #{unique_requested}" } &.first
|
161
|
+
success = execute(database) {" create index IF NOT EXISTS on #{type} (#{properties.join(', ')}) #{unique_requested}" } &.first
|
166
162
|
# puts "success: #{success}"
|
167
|
-
|
168
|
-
|
169
|
-
end
|
170
|
-
|
163
|
+
success[:operation] == 'create index'
|
171
164
|
|
172
|
-
# ------------------------------ transaction ------------------------------------------------- #
|
173
|
-
#
|
174
|
-
def self.begin_transaction database
|
175
|
-
result = Typhoeus.post Arcade::Config.base_uri + "begin/#{database}", auth
|
176
|
-
@session_id = result.headers["arcadedb-session-id"]
|
177
|
-
|
178
|
-
# returns the session-id
|
179
165
|
end
|
180
166
|
|
181
167
|
|
182
|
-
# ------------------------------ commit ------------------------------------------------- #
|
183
|
-
def self.commit database
|
184
|
-
options = auth.merge( headers: { "arcadedb-session-id" => session })
|
185
|
-
post_data "commit/#{database}", options
|
186
|
-
@session_id = nil
|
187
|
-
end
|
188
|
-
|
189
|
-
# ------------------------------ rollback ------------------------------------------------- #
|
190
|
-
def self.rollback database, publish_error=true
|
191
|
-
options = auth.merge( headers: { "arcadedb-session-id" => session })
|
192
|
-
post_data "rollback/#{database}", options
|
193
|
-
@session_id = nil
|
194
|
-
raise Arcade::RollbackError "A Transaction has been rolled back" if publish_error
|
195
|
-
end
|
196
|
-
|
197
168
|
private
|
198
169
|
|
199
170
|
def self.logger
|
200
171
|
Database.logger
|
201
172
|
end
|
202
173
|
|
203
|
-
def self.session
|
204
|
-
@session_id
|
205
|
-
end
|
206
174
|
|
207
175
|
def self. provide_payload( the_yield, action: :post )
|
208
176
|
unless the_yield.is_a? Hash
|
@@ -231,76 +199,20 @@ module Arcade
|
|
231
199
|
[ :serializer, value.to_sym ]
|
232
200
|
end
|
233
201
|
when :language
|
234
|
-
if [:sql, :cypher, :gremlin, :neo4j ].include? value.to_sym
|
202
|
+
if [:sql, :cypher, :gremlin, :neo4j, :sqlscript, :graphql, :mongo ].include? value.to_sym
|
235
203
|
[ :language, value.to_sym ]
|
236
204
|
end
|
237
205
|
end # case
|
238
|
-
end .to_h )
|
239
|
-
end
|
240
|
-
|
241
|
-
def self.get_data command, options = auth
|
242
|
-
result = Typhoeus.get Arcade::Config.base_uri + command, options
|
243
|
-
analyse_result(result, command)
|
206
|
+
end .to_h ) # map
|
244
207
|
end
|
245
208
|
|
246
209
|
|
247
|
-
def self.post_data command, options = auth
|
248
|
-
# puts "Post DATA #{command} #{options}" # debug
|
249
|
-
i = 0; a=""
|
250
|
-
loop do
|
251
|
-
result = Typhoeus.post Arcade::Config.base_uri + command, options
|
252
|
-
# Code: 503 – Service Unavailable
|
253
|
-
if result.response_code.to_i == 503 # retry two times
|
254
|
-
i += 1
|
255
|
-
raise Arcade::QueryError, JSON.parse( result.response_body, symbolize_names: true )[:result] if i > 3
|
256
|
-
sleep 0.1
|
257
|
-
else
|
258
|
-
a= analyse_result(result, command )
|
259
|
-
break
|
260
|
-
end
|
261
|
-
end
|
262
|
-
a
|
263
|
-
end
|
264
210
|
|
265
|
-
# returns the json-response
|
266
|
-
def self.analyse_result r, command
|
267
|
-
if r.success?
|
268
|
-
return nil if r.response_code == 204 # no content
|
269
|
-
result = JSON.parse( r.response_body, symbolize_names: true )[:result]
|
270
|
-
if result == [{}]
|
271
|
-
[]
|
272
|
-
else
|
273
|
-
result
|
274
|
-
end
|
275
|
-
elsif r.timed_out?
|
276
|
-
raise Arcade::Error "Timeout Error", caller
|
277
|
-
[]
|
278
|
-
elsif r.response_code > 0
|
279
|
-
logger.error "Execution Failure – Code: #{ r.response_code } – #{r.status_message} "
|
280
|
-
error_message = JSON.parse( r.response_body, symbolize_names: true )
|
281
|
-
logger.error "ErrorMessage: #{ error_message[:detail]} "
|
282
|
-
if error_message[:detail] =~ /Duplicated key/
|
283
|
-
raise Arcade::IndexError, error_message[:detail]
|
284
|
-
else
|
285
|
-
# available fields: :detail, :exception, error
|
286
|
-
puts error_message[:detail]
|
287
|
-
#raise error_message[:detail], caller
|
288
|
-
end
|
289
|
-
end
|
290
|
-
end
|
291
211
|
def self.auth
|
292
212
|
@a ||= { httpauth: :basic,
|
293
|
-
username:
|
294
|
-
password:
|
213
|
+
username: Config.admin[:user],
|
214
|
+
password: Config.admin[:pass] }
|
295
215
|
end
|
296
216
|
|
297
|
-
def self.json
|
298
|
-
{ headers: { "Content-Type" => "application/json"} }
|
299
|
-
end
|
300
|
-
# not tested
|
301
|
-
def self.delete_data command
|
302
|
-
result = Typhoeus.delete Arcade::Config.base_uri + command, auth
|
303
|
-
analyse_result(result, command)
|
304
|
-
end
|
305
217
|
end
|
306
218
|
end
|
@@ -0,0 +1,126 @@
|
|
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
|
+
case response = http.get( Config.base_uri + command )
|
21
|
+
in {status: 200..299}
|
22
|
+
# success
|
23
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
24
|
+
in {status: 400..}
|
25
|
+
raise Arcade::QueryError.new **response.json( symbolize_names: true )
|
26
|
+
else
|
27
|
+
# # http error
|
28
|
+
raise response
|
29
|
+
end
|
30
|
+
|
31
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
32
|
+
# alternative to `raise for status `
|
33
|
+
# case response = http.basic_auth(auth[:username], auth[:password]).get( Config.base_uri + command )
|
34
|
+
# in { status: 200..203, body: }
|
35
|
+
# puts "success: #{JSON.parse(body, symbolize_names: true)[:result]}"
|
36
|
+
# in { status: 400..499, body: }
|
37
|
+
# puts "client error: #{body.json}"
|
38
|
+
# in { status: 500.., body: }
|
39
|
+
# puts "server error: #{body.to_s}"
|
40
|
+
# in { error: error }
|
41
|
+
# puts "error: #{error.message}"
|
42
|
+
# else
|
43
|
+
# raise "unexpected: #{response}"
|
44
|
+
# end
|
45
|
+
# puts "result : #{response}"
|
46
|
+
# puts "code: #{response.status}"
|
47
|
+
# analyse_result(response, command)
|
48
|
+
end
|
49
|
+
|
50
|
+
# ------------------------------ post data -------------------------------------------------------- #
|
51
|
+
def post_data command, payload
|
52
|
+
case response = http.post( Config.base_uri + command, json: payload )
|
53
|
+
in {status: 200..299}
|
54
|
+
# success
|
55
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
56
|
+
in {status: 400..}
|
57
|
+
detail = response.json( symbolize_names: true )[:detail]
|
58
|
+
if detail =~ /Please retry the operation/
|
59
|
+
logger.error "--------------------------------"
|
60
|
+
logger.error " ----> Operation repeated <---- "
|
61
|
+
logger.error detail
|
62
|
+
logger.error "The query --> #{payload.inspect}"
|
63
|
+
logger.error "--------------------------------"
|
64
|
+
sleep 1
|
65
|
+
post_data command, payload
|
66
|
+
else
|
67
|
+
raise Arcade::QueryError.new **response.json( symbolize_names: true )
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# # http error
|
71
|
+
raise response
|
72
|
+
end
|
73
|
+
# response.raise_for_status
|
74
|
+
end
|
75
|
+
|
76
|
+
# ------------------------------ transaction ------------------------------------------------- #
|
77
|
+
#
|
78
|
+
# The payload, optional as a JSON, accepts the following parameters:
|
79
|
+
# isolationLevel: READ_COMMITTED (default) or REPEATABLE_READ. (not implemented)
|
80
|
+
#
|
81
|
+
def begin_transaction database
|
82
|
+
result = http.post Config.base_uri + "begin/#{database}"
|
83
|
+
# returns the session-id
|
84
|
+
result.headers["arcadedb-session-id"]
|
85
|
+
end
|
86
|
+
|
87
|
+
# ------------------------------ post transaction ------------------------------------------------- #
|
88
|
+
def post_transaction command, params, session_id:
|
89
|
+
http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
|
90
|
+
case response = http_a.post( Config.base_uri + command, json: params )
|
91
|
+
in {status: 200..299}
|
92
|
+
# success
|
93
|
+
JSON.parse( response.body, symbolize_names: true )[:result]
|
94
|
+
in {status: 400..}
|
95
|
+
## debug
|
96
|
+
# puts "Command: #{command}"
|
97
|
+
# puts "params: #{params}"
|
98
|
+
# puts response.json( symbolize_names: true )
|
99
|
+
raise Arcade::QueryError.new **response.json( symbolize_names: true )
|
100
|
+
else
|
101
|
+
# # http error
|
102
|
+
raise response
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# ------------------------------ commit ------------------------------------------------- #
|
107
|
+
def commit database, session_id:
|
108
|
+
http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
|
109
|
+
response = http_a.post( Config.base_uri + "commit/#{database}" )
|
110
|
+
response.status # returns 204 --> success
|
111
|
+
# 403 --> invalid credentials
|
112
|
+
# 500 --> Transaction not begun
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
# ------------------------------ rollback ------------------------------------------------- #
|
117
|
+
def rollback database, session_id: , log: true
|
118
|
+
http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
|
119
|
+
response = http_a.post( Config.base_uri + "rollback/#{database}" )
|
120
|
+
logger.info "A Transaction has been rolled back" if log
|
121
|
+
response.status # returns 500 !
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|