arcadedb 0.3.1 → 0.3.3

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.
@@ -0,0 +1,338 @@
1
+ module Arcade
2
+ ##
3
+ # Implements the Database-Adapter
4
+ #
5
+ # currently, only attributes of type String are supported
6
+ #
7
+ # {Database-instance}.database points to the connected Aradedb-database
8
+ # DB.hi
9
+ #
10
+ ##
11
+ class Database
12
+ include Logging
13
+ extend Dry::Core::ClassAttributes
14
+ include Support::Model # provides allocate_model
15
+
16
+ defines :namespace
17
+ defines :environment
18
+
19
+ def initialize environment=:development
20
+ self.class.configure_logger( Config.logger )
21
+ @connection = connect environment
22
+ if self.class.environment.nil? # class attribute is set on the first call
23
+ # further instances of Database share the same environment
24
+ self.class.environment environment
25
+ end
26
+ self.class.namespace Object.const_get( Config.namespace )
27
+ end
28
+
29
+ def database
30
+ @database ||= Config.database[self.class.environment]
31
+ end
32
+
33
+ # ------------ types ------------...
34
+ # returns an Array of type-attributes
35
+ # [{:name=>"Account", :type=>"document"},
36
+ # {:name=>"test", :type=>"vertex"},
37
+ # {:name=>"test1", :type=>"vertex"},
38
+ # {:parentTypes=>["test1"], :name=>"test2", :type=>"vertex"}]
39
+ #
40
+ def types refresh=false
41
+ # uses API
42
+ if $types.nil? || refresh
43
+ $types = Api.query(database, "select from schema:types" )
44
+ .map{ |x| x.transform_keys &:to_sym } # symbolize keys
45
+ .map{ |y| y.delete_if{|_,b,| b.empty? } } # eliminate empty entries
46
+ end
47
+ $types
48
+ ## upom startup, this is the first access to the database-server
49
+ rescue NoMethodError => e
50
+ logger.fatal "Could not read Database Types. \n Is the database running?"
51
+ Kernel.exit
52
+ end
53
+
54
+ def indexes
55
+ DB.types.find{|x| x.key? :indexes }[:indexes]
56
+ end
57
+
58
+ # ------------ hierarchy -------------
59
+ # returns an Array of types
60
+ #
61
+ # each entry is an Array
62
+ # => [["test"], ["test1", "test2"]] (using the example above)
63
+ #
64
+ # Parameter: type -- one of 'vertex', 'document', 'edge'
65
+ def hierarchy type: 'vertex'
66
+ # uses API
67
+ # gets all types depending on the parent-type
68
+ pt = ->( s ) { types.find_all{ |x| x[:parentTypes] &.include?(s) }.map{ |v| v[:name] } }
69
+ # takes an array of base-types. gets recursivly all childs
70
+ child_types = -> (base_types) do
71
+ base_types.map do | bt |
72
+ if pt[ bt ].empty?
73
+ [ bt ]
74
+ else
75
+ [bt, child_types[ pt[ bt ] ] ].flatten
76
+ end
77
+ end
78
+ end
79
+
80
+ # gets child-types for all base-types
81
+ child_types[ types.find_all{ |x| !x[:parentTypes] && x[:type] == type.to_s }.map{ |v| v[:name] } ]
82
+
83
+ end
84
+
85
+
86
+ # ------------ create type -----------
87
+ # returns an Array
88
+ # Example: > create_type :vertex, :my_vertex
89
+ # => [{"typeName"=>"my_vertex", "operation"=>"create vertex type"}]
90
+ #
91
+ # takes additional arguments: extends: '<a supertype>' (for inheritance)
92
+ # bucket: <a list of bucket-id's >
93
+ # buckets: <how many bukets to assign>
94
+ #
95
+ # additional arguments are just added to the command
96
+ #
97
+ # its aliased as `create_class`
98
+ #
99
+ def create_type kind, type, **args
100
+
101
+ exe = -> do
102
+ case kind.to_s.downcase
103
+ when /^v/
104
+ "create vertex type #{type} "
105
+ when /^d/
106
+ "create document type #{type} "
107
+ when /^e/
108
+ "create edge type #{type} "
109
+ end.concat( args.map{|x,y| "#{x} #{y} "}.join)
110
+ end
111
+ db= Api.execute database, &exe
112
+ types( true ) # update cached schema
113
+ db
114
+
115
+ rescue Arcade::QueryError
116
+ Arcade::Database.logger.warn "Database type #{type} already present"
117
+ end
118
+
119
+ alias create_class create_type
120
+
121
+ # ------------ drop type -----------
122
+ # delete any record prior to the attempt to drop a type.
123
+ # The `unsafe` option is nit implemented.
124
+ def drop_type type
125
+ Api.execute database, "drop type #{type} if exists"
126
+ end
127
+
128
+ # ------------ create -----------
129
+ # returns an rid of the successfully created vertex or document
130
+ #
131
+ # Parameter: name of the vertex or document type
132
+ # Hash of attributes
133
+ #
134
+ # Example: > DB.create :my_vertex, a: 14, name: "Hugo"
135
+ # => "#177:0"
136
+ #
137
+ def create type, **params
138
+ # uses API
139
+ Api.create_document database, type, **params
140
+ end
141
+
142
+ def insert **params
143
+
144
+ content_params = params.except( :type, :bucket, :index, :from, :return )
145
+ target_params = params.slice( :type, :bucket, :index )
146
+ if target_params.empty?
147
+ logger.error "Could not insert: target mising (type:, bucket:, index:)"
148
+ elsif content_params.empty?
149
+ logger.error "Nothing to Insert"
150
+ else
151
+ content = "CONTENT #{ content_params.to_json }"
152
+ target = target_params.map{|y,z| y==:type ? z : "#{y.to_s} #{ z } "}.join
153
+ Api.execute( database, "INSERT INTO #{target} #{content} ") &.first.allocate_model(false)
154
+ end
155
+ end
156
+
157
+ # ------------------------------ get ------------------------------------------------------ #
158
+ # Get fetches the record associated with the rid given as parameter.
159
+ #
160
+ # The rid is accepted as
161
+ # DB.get "#123:123", DB.get "123:123" or DB.get 123, 123
162
+ #
163
+ # Links are autoloaded (can be suppressed by the optional Block (false))
164
+ #
165
+ # puts DB.get( 19,0 )
166
+ # <my_document[#19:0]: emb : ["<my_alist[#33:0]: name : record 1, number : 1>", "<my_alist[#34:0]: name : record 2, number : 2>"]>
167
+ # puts DB.get( 19,0 ){ false }
168
+ # <my_document[#19:0]: emb : ["#33:0", "#34:0"]>
169
+ #
170
+ #
171
+ def get *rid
172
+ autocomplete = block_given? ? yield : true
173
+ rid = rid.join(':')
174
+ rid = rid[1..-1] if rid[0]=="#"
175
+ if rid.rid?
176
+ Api.query( database, "select from #{rid}" ).first &.allocate_model(autocomplete)
177
+ else
178
+ raise Arcade::QueryError "Get requires a rid input", caller
179
+ end
180
+ end
181
+
182
+ # ------------------------------ property ------------------------------------------------- #
183
+ # Adds properties to the type
184
+ #
185
+ # call via
186
+ # Api.property <database>, <type>, name1: a_format , name2: a_format
187
+ #
188
+ # Format is one of
189
+ # Boolean, Integer, Short, Long, Float, Double, String
190
+ # Datetime, Binary, Byte, Decimal, Link
191
+ # Embedded, EmbeddedList, EmbeddedMap
192
+ #
193
+ # In case of an Error, anything is rolled back and nil is returned
194
+ #
195
+ def self.property database, type, **args
196
+
197
+ begin_transaction database
198
+ success = args.map do | name, format |
199
+ r= execute(database) {" create property #{type.to_s}.#{name.to_s} #{format.to_s} " } &.first
200
+ if r.nil?
201
+ false
202
+ else
203
+ r.keys == [ :propertyName, :typeName, :operation ] && r[:operation] == 'create property'
204
+ end
205
+ end.uniq
206
+ if success == [true]
207
+ commit database
208
+ true
209
+ else
210
+ rollback database
211
+ end
212
+
213
+
214
+ end
215
+
216
+ # ------------------------------ index ------------------------------------------------- #
217
+ def self.index database, type, name , *properties
218
+ properties = properties.map( &:to_s )
219
+ unique_requested = "unique" if properties.delete("unique")
220
+ unique_requested = "notunique" if properties.delete("notunique" )
221
+ automatic = true if
222
+ properties << name if properties.empty?
223
+ end
224
+
225
+ def delete rid
226
+ r = Api.execute( database ){ "delete from #{rid}" }
227
+ success = r == [{ :count => 1 }]
228
+ end
229
+
230
+ # execute a command which modifies the database
231
+ #
232
+ # The operation is performed via Transaction/Commit
233
+ # If an Error occurs, its rolled back
234
+ #
235
+ def execute &block
236
+ Api.begin_transaction database
237
+ response = Api.execute database, &block
238
+ # puts response.inspect # debugging
239
+ r= if response.is_a? Hash
240
+ _allocate_model res
241
+ # elsif response.is_a? Array
242
+ # remove empty results
243
+ # response.delete_if{|y| y.empty?}
244
+ # response.map do | res |
245
+ # if res.key? :"@rid"
246
+ # allocate_model res
247
+ # else
248
+ # res
249
+ # end
250
+ # end
251
+ else
252
+ response
253
+ end
254
+ Api.commit database
255
+ r # return associated array of Arcade::Base-objects
256
+ rescue Dry::Struct::Error, Arcade::QueryError => e
257
+ Api.rollback database
258
+ logger.error "Execution FAILED --> #{e.exception}"
259
+ [] # return empty result
260
+ end
261
+
262
+ # returns an array of results
263
+ #
264
+ # detects database-records and allocates them as model-objects
265
+ #
266
+ def query query_object
267
+ Api.query database, query_object.to_s
268
+ end
269
+
270
+ # returns an array of rid's (same logic as create)
271
+ def create_edge edge_class, from:, to:, **attributes
272
+
273
+ content = attributes.empty? ? "" : "CONTENT #{attributes.to_json}"
274
+ cr = ->( f, t ) do
275
+ edges = Api.execute( database, "create edge #{edge_class} from #{f.rid} to #{t.rid} #{content}").allocate_model(false)
276
+ #else
277
+ # logger.error "Could not create Edge #{edge_class} from #{f} to #{t}"
278
+ ## logger.error edges.to_s
279
+ #end
280
+ end
281
+ from = [from] unless from.is_a? Array
282
+ to = [to] unless to.is_a? Array
283
+
284
+ from.map do | from_record |
285
+ to.map { | to_record | cr[ from_record, to_record ] if to_record.rid? } if from_record.rid?
286
+ end.flatten
287
+
288
+ end
289
+
290
+
291
+ # query all: select @rid, * from {database}
292
+
293
+ # not used
294
+ # def get_schema
295
+ # query( "select from schema:types" ).map do |a|
296
+ # puts "a: #{a}"
297
+ # class_name = a["name"]
298
+ # inherent_class = a["parentTypes"].empty? ? [Object,nil] : a["parentTypes"].map(&:camelcase_and_namespace)
299
+ # namespace, type_name = a["type"].camelcase_and_namespace
300
+ # namespace= Arcade if namespace.nil?
301
+ # klass= Dry::Core::ClassBuilder.new( name: type_name,
302
+ # parent: nil,
303
+ # namespace: namespace).call
304
+ # end
305
+ # rescue NameError
306
+ # logger.error "Dataset type #{e} not defined."
307
+ # raise
308
+ # end
309
+ # Postgres is not implemented
310
+ # connects to the database and initialises @connection
311
+ def connection
312
+ @connection
313
+ end
314
+
315
+ def connect environment=:development # environments: production devel test
316
+ if [:production, :development, :test].include? environment
317
+
318
+ # connect through the ruby postgres driver
319
+ # c= PG::Connection.new dbname: Config.database[environment],
320
+ # user: Config.username[environment],
321
+ # password: Config.password[environment],
322
+ # host: Config.pg[:host],
323
+ # port: Config.pg[:port]
324
+ #
325
+ end
326
+ rescue PG::ConnectionBad => e
327
+ if e.to_s =~ /Credentials/
328
+ logger.error "NOT CONNECTED ! Either Database is not present or credentials (#{ Config.username[environment]} / #{Config.password[environment]}) are wrong"
329
+ nil
330
+ else
331
+ raise
332
+ end
333
+ end # def
334
+
335
+
336
+
337
+ end # class
338
+ end # module
@@ -0,0 +1,71 @@
1
+ module Arcade
2
+
3
+ # Error handling
4
+ class Error < RuntimeError
5
+ end
6
+
7
+ class ArgumentError < ArgumentError
8
+ end
9
+
10
+ class SymbolError < ArgumentError
11
+ end
12
+
13
+ class LoadError < LoadError
14
+ end
15
+
16
+ class ImmutableError < RuntimeError
17
+ end
18
+ class IndexError < RuntimeError
19
+ end
20
+
21
+ class RollbackError < RuntimeError
22
+ end
23
+
24
+ class QueryError < RuntimeError
25
+ end
26
+
27
+ # used by Dry::Validation, not covered by "error"
28
+ class InvalidParamsError < StandardError
29
+ attr_reader :object
30
+ # @param [Hash] object that contains details about params errors.
31
+ # # @param [String] message of the error.
32
+ def initialize(object, message)
33
+ @object = object
34
+ super(message)
35
+ end
36
+ end
37
+
38
+ end # module Arcade
39
+
40
+ # Patching Object with universally accessible top level error method.
41
+ # The method is used throughout the lib instead of plainly raising exceptions.
42
+ # This allows lib user to easily inject user-specific error handling into the lib
43
+ # by just replacing Object#error method.
44
+ #def error message, type=:standard, backtrace=nil
45
+ # e = case type
46
+ # when :standard
47
+ # Arcade::Error.new message
48
+ # when :args
49
+ # Arcade::ArgumentError.new message
50
+ # when :symbol
51
+ # Arcade::SymbolError.new message
52
+ # when :load
53
+ # Arcade::LoadError.new message
54
+ # when :immutable
55
+ # Arcade::ImmutableError.new message
56
+ # when :commit
57
+ # Arcade::RollbackError.new message
58
+ # when :query
59
+ # Arcade::QueryError.new message
60
+ # when :args
61
+ # IB::ArgumentError.new message
62
+ # when :flex
63
+ # IB::FlexError.new message
64
+ # when :reader
65
+ # IB::TransmissionError.new message
66
+ # when :verify
67
+ # IB::VerifyError.new message
68
+ # end
69
+ # e.set_backtrace(backtrace) if backtrace
70
+ # raise e
71
+ #end
@@ -0,0 +1,38 @@
1
+ #require_relative 'default_formatter'
2
+ module Arcade
3
+ module Logging
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.send :define_method, :logger do
7
+ base.logger
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def logger
13
+ @logger
14
+ end
15
+
16
+ def logger=(logger)
17
+ @logger = logger
18
+ end
19
+
20
+ def configure_logger(log= STDOUT)
21
+ if log.is_a? Logger
22
+ @logger = log
23
+ else
24
+ @logger = Logger.new log
25
+ end
26
+ @logger.level = Logger::INFO
27
+ @logger.formatter = DefaultFormatter
28
+ end
29
+ end
30
+
31
+ class DefaultFormatter < Logger::Formatter
32
+ def self.call(severity, time, program_name, msg)
33
+ "#{time.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}\n"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ # source: https://github.com/jondot/sneakers/blob/master/lib/sneakers/concerns/logging.rb
@@ -0,0 +1,3 @@
1
+ module Arcade
2
+ VERSION = "0.3.3"
3
+ end
data/lib/arcade.rb ADDED
@@ -0,0 +1,39 @@
1
+ module Arcade
2
+
3
+ end
4
+
5
+ require "arcade/api/version"
6
+ require "dry/configurable"
7
+ require "dry/struct"
8
+ require "dry/core/class_builder"
9
+ require "dry/core/class_attributes"
10
+ require 'json'
11
+
12
+ module Types
13
+ include Dry.Types()
14
+ end
15
+ #require 'pg' # ruby postgres driver
16
+ #require 'mini_sql'
17
+ #require 'sequel'
18
+ #require 'httparty'
19
+ require 'yaml'
20
+ require 'securerandom'
21
+ require 'typhoeus' # curl library
22
+ require_relative '../lib/arcade/errors'
23
+ require_relative '../lib/support/object'
24
+ require_relative '../lib/support/string'
25
+ require_relative '../lib/support/class'
26
+ require_relative '../lib/support/sql'
27
+ require_relative '../lib/support/model'
28
+ require_relative '../lib/arcade/logging'
29
+ require_relative '../lib/config'
30
+ require_relative '../lib/support/conversions'
31
+ require_relative '../lib/arcade/api/operations'
32
+ require_relative '../lib/arcade/base'
33
+ require_relative '../lib/arcade/database'
34
+ require_relative '../lib/init'
35
+ require_relative "../lib/models"
36
+ require_relative '../lib/query'
37
+ require_relative '../lib/match'
38
+ require_relative '../lib/railtie' if defined? Rails::Railtie
39
+
data/lib/config.rb ADDED
@@ -0,0 +1,70 @@
1
+ module Arcade
2
+ class Config
3
+ extend Dry::Configurable
4
+ # central place to initialize constants
5
+ #
6
+ # ProjectRoot has to be a Pathname-Object
7
+ #
8
+ #puts "expand: #{File.expand_path(__dir__)}"
9
+ unless Arcade.const_defined?( :ProjectRoot )
10
+ Arcade::ProjectRoot = if defined?( Rails.env )
11
+ Rails.root
12
+ else
13
+ STDERR.puts "Using default (arcadedb gem) database credentials and settings"
14
+ # logger is not present at this stage
15
+ Pathname.new( File.expand_path( "../../", __FILE__ ))
16
+ end
17
+ else
18
+ STDERR.puts "Using provided database credentials and settings fron #{Arcade::ProjectRoot}"
19
+ end
20
+
21
+
22
+ # initialised a hash { environment => property }
23
+ setting :username, default: :user, reader: true,
24
+ constructor: ->(v) { yml(:environment).map{|x,y| [x , y[v.to_s]] }.to_h }
25
+ setting :password, default: :password, reader: true,
26
+ constructor: ->(v) { yml(:environment).map{|x,y| [x , y["pass"]] }.to_h }
27
+ setting :database, default: :database, reader: true,
28
+ constructor: ->(v) { yml(:environment).map{|x,y| [x , y["dbname"]] }.to_h }
29
+ setting(:base_uri, default: :host , reader: true,
30
+ constructor: ->(v) { "http://"+yml(:admin)[v]+':'+yml(:admin)[:port].to_s+"/api/v1/" })
31
+ setting :autoload, default: :autoload, reader: true , constructor: ->(v) { yml(v) }
32
+ setting :pg, default: :pg, reader: true , constructor: ->(v) { yml(v) }
33
+ setting :admin, default: :admin, reader: true , constructor: ->(v) { yml(v) }
34
+ setting :logger, default: :logger, reader: true ,
35
+ constructor: ->(v) do
36
+ if defined?( Rails.env )
37
+ Rails.logger
38
+ elsif Object.const_defined?(:Bridgetown)
39
+ else
40
+ output = yml(v)
41
+ if output.upcase == 'STDOUT'
42
+ Logger.new STDOUT
43
+ else
44
+ Logger.new File.open( output, 'a' )
45
+ end
46
+ end
47
+ end
48
+ setting :namespace, default: :namespace, reader: true , constructor: ->(v) { yml(v) }
49
+ setting :secret, reader: true, default: 12, constructor: ->(v) { seed(v) }
50
+ private
51
+ # if a config dir exists, use it
52
+ def self.config_file
53
+ if @cd.nil?
54
+ ( cd = ProjectRoot + 'arcade.yml' ).exist? ||
55
+ ( cd = ProjectRoot + 'config' + 'arcade.yml' ).exist? ||
56
+ ( cd = ProjectRoot + "config.yml" )
57
+ @cd = cd
58
+ else
59
+ @cd
60
+ end
61
+ end
62
+ def self.yml key=nil
63
+ y= YAML::load_file( config_file )
64
+ key.nil? ? y : y[key]
65
+ end
66
+ def self.seed( key= nil )
67
+ SecureRandom.hex( 40 )
68
+ end
69
+ end
70
+ end
data/lib/init.rb ADDED
@@ -0,0 +1,50 @@
1
+ module Arcade
2
+
3
+ # Arcade::Init.connect environment
4
+ # --------------------------------
5
+ # initializes the database connection
6
+ # and returns the active database handle
7
+ #
8
+ # The database cannot switched later
9
+ #
10
+ #
11
+ # Arcade::Init.db
12
+ # --------------
13
+ # returns an instance of the database handle
14
+ #
15
+ class Init
16
+ extend Dry::Core::ClassAttributes
17
+ defines :db # database handle
18
+
19
+ def self.connect e= :development
20
+
21
+ env = if e.to_s =~ /^p/
22
+ :production
23
+ elsif e.to_s =~ /^t/
24
+ :test
25
+ else
26
+ :development
27
+ end
28
+ # set the class attribute
29
+
30
+ db Database.new(env)
31
+ end
32
+ end
33
+
34
+ # Provides method `db` to every Model class
35
+ class Base
36
+ def self.db
37
+ Init.db
38
+ end
39
+ # expose db to instance methods as well
40
+ private define_method :db, &method(:db)
41
+ private_class_method :db
42
+ end
43
+ # Provides method `db` to every Query-Object
44
+ class Query
45
+ def db
46
+ Init.db
47
+ end
48
+ end
49
+
50
+ end