arcadedb 0.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ module Arcade
9
9
  $ Arcade::Api.drop_database <a string> # returns true if successfull
10
10
 
11
11
  $ Arcade::Api.create_document <database>, <type>, attributes
12
- $ Arcade::Api.execute( <database> ) { <query> }
12
+ $ Arcade::Api.execute( <database> [, session_id: some_session_id ]) { <query> }
13
13
  $ Arcade::Api.query( <database> ) { <query> }
14
14
  $ Arcade::Api.get_record <database>, rid # returns a hash
15
15
 
@@ -38,7 +38,7 @@ module Arcade
38
38
  return if databases.include?( name.to_s )
39
39
  payload = { "command" => "create database #{name}" }
40
40
  post_data "server", payload
41
- rescue HTTPX::HTTPError => e
41
+ rescue Arcade::QueryError => e
42
42
  logger.fatal "Create database #{name} through \"POST create/#{name}\" failed"
43
43
  logger.fatal e
44
44
  raise
@@ -50,7 +50,7 @@ module Arcade
50
50
  return unless databases.include?( name.to_s )
51
51
  payload = {"command" => "drop database #{name}" }
52
52
  post_data "server", payload
53
- rescue HTTPX::HTTPError => e
53
+ rescue Arcade::QueryError => e
54
54
  logger.fatal "Drop database #{name} through \"POST drop database/#{name}\" failed"
55
55
  raise
56
56
  end
@@ -63,15 +63,13 @@ module Arcade
63
63
  #
64
64
  # returns the rid of the inserted dataset
65
65
  #
66
- def self.create_document database, type, **attributes
66
+ def self.create_document database, type, session_id: nil, **attributes
67
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
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
75
73
  end
76
74
 
77
75
  # ------------------------------ execute ------------------------------------------------- #
@@ -84,19 +82,23 @@ module Arcade
84
82
  # Arcade::Api.execute( "devel" ) { 'select from test ' }
85
83
  # =y [{"@rid"=>"#57:0", "@type"=>"test", "name"=>"Hugo"}, {"@rid"=>"#60:0", "@type"=>"test", "name"=>"Hubert"}]
86
84
  #
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?
85
+ def self.execute database, session_id: nil
86
+ pl = provide_payload(yield)
87
+ if session_id.nil?
90
88
  post_data "command/#{database}" , pl
91
89
  else
92
- post_transaction "command/#{database}" , pl, session_id || session
90
+ post_transaction "command/#{database}" , pl, session_id: session_id
93
91
  end
94
92
  end
95
93
 
96
94
  # ------------------------------ query ------------------------------------------------- #
97
95
  # same for idempotent queries
98
- def self.query database, query
99
- post_data "query/#{database}" , provide_payload(query)
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 ------------------------------------------------- #
@@ -133,21 +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
- puts "R: #{r.inspect}"
140
- if r.nil?
141
- false
142
- else
143
- r[:operation] == 'create property'
144
- 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'
145
142
  end.uniq
146
143
  if success == [true]
147
- commit database
144
+ commit database, session_id: s
148
145
  true
149
146
  else
150
- rollback database
147
+ rollback database log: false, session_id: s
148
+ false
151
149
  end
152
150
 
153
151
 
@@ -160,9 +158,6 @@ module Arcade
160
158
  unique_requested = "notunique" if properties.delete("notunique" )
161
159
  automatic = true if
162
160
  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
161
  success = execute(database) {" create index IF NOT EXISTS on #{type} (#{properties.join(', ')}) #{unique_requested}" } &.first
167
162
  # puts "success: #{success}"
168
163
  success[:operation] == 'create index'
@@ -176,9 +171,6 @@ module Arcade
176
171
  Database.logger
177
172
  end
178
173
 
179
- def self.session
180
- @session_id
181
- end
182
174
 
183
175
  def self. provide_payload( the_yield, action: :post )
184
176
  unless the_yield.is_a? Hash
@@ -207,7 +199,7 @@ module Arcade
207
199
  [ :serializer, value.to_sym ]
208
200
  end
209
201
  when :language
210
- if [:sql, :cypher, :gremlin, :neo4j ].include? value.to_sym
202
+ if [:sql, :cypher, :gremlin, :neo4j, :sqlscript, :graphql, :mongo ].include? value.to_sym
211
203
  [ :language, value.to_sym ]
212
204
  end
213
205
  end # case
@@ -216,42 +208,11 @@ module Arcade
216
208
 
217
209
 
218
210
 
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
211
  def self.auth
246
212
  @a ||= { httpauth: :basic,
247
213
  username: Config.admin[:user],
248
214
  password: Config.admin[:pass] }
249
215
  end
250
216
 
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
217
  end
257
218
  end
@@ -8,7 +8,7 @@ module Arcade
8
8
  # persistent http handle to the database
9
9
 
10
10
  def http
11
- break_on = -> (response) { response.status == 500 }
11
+ # break_on = -> (response) { response.status == 500 }
12
12
  @http ||= HTTPX.plugin(:basic_auth).basic_auth(auth[:username], auth[:password])
13
13
  .plugin(:persistent)
14
14
  .plugin(:circuit_breaker)
@@ -17,8 +17,16 @@ module Arcade
17
17
 
18
18
  # ------------------------------ get data -------------------------------------------------------- #
19
19
  def get_data command
20
- response = http.get( Config.base_uri + command )
21
- response.raise_for_status
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
22
30
 
23
31
  JSON.parse( response.body, symbolize_names: true )[:result]
24
32
  # alternative to `raise for status `
@@ -41,56 +49,76 @@ module Arcade
41
49
 
42
50
  # ------------------------------ post data -------------------------------------------------------- #
43
51
  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]
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
49
74
  end
50
75
 
51
76
  # ------------------------------ transaction ------------------------------------------------- #
52
77
  #
78
+ # The payload, optional as a JSON, accepts the following parameters:
79
+ # isolationLevel: READ_COMMITTED (default) or REPEATABLE_READ. (not implemented)
80
+ #
53
81
  def begin_transaction database
54
82
  result = http.post Config.base_uri + "begin/#{database}"
55
- @session_id = result.headers["arcadedb-session-id"]
56
83
  # returns the session-id
84
+ result.headers["arcadedb-session-id"]
57
85
  end
58
86
 
59
87
  # ------------------------------ 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)
88
+ def post_transaction command, params, session_id:
64
89
  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
-
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
69
104
  end
70
105
 
71
106
  # ------------------------------ commit ------------------------------------------------- #
72
- def commit database, session_id = @session_id
107
+ def commit database, session_id:
73
108
  http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
74
109
  response = http_a.post( Config.base_uri + "commit/#{database}" )
75
- response.raise_for_status
76
- @session_id = nil
77
110
  response.status # returns 204 --> success
78
- # 403 --> incalid credentials
111
+ # 403 --> invalid credentials
79
112
  # 500 --> Transaction not begun
80
113
 
81
114
  end
82
115
 
83
116
  # ------------------------------ 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)
117
+ def rollback database, session_id: , log: true
88
118
  http_a = http.with( headers: { "arcadedb-session-id" => session_id } , debug_level: 1)
89
119
  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
120
+ logger.info "A Transaction has been rolled back" if log
121
+ response.status # returns 500 !
94
122
  end
95
123
  end
96
124
  end
data/lib/arcade/base.rb CHANGED
@@ -27,6 +27,16 @@ module Arcade
27
27
  self.name.snake_case
28
28
  end
29
29
 
30
+ def begin_transaction
31
+ db.begin_transaction
32
+ end
33
+ def commit
34
+ db.commit
35
+ end
36
+ def rollback
37
+ db.rollback
38
+ end
39
+
30
40
  def create_type
31
41
  the_class = nil # declare as local var
32
42
  parent_present = ->(cl){ db.hierarchy.flatten.include? cl }
@@ -67,7 +77,7 @@ module Arcade
67
77
  the_command = command[0 .. -2] # remove '\n'
68
78
  next if the_command == ''
69
79
  # db.logger.info "Custom Setup:: #{the_command}"
70
- db.execute { the_command }
80
+ db.transmit { the_command }
71
81
  end unless custom_setup.nil?
72
82
 
73
83
  rescue RollbackError => e
@@ -118,9 +128,11 @@ module Arcade
118
128
  # (not supported (jet): [RETURN <expression>] [FROM <query>] )
119
129
 
120
130
  def insert **attributes
121
- db.insert type: database_name, **attributes
131
+ db.insert type: database_name, session_id: attributes.delete(:session_id), **attributes
122
132
  end
123
133
 
134
+ alias create insert
135
+
124
136
  ## ----------------------------------------- create ---------------------------------- ##
125
137
  #
126
138
  # Adds a record to the database
@@ -128,21 +140,21 @@ module Arcade
128
140
  # returns the model dataset
129
141
  # ( depreciated )
130
142
 
131
- def create **attributes
132
- s = Api.begin_transaction db.database
133
- attributes.merge!( created: DateTime.now ) if timestamps
134
- record = insert **attributes
135
- Api.commit db.database, s
136
- record
137
- rescue QueryError => e
138
- db.logger.error "Dataset NOT created"
139
- db.logger.error "Provided Attributes: #{ attributes.inspect }"
140
- # Api.rollback db.database ---> raises "transactgion not begun"
141
- rescue Dry::Struct::Error => e
142
- Api.rollback db.database
143
- db.logger.error "#{ rid } :: Validation failed, record deleted."
144
- db.logger.error e.message
145
- end
143
+ # def create **attributes
144
+ # s = Api.begin_transaction db.database
145
+ # attributes.merge!( created: DateTime.now ) if timestamps
146
+ # record = insert **attributes
147
+ # Api.commit db.database, s
148
+ # record
149
+ # rescue HTTPX::HTTPError => e
150
+ # db.logger.error "Dataset NOT created"
151
+ # db.logger.error "Provided Attributes: #{ attributes.inspect }"
152
+ # Api.rollback db.database # ---> raises "transactgion not begun"
153
+ # rescue Dry::Struct::Error => e
154
+ # Api.rollback db.database
155
+ # db.logger.error "#{ rid } :: Validation failed, record deleted."
156
+ # db.logger.error e.message
157
+ # end
146
158
 
147
159
  def count **args
148
160
  command = "count(*)"
@@ -219,9 +231,9 @@ module Arcade
219
231
  # is equivalent to
220
232
  # Strategie.all.find{|y| y.symbol == 'Still' }
221
233
  def find **args
222
- f= where(**args).first
223
- f= where( "#{ args.keys.first } like #{ args.values.first.to_or }" ).first if f.nil? || f.empty?
224
- f
234
+ where(**args).first
235
+ # f= where( "#{ args.keys.first } like #{ args.values.first.to_or }" ).first if f.nil? || f.empty?
236
+ # f
225
237
  end
226
238
  # update returns a list of updated records
227
239
  #
@@ -367,6 +379,9 @@ module Arcade
367
379
  end
368
380
  "%s : %s" % [ attr, v] unless v.nil?
369
381
  end.compact.sort.join(', ') + ">".gsub('"' , ' ')
382
+
383
+ rescue TypeError => e
384
+ attributes
370
385
  end
371
386
 
372
387
 
@@ -376,23 +391,41 @@ module Arcade
376
391
  to_human
377
392
  end
378
393
 
394
+
395
+ def html_attributes
396
+ invariant_attributes
397
+ end
398
+
399
+ def in_and_out_attributes
400
+ _modul, _class = self.class.to_s.split "::"
401
+ the_class = _modul == 'Arcade' ? _class : self.class.to_s
402
+ the_attributes = { :"CLASS" => the_class, :"IN" => self.in.count, :"OUT" => self.out.count, :"RID" => rid }
403
+ end
404
+
379
405
  def to_html # iruby
406
+ in_and_out = ->(r) { "[#{r}] : {#{self.in.count}->}{->#{self.out.count }}" }
407
+ the_rid = rid? && rid != "0:0" ? in_and_out[rid] : ""
380
408
  _modul, _class = self.class.to_s.split "::"
381
409
  the_class = _modul == 'Arcade' ? _class : self.class.to_s
382
- IRuby.display IRuby.html "<b style=\"color: #50953DFF\"><#{ the_class}</b>"
383
- + rid? ? "[#{ rid }]: " : " " + invariant_attributes.map do |attr, value|
384
- v= case value
385
- when Base
386
- "< #{ self.class.to_s.snake_case }: #{ value.rid } >"
387
- when Array
388
- value.map{|x| x.to_s}
389
- else
390
- value.from_db
391
- end
392
- "%s : %s" % [ attr, v] unless v.nil?
393
- end.compact.sort.join(', ') + ">".gsub('"' , ' ')
410
+ # the_attribute = ->(v) do
411
+ # case v
412
+ # when Base
413
+ # "< #{ self.class.to_s.snake_case }: #{ v.rid } >"
414
+ # when Array
415
+ # v.map{|x| x.to_s}
416
+ # else
417
+ # v.to_s
418
+ # end
419
+ # end
420
+ # last_part = invariant_attributes.map do |attr, value|
421
+ # [ attr, the_attribute[value] ].join(": ")
422
+ # end.join(', ')
423
+
424
+ # IRuby.display( [IRuby.html("<span style=\"color: #50953DFF\"><b>#{the_class}</b><#{ the_class }</b>#{the_rid}</span><br/> ") , IRuby.table(html_attributes) ] )
425
+ IRuby.display IRuby.html("<span style=\"color: #50953DFF\"><b>#{the_class}</b><#{ the_class }</b>#{the_rid}</span>< #{ html_attributes.map{|_,v| v }.join(', ') } >")
394
426
  end
395
427
 
428
+
396
429
  def update **args
397
430
  Query.new( from: rid , kind: :update, set: args).execute
398
431
  refresh
@@ -406,13 +439,13 @@ module Arcade
406
439
  obj.to_or
407
440
  end
408
441
  # if send( name ).nil? || send( name ).empty?
409
- db.execute { "update #{ rid } set #{ name } = #{ value }" }.first[:count]
442
+ db.transmit { "update #{ rid } set #{ name } = #{ value }" }.first[:count]
410
443
  # end
411
444
  end
412
445
 
413
446
  # updates a single property in an embedded document
414
447
  def update_embedded embedded, embedded_property, value
415
- db.execute{ " update #{rid} set `#{embedded}`.`#{embedded_property}` = #{value.to_or}" }
448
+ db.transmit { " update #{rid} set `#{embedded}`.`#{embedded_property}` = #{value.to_or}" }
416
449
  end
417
450
 
418
451
  def update_list list, value
@@ -422,9 +455,9 @@ module Arcade
422
455
  value.to_or
423
456
  end
424
457
  if send( list ).nil? || send( list ).empty?
425
- db.execute { "update #{ rid } set #{ list } = [#{ value }]" }
458
+ db.transmit { "update #{ rid } set #{ list } = [#{ value }]" }
426
459
  else
427
- db.execute { "update #{ rid } set #{ list } += #{ value }" }
460
+ db.transmit { "update #{ rid } set #{ list } += #{ value }" }
428
461
  end
429
462
  refresh
430
463
  end
@@ -432,14 +465,14 @@ module Arcade
432
465
  # updates a map property , actually adds the key-value pair to the property
433
466
  def update_map m, key, value
434
467
  if send( m ).nil?
435
- db.execute { "update #{ rid } set #{ m } = MAP ( #{ key.to_s.to_or } , #{ value.to_or } ) " }
468
+ db.transmit { "update #{ rid } set #{ m } = MAP ( #{ key.to_s.to_or } , #{ value.to_or } ) " }
436
469
  else
437
- db.execute { "update #{ rid } set #{ m }.`#{ key.to_s }` = #{ value.to_or }" }
470
+ db.transmit { "update #{ rid } set #{ m }.`#{ key.to_s }` = #{ value.to_or }" }
438
471
  end
439
472
  refresh
440
473
  end
441
474
  def delete
442
- response = db.execute { "delete from #{ rid }" }
475
+ response = db.transmit { "delete from #{ rid }" }
443
476
  true if response == [{ count: 1 }]
444
477
  end
445
478
  def == arg
@@ -3,11 +3,11 @@ module Arcade
3
3
  extend Dry::Configurable
4
4
  # central place to initialize constants
5
5
  #
6
- # ProjectRoot has to be a Pathname-Object
6
+ # ProjectRoot should to be a Pathname-Object and has to be defined before `arcadedb` is required
7
7
  #
8
8
  #puts "expand: #{File.expand_path(__dir__)}"
9
- unless Arcade.const_defined?( :ProjectRoot )
10
- Arcade::ProjectRoot = if defined?( Rails.env )
9
+ unless Object.const_defined?( :ProjectRoot )
10
+ ::ProjectRoot = if defined?( Rails.env )
11
11
  Rails.root
12
12
  else
13
13
  STDERR.puts "Using default (arcadedb gem) database credentials and settings"
@@ -15,10 +15,9 @@ module Arcade
15
15
  Pathname.new( File.expand_path( "../../", __FILE__ ))
16
16
  end
17
17
  else
18
- STDERR.puts "Using provided database credentials and settings fron #{Arcade::ProjectRoot}"
18
+ STDERR.puts "Using provided database credentials and settings from #{::ProjectRoot}"
19
19
  end
20
20
 
21
-
22
21
  # initialised a hash { environment => property }
23
22
  setting :username, default: :user, reader: true,
24
23
  constructor: ->(v) { yml(:environment).map{|x,y| [x , y[v.to_s]] }.to_h }
@@ -53,7 +52,7 @@ module Arcade
53
52
  def self.config_file
54
53
 
55
54
  configdir = -> do
56
- pr = ProjectRoot.is_a?(Pathname)? ProjectRoot : Pathname.new( ProjectRoot )
55
+ pr = ::ProjectRoot.is_a?(Pathname)? ::ProjectRoot : Pathname.new( ::ProjectRoot )
57
56
  ( cd = pr + 'arcade.yml' ).exist? || ( cd = pr + 'config' + 'arcade.yml' ).exist? || ( cd = pr + 'config.yml' )
58
57
  cd
59
58
  end