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.
data/lib/query.rb ADDED
@@ -0,0 +1,384 @@
1
+ #require ' active_support/inflector'
2
+
3
+ module Arcade
4
+
5
+ ######################## Query ###################################
6
+
7
+ QueryAttributes = Struct.new( :kind, :projection, :where, :let, :order, :while, :misc,
8
+ :class, :return, :aliases, :database,
9
+ :set, :remove, :group, :skip, :limit, :unwind, :map )
10
+
11
+ class Query
12
+ include Support::Sql
13
+ include Support::Model
14
+
15
+
16
+ #
17
+ def initialize **args
18
+ @q = QueryAttributes.new args[:kind] || 'select' ,
19
+ [], # :projection
20
+ [], # :where ,
21
+ [], # :let ,
22
+ [], # :order,
23
+ [], # :while,
24
+ [] , # misc
25
+ '', # class
26
+ '', # return
27
+ [], # aliases
28
+ '', # database
29
+ [], #set,
30
+ [] # remove
31
+ args.each{|k,v| send k, v}
32
+ @fill = block_given? ? yield : 'and'
33
+ end
34
+
35
+ =begin
36
+ where: "r > 9" --> where r > 9
37
+ where: {a: 9, b: 's'} --> where a = 9 and b = 's'
38
+ where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
39
+ =end
40
+ def method_missing method, *arg, &b # :nodoc:
41
+ if method ==:while || method=='while'
42
+ while_s arg.first
43
+ else
44
+ @q[:misc] << method.to_s << generate_sql_list(arg)
45
+ end
46
+ self
47
+ end
48
+
49
+ def misc # :nodoc:
50
+ @q[:misc].join(' ') unless @q[:misc].empty?
51
+ end
52
+
53
+ def subquery # :nodoc:
54
+ nil
55
+ end
56
+
57
+ def kind value=nil
58
+ if value.present?
59
+ @q[:kind] = value
60
+ self
61
+ else
62
+ @q[:kind]
63
+ end
64
+ end
65
+ =begin
66
+ Output the compiled query
67
+ Parameter: destination (rest, batch )
68
+ If the query is submitted via the REST-Interface (as get-command), the limit parameter is extracted.
69
+ =end
70
+
71
+ def compose(destination: :batch)
72
+ if kind.to_sym == :update
73
+ return_statement = "return after " + ( @q[:aliases].empty? ? "$current" : @q[:aliases].first.to_s)
74
+ [ 'update', target, set, remove, return_statement, where ].compact.join(' ')
75
+ elsif kind.to_sym == :"update_map"
76
+ [ "update", target, map, where, misc ].compact.join(' ')
77
+ elsif kind.to_sym == :update!
78
+ [ 'update', target, set, where, misc ].compact.join(' ')
79
+ # elsif kind.to_sym == :create # relict of orientdb
80
+ # [ "CREATE VERTEX", target, set ].compact.join(' ')
81
+ # [ kind, target, set, return_statement ,where, limit, misc ].compact.join(' ')
82
+ elsif kind.to_sym == :upsert
83
+ set( generate_sql_list( @q[:where] ){ @fill || 'and' } ) if set.nil?
84
+ return_statement = "return after " + ( @q[:aliases].empty? ? "$current" : @q[:aliases].first.to_s)
85
+ [ "update", target, set,"upsert", return_statement, where, limit, misc ].compact.join(' ')
86
+ #[ kind, where, return_statement ].compact.join(' ')
87
+ elsif destination == :rest
88
+ [ kind, projection, from, let, where, subquery, misc, order, group_by, unwind, skip].compact.join(' ')
89
+ else
90
+ [ kind, projection, from, let, where, subquery, while_s, misc, order, group_by, limit, unwind, skip].compact.join(' ')
91
+ end
92
+ end
93
+ alias :to_s :compose
94
+
95
+
96
+ def to_or
97
+ compose.to_or
98
+ end
99
+
100
+ def target arg = nil
101
+ if arg.present?
102
+ @q[:database] = arg
103
+ self # return query-object
104
+ elsif @q[:database].present?
105
+ the_argument = @q[:database]
106
+ case @q[:database]
107
+ when Arcade::Base # a single record
108
+ the_argument.rid
109
+ when self.class # result of a query
110
+ ' ( '+ the_argument.compose + ' ) '
111
+ when Class
112
+ the_argument.database_name
113
+ else
114
+ if the_argument.to_s.rid? # a string with "#ab:cd"
115
+ the_argument
116
+ else # a database-class-name
117
+ the_argument.to_s
118
+ end
119
+ end
120
+ else
121
+ raise "cannot complete until a target is specified"
122
+ end
123
+ end
124
+
125
+ =begin
126
+ from can either be a Databaseclass to operate on or a Subquery providing data to query further
127
+ =end
128
+ def from arg = nil
129
+ if arg.present?
130
+ @q[:database] = arg
131
+ self # return query-object
132
+ elsif @q[:database].present? # read from
133
+ "from #{ target }"
134
+ end
135
+ end
136
+
137
+
138
+ def order value = nil
139
+ if value.present?
140
+ @q[:order] << value
141
+ self
142
+ elsif @q[:order].present?
143
+
144
+ "order by " << @q[:order].compact.flatten.map do |o|
145
+ case o
146
+ when String, Symbol, Array
147
+ o.to_s
148
+ else
149
+ o.map{|x,y| "#{x} #{y}"}.join(" ")
150
+ end # case
151
+ end.join(', ')
152
+ else
153
+ ''
154
+ end # unless
155
+ end # def
156
+
157
+
158
+ def database_class # :nodoc:
159
+ @q[:database]
160
+ end
161
+
162
+ def database_class= arg # :nodoc:
163
+ @q[:database] = arg
164
+ end
165
+
166
+ def distinct d
167
+ @q[:projection] << "distinct " + generate_sql_list( d ){ ' as ' }
168
+ self
169
+ end
170
+
171
+ class << self
172
+ def mk_simple_setter *m
173
+ m.each do |def_m|
174
+ define_method( def_m ) do | value=nil |
175
+ if value.present?
176
+ @q[def_m] = value
177
+ self
178
+ elsif @q[def_m].present?
179
+ "#{def_m.to_s} #{generate_sql_list(@q[def_m]){' ,'}}"
180
+ end
181
+ end
182
+ end
183
+ end
184
+ def mk_std_setter *m
185
+ m.each do |def_m|
186
+ define_method( def_m ) do | value = nil |
187
+ if value.present?
188
+ @q[def_m] << case value
189
+ when String
190
+ value
191
+ when ::Hash
192
+ value.map{|k,v| "#{k} = #{v.to_or}"}.join(", ")
193
+ else
194
+ raise "Only String or Hash allowed in #{def_m} statement"
195
+ end
196
+ self
197
+ elsif @q[def_m].present?
198
+ "#{def_m.to_s} #{@q[def_m].join(',')}"
199
+ end # branch
200
+ end # def_method
201
+ end # each
202
+ end # def
203
+ end # class << self
204
+ mk_simple_setter :limit, :skip, :unwind
205
+ mk_std_setter :set, :remove
206
+
207
+ def while_s value=nil # :nodoc:
208
+ if value.present?
209
+ @q[:while] << value
210
+ self
211
+ elsif @q[:while].present?
212
+ "while #{ generate_sql_list( @q[:while] ) }"
213
+ end
214
+ end
215
+ def where value=nil # :nodoc:
216
+ if value.present?
217
+ if value.is_a?( Hash ) && value.size >1
218
+ value.each {| a, b | where( {a => b} ) }
219
+ else
220
+ @q[:where] << value
221
+ end
222
+ self
223
+ elsif @q[:where].present?
224
+ "where #{ generate_sql_list( @q[:where] ){ @fill || 'and' } }"
225
+ end
226
+ end
227
+
228
+ def as a=nil
229
+ if a
230
+ @q[:as] = a # subsequent calls overwrite previous entries
231
+ else
232
+ if @q[:as].blank?
233
+ nil
234
+ else
235
+ "as: #{ @q[:as] }"
236
+ end
237
+ end
238
+ end
239
+
240
+ # update_map - update an embedded map
241
+ def map value=nil
242
+ if value.present?
243
+ @q[:map] = value
244
+ self
245
+ elsif @q[:map].present?
246
+ # only one pair is supported
247
+ "set #{@q[:map].keys.first} = #{@q[:map].values.first.to_or}"
248
+ end
249
+ end
250
+ def let value = nil
251
+ if value.present?
252
+ @q[:let] << value
253
+ self
254
+ elsif @q[:let].present?
255
+ "let " << @q[:let].map do |s|
256
+ case s
257
+ when String
258
+ s
259
+ when ::Hash
260
+ s.map do |x,y|
261
+ # if the symbol: value notation of Hash is used, add "$" to the key
262
+ x = "$#{x.to_s}" unless x.is_a?(String) && x[0] == "$"
263
+ "#{x} = #{ case y
264
+ when self.class
265
+ "(#{y.compose})"
266
+ else
267
+ y.to_db
268
+ end }"
269
+ end
270
+ end
271
+ end.join(', ')
272
+ end
273
+ end
274
+ #
275
+ def projection value= nil # :nodoc:
276
+ if value.present?
277
+ @q[:projection] << value
278
+ self
279
+ elsif @q[:projection].present?
280
+ @q[:projection].compact.map do | s |
281
+ case s
282
+ when ::Array
283
+ s.join(', ')
284
+ when String, Symbol
285
+ s.to_s
286
+ when ::Hash
287
+ s.map{ |x,y| "#{x} as #{y}"}.join( ', ')
288
+ end
289
+ end.join( ', ' )
290
+ end
291
+ end
292
+
293
+ def group value = nil
294
+ if value.present?
295
+ @q[:group] << value
296
+ self
297
+ elsif @q[:group].present?
298
+ "group by #{@q[:group].join(', ')}"
299
+ end
300
+ end
301
+
302
+ alias order_by order
303
+ alias group_by group
304
+
305
+ def get_limit # :nodoc:
306
+ @q[:limit].nil? ? -1 : @q[:limit].to_i
307
+ end
308
+
309
+ def expand item
310
+ @q[:projection] =[ " expand ( #{item.to_s} )" ]
311
+ self
312
+ end
313
+
314
+ # connects by adding {in_or_out}('edgeClass')
315
+ def connect_with in_or_out, via: nil
316
+ argument = " #{in_or_out}(#{via.to_or if via.present?})"
317
+ end
318
+ # adds a connection
319
+ # in_or_out: :out ---> outE('edgeClass').in[where-condition]
320
+ # :in ---> inE('edgeClass').out[where-condition]
321
+
322
+ def nodes in_or_out = :out, via: nil, where: nil, expand: false
323
+
324
+ condition = where.present? ? "[ #{generate_sql_list(where)} ]" : ""
325
+ via = resolve_edge_name(via) unless via.nil?
326
+
327
+ start = if in_or_out.is_a? Symbol
328
+ in_or_out.to_s
329
+ elsif in_or_out.is_a? String
330
+ in_or_out
331
+ else
332
+ "both"
333
+ end
334
+ argument = " #{start}(#{via})#{condition} "
335
+
336
+ if expand.present?
337
+ send :expand, argument
338
+ else
339
+ @q[:projection] << argument
340
+ end
341
+ self
342
+ end
343
+
344
+
345
+ def query
346
+ db.query compose
347
+ end
348
+
349
+ # returns nil if the query was not sucessfully executed
350
+ def execute(reduce: false, autoload: true )
351
+ # unless projection.nil? || projection.empty?
352
+ result = db.execute { compose }
353
+ return nil unless result.is_a?(Array)
354
+ block_given? ? result.map{|x| yield x } : result
355
+ # return result.first if reduce && result.size == 1
356
+ ## case select count(*) from ... --> [{ :count => n }] projection is set
357
+ ## case update ... after $current --> [{ :$current => n}] projection is not set, but result is an integer
358
+ # separate key from values and get model-files
359
+ # if !@q[:projection].empty? && result.first.is_a?(Hash) && result.first.values.is_a?( Array )
360
+ # if reduce
361
+ # result.first.values.map{|x| allocate_model x, autoload}
362
+ # else
363
+ # result.map{|_,m| allocate_model m, autoload }
364
+ # end
365
+ # eloe
366
+ # result.map{|y| allocate_model y, autoload }
367
+ # end
368
+ ## standard case: return Array
369
+ #result.arcade_flatten
370
+ end
371
+ :protected
372
+ def resolve_target
373
+ if @q[:database].is_a? Arcade::Query
374
+ @q[:database].resolve_target
375
+ else
376
+ @q[:database]
377
+ end
378
+ end
379
+
380
+ # end
381
+ end # class
382
+
383
+
384
+ end # module
@@ -0,0 +1,13 @@
1
+ module Arcade
2
+ module Class
3
+ def demodulize
4
+ a, *b = to_s.split("::")
5
+ if b.empty?
6
+ a
7
+ else
8
+ b.join('::')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ Class.include Arcade::Class
@@ -0,0 +1,295 @@
1
+ module Arcade
2
+ module Support
3
+ module Array
4
+ # Class extentions to manage to_db and from_db
5
+ def to_db
6
+ if all?{ |x| x.respond_to?(:rid?)} && any?( &:rid? )
7
+ "["+ map{|x| x.rid? ? x.rid : x.to_or }.join(', ') + ']'
8
+ else
9
+ map(&:to_db) # .join(',')
10
+ end
11
+ end
12
+
13
+ def to_or
14
+ "["+ map( &:to_or).join(', ')+"]"
15
+ end
16
+
17
+ def from_db
18
+ map &:from_db
19
+ end
20
+
21
+ def to_human
22
+ map &:to_human
23
+ end
24
+
25
+ def to_html
26
+ # all elements are treated equally
27
+ if first.is_a? Arcade::Base
28
+ IRuby::Table map(&:invariant_attributes)
29
+ else
30
+ IRuby.display IRuby.html "<p> #{map(&:to_human).join("<br>")}</p>"
31
+ end
32
+ end
33
+
34
+ # used to enable
35
+ # def abc *key
36
+ # where key is a Range, an comma separated List or an item
37
+ # aimed to support #compose_where
38
+ def analyse # :nodoc:
39
+ if first.is_a?(Range)
40
+ first
41
+ elsif size ==1
42
+ first
43
+ else
44
+ self
45
+ end
46
+ end
47
+
48
+ def arcade_flatten
49
+ while( first.is_a?(Array) )
50
+ self.flatten!(1)
51
+ end
52
+ self.compact!
53
+ self ## return object
54
+ end
55
+
56
+
57
+ # chainable model-extaction method
58
+ #
59
+ # Used to get the results of a query where a projection is specified
60
+ # q = DB.query(" select <projection> from ...") --> [<projection> => [ { result (hash) } , ... ]]
61
+ # q.select_result( <projection> ) --> An array or plain results or
62
+ # --> an array of Arcade::Base-objects
63
+ #
64
+
65
+ def select_result condition=nil
66
+ condition = first.keys.first if condition.nil?
67
+ map{|x| x[condition.to_sym]}.flatten.allocate_model
68
+ end
69
+
70
+ # convert query results into Arcade::Base-objects
71
+ # handles [query: => [{ result }, {result} ], too
72
+
73
+ def allocate_model autoload=false
74
+ if size==1 && first.is_a?( Hash ) && !first.keys.include?( :@type )
75
+ # Load only the associated record, not the entire structure
76
+ first.values.flatten.map{ |x| x.allocate_model(false) }
77
+ else
78
+ map{ |x| _allocate_model x, autoload }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ class Array
86
+ include Arcade::Support::Array
87
+ include Arcade::Support::Model # mixin allocate_model
88
+
89
+ @@accepted_methods = [:"_allocate_model"]
90
+ ## dummy for refining
91
+
92
+ # it is assumed that the first element of the array acts
93
+ # as master .
94
+ def method_missing method, *args, &b
95
+ return if [:to_hash, :to_str].include? method
96
+ if @@accepted_methods.include?( method ) || first.invariant_attributes.include?( method )
97
+ self.map{ |x| x.public_send method, *args, &b }
98
+ else
99
+ raise ArgumentError.new("Method #{method} does not exist in class #{first.class}")
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ module Arcade
106
+ module Support
107
+ module Symbol
108
+ def to_a
109
+ [ self ]
110
+ end
111
+ # symbols are masked with ":{symbol}:"
112
+ def to_db
113
+ ":" + self.to_s + ":"
114
+ end
115
+ def to_or
116
+ "'" + self.to_db + "'"
117
+ end
118
+
119
+ end
120
+
121
+ module Object
122
+ def from_db
123
+ self
124
+ end
125
+
126
+ def to_db
127
+ self
128
+ end
129
+
130
+ def to_or
131
+ self
132
+ end
133
+
134
+ def rid?
135
+ false
136
+ end
137
+ end
138
+
139
+ module Time
140
+ def to_or
141
+ "DATE(#{self.to_datetime.strftime('%Q')})"
142
+ end
143
+ end
144
+
145
+ module Date
146
+ def to_db
147
+ if RUBY_PLATFORM == 'java'
148
+ java.util.Date.new( year-1900, month-1, day , 0, 0 , 0 ) ## Jahr 0 => 1900
149
+ else
150
+ self
151
+ end
152
+ end
153
+ def to_or
154
+ "\"#{self.to_s}\""
155
+ # "DATE(\'#{self.to_s}\',\'yyyy-MM-dd\')"
156
+ # "DATE(#{self.strftime('%Q')})"
157
+ end
158
+ # def to_json
159
+ # "DATE(#{self.strftime('%Q')})"
160
+ # end
161
+ end
162
+ module DateTime
163
+ def to_or
164
+ "DATE(#{self.strftime('%Q')})"
165
+ end
166
+ end
167
+ module Numeric
168
+
169
+ def to_or
170
+ # "#{self.to_s}"
171
+ self
172
+ end
173
+
174
+ def to_a
175
+ [ self ]
176
+ end
177
+
178
+ def rid?
179
+ nil
180
+ end
181
+ end
182
+
183
+ module Hash
184
+
185
+ # converts :abc => {anything} to "abc" => {anything}
186
+ #
187
+ # converts nn => {anything} to nn => {anything}
188
+ #
189
+ # leaves "abc" => {anything} untouched
190
+ def to_db # converts hast from activeorient to db
191
+ map do | k, v|
192
+ orient_k = case k
193
+ when Numeric
194
+ k
195
+ when Symbol, String
196
+ k.to_s
197
+ else
198
+ raise "not supported key: #[k} -- must a sting, symbol or number"
199
+ end
200
+ [orient_k, v.to_db]
201
+ end.to_h
202
+ end
203
+ #
204
+ def from_db
205
+ # populate the hash by converting keys: stings to symbols, values: preprocess through :from_db
206
+ map do |k,v|
207
+ orient_k = if k.to_s.to_i.to_s == k.to_s
208
+ k.to_i
209
+ else
210
+ k.to_sym
211
+ end
212
+
213
+ [orient_k, v.from_db]
214
+ end.to_h
215
+ end
216
+
217
+ def to_human
218
+ "< " + map do | k,v |
219
+ vv = v.is_a?( Arcade::Base ) ? v.to_human[1..-2] : v.to_s
220
+ k.to_s + ": " + vv
221
+ end.join( "\n " ) + " >"
222
+ end
223
+
224
+ def allocate_model( autoload = Config.autoload )
225
+ _allocate_model( self , autoload )
226
+ end
227
+
228
+
229
+
230
+ # converts a hash to a string appropiate to include in raw queries
231
+ def to_or
232
+ "{ " + to_db.map{|k,v| "#{k.to_or}: #{v.to_or}"}.join(',') + "}"
233
+ end
234
+ end
235
+
236
+
237
+ module String2
238
+
239
+ # from db translates the database response into active-orient objects
240
+ #
241
+ # symbols are representated via ":{something]:}"
242
+ #
243
+ # database records respond to the "rid"-method
244
+ #
245
+ # other values are not modified
246
+ def from_db
247
+ if rid?
248
+ Arcade::Init.db.get self
249
+ elsif self =~ /^:.*:$/
250
+ # symbol-representation in the database
251
+ self[1..-2].to_sym
252
+ else
253
+ self
254
+ end
255
+ end
256
+
257
+ alias expand from_db
258
+ # if the string contains "#xx:yy" omit quotes
259
+ def to_db
260
+ rid? ? "#"+rid : self # return the string (not the quoted string. this is to_or)
261
+ end
262
+
263
+ def to_or
264
+ quote
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+
271
+ Symbol.include Arcade::Support::Symbol
272
+ Numeric.include Arcade::Support::Numeric
273
+ Object.include Arcade::Support::Object
274
+ Time.include Arcade::Support::Time
275
+ Date.include Arcade::Support::Date
276
+ DateTime.include Arcade::Support::DateTime
277
+ String.include Arcade::Support::String2
278
+ class Hash
279
+ include Arcade::Support::Model # mixin allocate_model
280
+ include Arcade::Support::Hash
281
+ end
282
+
283
+ class NilClass
284
+ def to_or
285
+ "NULL"
286
+ end
287
+ end
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+