arcadedb 0.4 → 0.5.2
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/CHANGELOG.md +32 -1
- data/Gemfile.lock +2 -36
- data/README.md +191 -41
- data/arcade.yml +1 -1
- data/bin/console +17 -16
- 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 +48 -78
- data/lib/arcade/api/primitives.rb +64 -29
- data/lib/arcade/base.rb +105 -51
- data/lib/{config.rb → arcade/config.rb} +5 -6
- data/lib/arcade/database.rb +107 -156
- data/lib/arcade/errors.rb +8 -0
- data/lib/{init.rb → arcade/init.rb} +5 -2
- data/lib/arcade/match.rb +210 -0
- data/lib/{query.rb → arcade/query.rb} +40 -29
- data/lib/{support → arcade/support}/conversions.rb +1 -0
- data/lib/{support → arcade/support}/model.rb +23 -22
- data/lib/{support → arcade/support}/sql.rb +1 -1
- data/lib/{support → arcade/support}/string.rb +11 -14
- data/lib/arcade/version.rb +1 -1
- data/lib/arcade.rb +16 -15
- data/lib/model/document.rb +22 -0
- data/lib/model/edge.rb +29 -0
- data/lib/model/revision.rb +41 -0
- data/lib/model/revision_record.rb +29 -0
- data/lib/model/vertex.rb +118 -48
- data/lib/models.rb +2 -0
- data/lib/types.rb +35 -0
- metadata +22 -16
- data/lib/match.rb +0 -216
- /data/lib/{support → arcade/support}/class.rb +0 -0
- /data/lib/{support → arcade/support}/object.rb +0 -0
data/lib/arcade/database.rb
CHANGED
@@ -17,12 +17,12 @@ module Arcade
|
|
17
17
|
defines :environment
|
18
18
|
|
19
19
|
def initialize environment=:development
|
20
|
-
self.class.configure_logger( Config.logger )
|
21
|
-
@connection = connect environment
|
20
|
+
self.class.configure_logger( Config.logger )
|
22
21
|
if self.class.environment.nil? # class attribute is set on the first call
|
23
22
|
# further instances of Database share the same environment
|
24
23
|
self.class.environment environment
|
25
24
|
end
|
25
|
+
@session_id = nil # declare session_id
|
26
26
|
self.class.namespace Object.const_get( Config.namespace )
|
27
27
|
end
|
28
28
|
|
@@ -39,20 +39,19 @@ module Arcade
|
|
39
39
|
#
|
40
40
|
def types refresh=false
|
41
41
|
# uses API
|
42
|
-
if
|
43
|
-
|
44
|
-
.map{ |
|
45
|
-
.map{ |y| y.delete_if{|_,b,| b.empty? } } # eliminate empty entries
|
42
|
+
if @types.nil? || refresh
|
43
|
+
@types = Api.query(database, "select from schema:types" )
|
44
|
+
.map{ |y| y.delete_if{|_,b,| b.blank? } } # eliminate empty entries
|
46
45
|
end
|
47
|
-
|
48
|
-
##
|
46
|
+
@types
|
47
|
+
## upon startup, this is the first access to the database-server
|
49
48
|
rescue NoMethodError => e
|
50
49
|
logger.fatal "Could not read Database Types. \n Is the database running?"
|
51
50
|
Kernel.exit
|
52
51
|
end
|
53
52
|
|
54
|
-
def indexes
|
55
|
-
|
53
|
+
def indexes refresh=false
|
54
|
+
types(refresh).find_all{|x| x.key? :indexes }.map{|y| y[:indexes]}.flatten
|
56
55
|
end
|
57
56
|
|
58
57
|
# ------------ hierarchy -------------
|
@@ -108,29 +107,57 @@ module Arcade
|
|
108
107
|
"create edge type #{type} "
|
109
108
|
end.concat( args.map{|x,y| "#{x} #{y} "}.join)
|
110
109
|
end
|
111
|
-
|
110
|
+
dbe= Api.execute database, &exe
|
112
111
|
types( true ) # update cached schema
|
113
|
-
|
114
|
-
|
115
|
-
rescue
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
112
|
+
dbe
|
113
|
+
|
114
|
+
rescue Arcade::QueryError => e
|
115
|
+
if e.message =~/Type\s+.+\salready\s+exists/
|
116
|
+
Arcade::Database.logger.debug "Database type #{type} already present"
|
117
|
+
else
|
118
|
+
raise
|
119
|
+
end
|
122
120
|
end
|
123
121
|
|
124
122
|
alias create_class create_type
|
125
123
|
|
126
124
|
# ------------ drop type -----------
|
127
125
|
# delete any record prior to the attempt to drop a type.
|
128
|
-
# The `unsafe` option is
|
126
|
+
# The `unsafe` option is not implemented.
|
129
127
|
def drop_type type
|
130
128
|
Api.execute database, "drop type #{type} if exists"
|
131
129
|
end
|
132
130
|
|
133
|
-
#
|
131
|
+
# ------------------------------ transaction ----------------------------------------------------- #
|
132
|
+
# Encapsulates simple transactions
|
133
|
+
#
|
134
|
+
# nested transactions are not supported.
|
135
|
+
# * use the low-leve api.begin_tranaction for that purpose
|
136
|
+
# * reuses an existing transaction
|
137
|
+
#
|
138
|
+
def begin_transaction
|
139
|
+
@session_id ||= Api.begin_transaction database
|
140
|
+
end
|
141
|
+
# ------------------------------ commit ----------------------------------------------------- #
|
142
|
+
def commit
|
143
|
+
r = Api.commit( database, session_id: session)
|
144
|
+
@session_id = nil
|
145
|
+
true if r == 204
|
146
|
+
end
|
147
|
+
|
148
|
+
# ------------------------------ rollback ----------------------------------------------------- #
|
149
|
+
#
|
150
|
+
def rollback
|
151
|
+
r = Api.rollback( database, session_id: session)
|
152
|
+
@session_id = nil
|
153
|
+
true if r == 500
|
154
|
+
rescue HTTPX::HTTPError => e
|
155
|
+
raise
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
# ------------ create ----------- ## not supported anymore by the api
|
134
161
|
# returns an rid of the successfully created vertex or document
|
135
162
|
#
|
136
163
|
# Parameter: name of the vertex or document type
|
@@ -141,24 +168,38 @@ module Arcade
|
|
141
168
|
#
|
142
169
|
def create type, **params
|
143
170
|
# uses API
|
144
|
-
Api.create_document database, type, **params
|
171
|
+
Api.create_document database, type, session_id: session, **params
|
145
172
|
end
|
146
173
|
|
174
|
+
# ------------------------------ insert ------------------------------------------------------ #
|
175
|
+
#
|
176
|
+
# translates the given parameters to
|
177
|
+
# INSERT INTO [TYPE:]<type>|BUCKET:<bucket>|INDEX:<index>
|
178
|
+
# [(<field>[,]*) VALUES (<expression>[,]*)[,]*]|
|
179
|
+
# [CONTENT {<JSON>}|[{<JSON>}[,]*]]
|
180
|
+
#
|
181
|
+
# :from and :return are not supported
|
182
|
+
#
|
183
|
+
# If a transaction is active, the insert is executed in that context.
|
184
|
+
# Nested transactions are not supported
|
147
185
|
def insert **params
|
148
186
|
|
149
|
-
content_params = params.except( :type, :bucket, :index, :from, :return )
|
187
|
+
content_params = params.except( :type, :bucket, :index, :from, :return, :session_id )
|
150
188
|
target_params = params.slice( :type, :bucket, :index )
|
189
|
+
# session_id = params[:session_id] # extraxt session_id --> future-use?
|
151
190
|
if target_params.empty?
|
152
|
-
|
191
|
+
raise "Could not insert: target missing (type:, bucket:, index:)"
|
153
192
|
elsif content_params.empty?
|
154
193
|
logger.error "Nothing to Insert"
|
155
194
|
else
|
156
|
-
content =
|
195
|
+
content = "CONTENT #{ content_params.to_json }"
|
157
196
|
target = target_params.map{|y,z| y==:type ? z : "#{y.to_s} #{ z } "}.join
|
158
|
-
Api.execute( database, "INSERT INTO #{target} #{content} "
|
197
|
+
result = Api.execute( database, session_id: session ){ "INSERT INTO #{target} #{content} "}
|
198
|
+
result &.first.allocate_model(false)
|
159
199
|
end
|
160
200
|
end
|
161
201
|
|
202
|
+
|
162
203
|
# ------------------------------ get ------------------------------------------------------ #
|
163
204
|
# Get fetches the record associated with the rid given as parameter.
|
164
205
|
#
|
@@ -178,103 +219,61 @@ module Arcade
|
|
178
219
|
rid = rid.join(':')
|
179
220
|
rid = rid[1..-1] if rid[0]=="#"
|
180
221
|
if rid.rid?
|
181
|
-
Api.query( database, "select from #{rid}" ).first &.allocate_model(autocomplete)
|
222
|
+
Api.query( database, "select from #{rid}", session_id: session ).first &.allocate_model(autocomplete)
|
182
223
|
else
|
183
224
|
raise Arcade::QueryError "Get requires a rid input", caller
|
184
225
|
end
|
185
226
|
end
|
186
227
|
|
187
|
-
# ------------------------------
|
188
|
-
# Adds properties to the type
|
228
|
+
# ------------------------------ delete ------------------------------------------------------ #
|
189
229
|
#
|
190
|
-
#
|
191
|
-
# Api.property <database>, <type>, name1: a_format , name2: a_format
|
230
|
+
# Delete the specified rid
|
192
231
|
#
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
232
|
+
def delete rid
|
233
|
+
r = Api.execute( database, session_id: session ){ "delete from #{rid}" }
|
234
|
+
success = r == [{ :count => 1 }]
|
235
|
+
end
|
236
|
+
|
237
|
+
# ------------------------------ transmit ------------------------------------------------------ #
|
238
|
+
# transmits a command which potentially modifies the database
|
197
239
|
#
|
198
|
-
#
|
240
|
+
# Uses the given session_id for transaction-based operations
|
199
241
|
#
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
false
|
207
|
-
else
|
208
|
-
r.keys == [ :propertyName, :typeName, :operation ] && r[:operation] == 'create property'
|
209
|
-
end
|
210
|
-
end.uniq
|
211
|
-
if success == [true]
|
212
|
-
commit database
|
213
|
-
true
|
242
|
+
# Otherwise just performs the operation
|
243
|
+
|
244
|
+
def transmit &block
|
245
|
+
response = Api.execute database, session_id: session, &block
|
246
|
+
if response.is_a? Hash
|
247
|
+
_allocate_model res
|
214
248
|
else
|
215
|
-
|
249
|
+
response
|
216
250
|
end
|
217
|
-
|
218
|
-
|
219
|
-
end
|
220
|
-
|
221
|
-
# ------------------------------ index ------------------------------------------------- #
|
222
|
-
def self.index database, type, name , *properties
|
223
|
-
properties = properties.map( &:to_s )
|
224
|
-
unique_requested = "unique" if properties.delete("unique")
|
225
|
-
unique_requested = "notunique" if properties.delete("notunique" )
|
226
|
-
automatic = true if
|
227
|
-
properties << name if properties.empty?
|
228
|
-
end
|
229
|
-
|
230
|
-
def delete rid
|
231
|
-
r = Api.execute( database ){ "delete from #{rid}" }
|
232
|
-
success = r == [{ :count => 1 }]
|
233
251
|
end
|
234
|
-
|
252
|
+
# ------------------------------ execute ------------------------------------------------------ #
|
235
253
|
# execute a command which modifies the database
|
236
254
|
#
|
237
255
|
# The operation is performed via Transaction/Commit
|
238
256
|
# If an Error occurs, its rolled back
|
239
257
|
#
|
258
|
+
# If a transaction is already active, a nested transation is initiated
|
259
|
+
#
|
240
260
|
def execute &block
|
241
|
-
|
242
|
-
|
243
|
-
response = Api.execute database,
|
244
|
-
# rescue HTTPX::HTTPError => e
|
245
|
-
# raise e.message
|
246
|
-
# puts e.methods
|
247
|
-
# puts e.status
|
248
|
-
# puts e.response
|
249
|
-
# puts e.message
|
250
|
-
# puts e.exception
|
251
|
-
# puts e.cause
|
252
|
-
# end
|
253
|
-
# puts response.inspect # debugging
|
261
|
+
# initiate a new transaction
|
262
|
+
s= Api.begin_transaction database
|
263
|
+
response = Api.execute database, session_id: s, &block
|
254
264
|
r= if response.is_a? Hash
|
255
|
-
_allocate_model
|
256
|
-
# elsif response.is_a? Array
|
257
|
-
# remove empty results
|
258
|
-
# response.delete_if{|y| y.empty?}
|
259
|
-
# response.map do | res |
|
260
|
-
# if res.key? :"@rid"
|
261
|
-
# allocate_model res
|
262
|
-
# else
|
263
|
-
# res
|
264
|
-
# end
|
265
|
-
# end
|
265
|
+
_allocate_model response
|
266
266
|
else
|
267
267
|
response
|
268
268
|
end
|
269
|
-
if Api.commit( database, s) == 204
|
269
|
+
if Api.commit( database, session_id: s) == 204
|
270
270
|
r # return associated array of Arcade::Base-objects
|
271
271
|
else
|
272
272
|
[]
|
273
273
|
end
|
274
|
-
rescue Dry::Struct::Error,
|
275
|
-
Api.rollback database, s
|
276
|
-
logger.
|
277
|
-
# logger.error "Execution FAILED --> #{e.exception.message}"
|
274
|
+
rescue Dry::Struct::Error, Arcade::QueryError => e
|
275
|
+
Api.rollback database, session_id: s, log: false
|
276
|
+
logger.fatal "Execution FAILED --> Status #{e}"
|
278
277
|
[] # return empty result
|
279
278
|
end
|
280
279
|
|
@@ -283,7 +282,7 @@ module Arcade
|
|
283
282
|
# detects database-records and allocates them as model-objects
|
284
283
|
#
|
285
284
|
def query query_object
|
286
|
-
Api.query database, query_object.to_s
|
285
|
+
Api.query database, query_object.to_s, session_id: session
|
287
286
|
end
|
288
287
|
|
289
288
|
# returns an array of rid's (same logic as create)
|
@@ -292,20 +291,12 @@ module Arcade
|
|
292
291
|
content = attributes.empty? ? "" : "CONTENT #{attributes.to_json}"
|
293
292
|
cr = ->( f, t ) do
|
294
293
|
begin
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
# puts e.status
|
299
|
-
# puts e.message
|
300
|
-
# puts e.message.class
|
301
|
-
# end
|
294
|
+
cmd = -> (){ "create edge #{edge_class} from #{f.rid} to #{t.rid} #{content}" }
|
295
|
+
edges = transmit( &cmd ).allocate_model(false)
|
296
|
+
rescue Arcade::QueryError => e
|
302
297
|
raise unless e.message =~ /Found duplicate key/
|
303
|
-
puts "#"+e.
|
298
|
+
puts "#"+e.detail.split("#").last[0..-3]
|
304
299
|
end
|
305
|
-
#else
|
306
|
-
# logger.error "Could not create Edge #{edge_class} from #{f} to #{t}"
|
307
|
-
## logger.error edges.to_s
|
308
|
-
#end
|
309
300
|
end
|
310
301
|
from = [from] unless from.is_a? Array
|
311
302
|
to = [to] unless to.is_a? Array
|
@@ -316,52 +307,12 @@ module Arcade
|
|
316
307
|
|
317
308
|
end
|
318
309
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
# not used
|
323
|
-
# def get_schema
|
324
|
-
# query( "select from schema:types" ).map do |a|
|
325
|
-
# puts "a: #{a}"
|
326
|
-
# class_name = a["name"]
|
327
|
-
# inherent_class = a["parentTypes"].empty? ? [Object,nil] : a["parentTypes"].map(&:camelcase_and_namespace)
|
328
|
-
# namespace, type_name = a["type"].camelcase_and_namespace
|
329
|
-
# namespace= Arcade if namespace.nil?
|
330
|
-
# klass= Dry::Core::ClassBuilder.new( name: type_name,
|
331
|
-
# parent: nil,
|
332
|
-
# namespace: namespace).call
|
333
|
-
# end
|
334
|
-
# rescue NameError
|
335
|
-
# logger.error "Dataset type #{e} not defined."
|
336
|
-
# raise
|
337
|
-
# end
|
338
|
-
# Postgres is not implemented
|
339
|
-
# connects to the database and initialises @connection
|
340
|
-
def connection
|
341
|
-
@connection
|
310
|
+
def session
|
311
|
+
@session_id
|
342
312
|
end
|
343
313
|
|
344
|
-
def
|
345
|
-
|
346
|
-
|
347
|
-
# connect through the ruby postgres driver
|
348
|
-
# c= PG::Connection.new dbname: Config.database[environment],
|
349
|
-
# user: Config.username[environment],
|
350
|
-
# password: Config.password[environment],
|
351
|
-
# host: Config.pg[:host],
|
352
|
-
# port: Config.pg[:port]
|
353
|
-
#
|
354
|
-
end
|
355
|
-
rescue PG::ConnectionBad => e
|
356
|
-
if e.to_s =~ /Credentials/
|
357
|
-
logger.error "NOT CONNECTED ! Either Database is not present or credentials (#{ Config.username[environment]} / #{Config.password[environment]}) are wrong"
|
358
|
-
nil
|
359
|
-
else
|
360
|
-
raise
|
361
|
-
end
|
362
|
-
end # def
|
363
|
-
|
364
|
-
|
365
|
-
|
314
|
+
def session?
|
315
|
+
!session.nil?
|
316
|
+
end
|
366
317
|
end # class
|
367
318
|
end # module
|
data/lib/arcade/errors.rb
CHANGED
@@ -22,6 +22,14 @@ module Arcade
|
|
22
22
|
end
|
23
23
|
|
24
24
|
class QueryError < RuntimeError
|
25
|
+
attr_reader :error, :args
|
26
|
+
def initialize error: "", detail: "", exception: "", **args
|
27
|
+
@error = error
|
28
|
+
# @detail = detail
|
29
|
+
@args = args
|
30
|
+
@exception = exception
|
31
|
+
super detail
|
32
|
+
end
|
25
33
|
end
|
26
34
|
|
27
35
|
# used by Dry::Validation, not covered by "error"
|
@@ -14,7 +14,8 @@ module Arcade
|
|
14
14
|
#
|
15
15
|
class Init
|
16
16
|
extend Dry::Core::ClassAttributes
|
17
|
-
defines :db
|
17
|
+
defines :db # database handle
|
18
|
+
defines :models # static array of database name for arcade-model classes
|
18
19
|
|
19
20
|
def self.connect e= :development
|
20
21
|
|
@@ -25,10 +26,12 @@ module Arcade
|
|
25
26
|
else
|
26
27
|
:development
|
27
28
|
end
|
28
|
-
# set the class attribute
|
29
29
|
|
30
|
+
# set the class attributes
|
31
|
+
models Base.descendants.map{|x| x.to_s.split("Arcade::").last }
|
30
32
|
db Database.new(env)
|
31
33
|
end
|
34
|
+
|
32
35
|
end
|
33
36
|
|
34
37
|
# Provides method `db` to every Model class
|
data/lib/arcade/match.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
module Arcade
|
2
|
+
class Match
|
3
|
+
|
4
|
+
include Arcade::Support::Sql
|
5
|
+
|
6
|
+
=begin
|
7
|
+
This is a wrapper for the match statement
|
8
|
+
|
9
|
+
Initialize: a= Arcade::Match.new type: Arcade::DatabaseType, where: { property: 'value' }, as: :alias
|
10
|
+
Complete: b = a.out( Arcade::EdgeType ).node( while: true, as: item )[ .in.node ... ]
|
11
|
+
Inspect b.to_s
|
12
|
+
Query DB b.execute [.allocate_model]
|
13
|
+
[.analyse_result]
|
14
|
+
Customize the return values:
|
15
|
+
b.to_s{ "customized return statement" }
|
16
|
+
b.execute{ "customized return statment" }
|
17
|
+
|
18
|
+
Conditions on edges:
|
19
|
+
m = Match.new type: Arcade::DatabaseType
|
20
|
+
m.inE via: Arcade::Edge || via: [Arcade::Edge1, Arcade::Edge2], where: { var: 3..6 }
|
21
|
+
---> inE('edge(1)'){ where: (var between 3 and 6 )}.outV('edge(2)')
|
22
|
+
|
23
|
+
Address a single database record:
|
24
|
+
m = Match.new( vertex: { Arcade::Vertex-Instance } )
|
25
|
+
.....
|
26
|
+
.node( vertex: { Arcade::Vertex-Instance } ) # instead of where statement
|
27
|
+
|
28
|
+
Example
|
29
|
+
|
30
|
+
symbol_or_title = symbol.present? ? { :symbol => symbol } : { :title => title }
|
31
|
+
where = { :right => right }
|
32
|
+
|
33
|
+
a= Arcade::Match.new( type: self.class, where: symbol_or_title )
|
34
|
+
.out( Arcade::HasContract )
|
35
|
+
.node( as: :c, where: where )
|
36
|
+
a.execute do "c.last_trading_day as expiry, # c is returned form the query [as: :c]
|
37
|
+
count(c) as contracts,
|
38
|
+
min(c.strike) as s_min,
|
39
|
+
max(c.strike) as s_max
|
40
|
+
group by c.last_trading_day order by c.last_trading_day"
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
=end
|
45
|
+
|
46
|
+
def initialize **args
|
47
|
+
type = args.delete :type
|
48
|
+
vertex = args.delete :vertex
|
49
|
+
rid = args.delete :rid
|
50
|
+
raise "MATCH#new:: parameter rid is not supported. Use `vertex: Arcade::Vertex.instance` instead." if rid.present?
|
51
|
+
@args = args
|
52
|
+
@as = []
|
53
|
+
|
54
|
+
@stack = if vertex.is_a?( Arcade::Vertex ) && args.empty?
|
55
|
+
[ "MATCH { type: #{vertex.class.database_name }, rid: #{vertex.rid} }" ]
|
56
|
+
elsif vertex.is_a?( Arcade::Vertex )
|
57
|
+
[ "MATCH { type: #{vertex.class.database_name }, rid: #{vertex.rid}, #{ assigned_parameters } }" ]
|
58
|
+
elsif type.is_a?( Class) && type.ancestors.include?(Arcade::Vertex) && args.empty?
|
59
|
+
[ "MATCH { type: #{type.database_name} }" ]
|
60
|
+
elsif type.is_a?( Class) && type.ancestors.include?(Arcade::Vertex)
|
61
|
+
[ "MATCH { type: #{type.database_name}, #{ assigned_parameters } }" ]
|
62
|
+
else
|
63
|
+
raise "Match:: Either type (Arcade::Vertex-Class) or vertex (Arcade::Vertex-Object) is required as parameter"
|
64
|
+
end
|
65
|
+
|
66
|
+
return self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Inspect the generated match statement
|
70
|
+
def to_s &b
|
71
|
+
r = ""
|
72
|
+
r = "DISTINCT " if @distinct
|
73
|
+
r << @as.join(",")
|
74
|
+
r= yield(r) if block_given?
|
75
|
+
@stack.join("") + " RETURN #{r} "
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Execute the @stack
|
80
|
+
# generally followed by `select_result` to convert json-hashes to arcade objects
|
81
|
+
#
|
82
|
+
# The optional block modifies the result-statement
|
83
|
+
# i.e
|
84
|
+
# TG::TimeGraph.grid( 2023, 2..9 ).out(HasPosition)
|
85
|
+
# .node( as: :contract )
|
86
|
+
# .execute { "contract.symbol" }
|
87
|
+
# .select_result
|
88
|
+
# gets all associated contracts connected to the month-grid
|
89
|
+
def execute &b
|
90
|
+
Arcade::Init.db.query( to_s( &b ) )
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
def self.define_base_edge *direction
|
96
|
+
direction.each do | e_d |
|
97
|
+
define_method e_d do | edge = "" |
|
98
|
+
raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
|
99
|
+
@stack << ".#{ e_d }(#{edge.is_a?(Class) ? edge.database_name.to_or : ''})"
|
100
|
+
self
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# add conditions on edges to the match statement
|
107
|
+
def self.define_inner_edge start_dir, final_dir
|
108
|
+
define_method start_dir do | edge = "", **a |
|
109
|
+
raise "edge must be a Database-class" unless edge.is_a?(Class) || edge.empty?
|
110
|
+
|
111
|
+
print_edges = -> (e){ e.is_a?(Class) ? e.database_name.to_or : ''}
|
112
|
+
edges = [ a.delete(:via) ].flatten
|
113
|
+
edges << edge unless edge == ''
|
114
|
+
n = if a.empty?
|
115
|
+
""
|
116
|
+
else
|
117
|
+
@args = a
|
118
|
+
"{ #{ assigned_parameters } }"
|
119
|
+
end
|
120
|
+
@stack << ".#{ start_dir }(#{print_edges.call(edges.first)})#{ n }.#{ final_dir }(#{print_edges.call(edges.last)})"
|
121
|
+
return self
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
define_base_edge :out, :in, :both
|
127
|
+
define_inner_edge :inE, :outV
|
128
|
+
define_inner_edge :outE, :inV
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
# general declation of a node (ie. vertex)
|
133
|
+
def node **args
|
134
|
+
vertex = args.delete :vertex
|
135
|
+
rid = args.delete :rid
|
136
|
+
raise "MATCH#node:: parameter rid is not supported. Use `vertex: Arcade::Vertex.instance` instead." if rid.present?
|
137
|
+
@args = args
|
138
|
+
@stack << if args.empty?
|
139
|
+
vertex.present? ? "{ type: #{vertex.class.database_name }, rid: #{vertex.rid} }" : "{}"
|
140
|
+
else
|
141
|
+
vertex.present? ? "{ type: #{vertex.class.database_name }, rid: #{vertex.rid}, #{assigned_parameters} }" : "{ #{ assigned_parameters } }"
|
142
|
+
end
|
143
|
+
return self
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
### ---------------- end of public api ---------------------------------------------------------------- ###
|
149
|
+
private
|
150
|
+
|
151
|
+
|
152
|
+
def assigned_parameters
|
153
|
+
@args.map do | k, v |
|
154
|
+
# unless k == :while # mask ruby keyword
|
155
|
+
send k, v
|
156
|
+
# else
|
157
|
+
# the_while v
|
158
|
+
# end
|
159
|
+
end.compact.join(', ')
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
## Metastatement ---------- last --------------------
|
164
|
+
##
|
165
|
+
## generates a node declaration for fetching the last element of a traversal on the previous edge
|
166
|
+
##
|
167
|
+
## ie: Arcade::Match.new( type: self.class, where: { symbol: symbol } ).out( Arcade::HasContract )
|
168
|
+
# .node( as: :c, where: where )
|
169
|
+
# .in( Arcade::IsOrder )
|
170
|
+
# .node( last: true, as: :o )
|
171
|
+
#
|
172
|
+
# --> ... }.in('is_order'){ while: (in('is_order').size() > 0), where: (in('is_order').size() == 0), as: o }
|
173
|
+
def last arg
|
174
|
+
in_or_out = @stack[-1][1..-1] # use the last statement
|
175
|
+
"while: ( #{in_or_out}.size() > 0 ), where: (#{in_or_out}.size() == 0)"
|
176
|
+
end
|
177
|
+
|
178
|
+
def distinct arg
|
179
|
+
@distinct = true
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def where arg
|
184
|
+
"where: ( #{ generate_sql_list( arg ) } )" unless arg.empty?
|
185
|
+
end
|
186
|
+
|
187
|
+
def while arg
|
188
|
+
if arg.is_a? TrueClass
|
189
|
+
"while: ( true )"
|
190
|
+
else
|
191
|
+
"while: ( #{ generate_sql_list( arg ) } )"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def maxdepth arg
|
196
|
+
"maxDepth: #{arg}"
|
197
|
+
end
|
198
|
+
|
199
|
+
def as arg
|
200
|
+
@as << arg
|
201
|
+
"as: " + arg.to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
def type klassname
|
205
|
+
raise "type must be a Database-class" unless klassname.is_a? Class
|
206
|
+
"type: #{klassname.database_name}"
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|