arcadedb 0.3.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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
+