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.
@@ -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
- unless databases.include?( name.to_s )
32
- payload = { "command" => "create database #{name}" }.to_json
33
- post_data "server", { body: payload }.merge( auth ).merge( json )
34
- end
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
- if databases.include?( name.to_s )
43
- payload = { "command" => "drop database #{name}" }.to_json
44
- post_data "server", { body: payload }.merge( auth ).merge( json )
45
- end
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 ).to_json
58
- logger.debug "C: #{payload}"
59
- options = if session.nil?
60
- { body: payload }.merge( auth ).merge( json )
61
- else
62
- { body: payload }.merge( auth ).merge( json ).merge( headers: { "arcadedb-session-id" => session })
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.execcute( "devel" ) { 'select from test ' }
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, query=nil
78
- pl = query.nil? ? provide_payload(yield) : provide_payload(query)
79
- options = { body: pl }.merge( auth ).merge( json )
80
- unless session.nil?
81
- options = options.merge( headers: { "arcadedb-session-id" => session })
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
- options = { body: provide_payload(query) }.merge( auth ).merge( json )
99
- post_data "query/#{database}" , options
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 Arcade::Error "Get requires a rid input"
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
- if r.nil?
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
- # puts " create index #{type.to_s}[#{name.to_s}] on #{type} ( #{properties.join(',')} ) #{unique_requested}"
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
- success && success.keys == [ :totalIndexed, :name, :operation ] && success[:operation] == 'create index'
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 ).to_json # map
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: Arcade::Config.admin[:user],
294
- password: Arcade::Config.admin[:pass] }
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
+