skydb 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/bin/sky +4 -0
  2. data/lib/skydb.rb +3 -2
  3. data/lib/skydb/action.rb +19 -0
  4. data/lib/skydb/client.rb +15 -5
  5. data/lib/skydb/event.rb +3 -7
  6. data/lib/skydb/import/importer.rb +236 -59
  7. data/lib/skydb/import/transforms/apache.yml +4 -0
  8. data/lib/skydb/import/transforms/sky.yml +20 -12
  9. data/lib/skydb/message.rb +1 -0
  10. data/lib/skydb/message/add_event.rb +1 -1
  11. data/lib/skydb/message/get_actions.rb +4 -0
  12. data/lib/skydb/message/get_properties.rb +4 -0
  13. data/lib/skydb/message/get_tables.rb +43 -0
  14. data/lib/skydb/message/lua/aggregate.rb +4 -0
  15. data/lib/skydb/property.rb +10 -0
  16. data/lib/skydb/query.rb +44 -59
  17. data/lib/skydb/query/after_condition.rb +104 -0
  18. data/lib/skydb/query/{after.rb → condition.rb} +37 -27
  19. data/lib/skydb/query/on_condition.rb +53 -0
  20. data/lib/skydb/query/selection.rb +131 -1
  21. data/lib/skydb/query/selection_field.rb +25 -0
  22. data/lib/skydb/query/selection_group.rb +21 -0
  23. data/lib/skydb/table.rb +7 -0
  24. data/lib/skydb/version.rb +1 -1
  25. data/test/integration/query_test.rb +102 -0
  26. data/test/test_helper.rb +42 -1
  27. data/test/{client_test.rb → unit/client_test.rb} +0 -0
  28. data/test/{event_test.rb → unit/event_test.rb} +0 -5
  29. data/test/unit/import/importer_test.rb +208 -0
  30. data/test/{import → unit/import}/translator_test.rb +0 -0
  31. data/test/{message → unit/message}/add_action_message_test.rb +0 -0
  32. data/test/{message → unit/message}/add_event_message_test.rb +2 -2
  33. data/test/{message → unit/message}/add_property_message_test.rb +0 -0
  34. data/test/{message → unit/message}/create_table_message_test.rb +0 -0
  35. data/test/{message → unit/message}/delete_table_message_test.rb +0 -0
  36. data/test/{message → unit/message}/get_action_message_test.rb +0 -0
  37. data/test/{message → unit/message}/get_actions_message_test.rb +0 -0
  38. data/test/{message → unit/message}/get_properties_message_test.rb +0 -0
  39. data/test/{message → unit/message}/get_property_message_test.rb +0 -0
  40. data/test/{message → unit/message}/get_table_message_test.rb +0 -0
  41. data/test/unit/message/get_tables_message_test.rb +18 -0
  42. data/test/{message → unit/message}/lookup_message_test.rb +0 -0
  43. data/test/{message → unit/message}/lua_aggregate_message_test.rb +0 -0
  44. data/test/{message → unit/message}/multi_message_test.rb +0 -0
  45. data/test/{message → unit/message}/next_action_message_test.rb +0 -0
  46. data/test/{message → unit/message}/ping_message_test.rb +0 -0
  47. data/test/{message_test.rb → unit/message_test.rb} +0 -0
  48. data/test/unit/query/after_test.rb +89 -0
  49. data/test/{query/after_test.rb → unit/query/on_test.rb} +10 -10
  50. data/test/{query → unit/query}/selection_test.rb +2 -2
  51. data/test/{query_test.rb → unit/query_test.rb} +32 -6
  52. data/test/{skydb_test.rb → unit/skydb_test.rb} +0 -0
  53. metadata +165 -53
  54. data/test/import/importer_test.rb +0 -42
@@ -0,0 +1,53 @@
1
+ class SkyDB
2
+ class Query
3
+ # The 'on' condition filters a selection and leaves the cursor on the
4
+ # current matching event.
5
+ class OnCondition < SkyDB::Query::Condition
6
+ ##########################################################################
7
+ #
8
+ # Constructor
9
+ #
10
+ ##########################################################################
11
+
12
+ def initialize(action=nil, options={})
13
+ options.merge!(action.is_a?(Hash) ? action : {:action => action})
14
+ super(options)
15
+ end
16
+
17
+
18
+ ##########################################################################
19
+ #
20
+ # Methods
21
+ #
22
+ ##########################################################################
23
+
24
+ ##################################
25
+ # Codegen
26
+ ##################################
27
+
28
+ # Generates Lua code to match a given action.
29
+ def codegen(options={})
30
+ header, body, footer = "function #{function_name.to_s}(cursor, data)\n", [], "end\n"
31
+
32
+ # If the action is :enter then just check for the beginning of a session.
33
+ if action == :enter
34
+ body << "return (cursor.session_event_index == 0)"
35
+ else
36
+ # Only move to the next event if directed to by the options.
37
+ body << "if cursor:eos() or cursor:eof() then return false end"
38
+ body << "repeat"
39
+ body << " if cursor.event.action_id == #{action.id.to_i} then"
40
+ body << " return true"
41
+ body << " end"
42
+ body << "until not cursor:next()"
43
+ body << "return false"
44
+ end
45
+
46
+ # Indent body and return.
47
+ body.map! {|line| " " + line}
48
+ return header + body.join("\n") + "\n" + footer
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -73,8 +73,10 @@ class SkyDB
73
73
  ##########################################################################
74
74
 
75
75
  def initialize(options={})
76
+ self.query = options[:query]
76
77
  self.fields = options[:fields] || []
77
78
  self.groups = options[:groups] || []
79
+ self.conditions = options[:conditions] || []
78
80
  end
79
81
 
80
82
 
@@ -84,12 +86,19 @@ class SkyDB
84
86
  #
85
87
  ##########################################################################
86
88
 
89
+ # The query this selection is attached to.
90
+ attr_accessor :query
91
+
87
92
  # A list of fields that will be returned from the server.
88
93
  attr_accessor :fields
89
94
 
90
95
  # A list of expressions to group the returned data by.
91
96
  attr_accessor :groups
92
97
 
98
+ # A list of conditions that must be fulfilled before performing a
99
+ # selection.
100
+ attr_accessor :conditions
101
+
93
102
 
94
103
  ##########################################################################
95
104
  #
@@ -139,6 +148,31 @@ class SkyDB
139
148
  return self
140
149
  end
141
150
 
151
+ # Adds an 'after' condition to the query.
152
+ #
153
+ # @param [Hash] options The options to pass to the 'after' condition.
154
+ #
155
+ # @return [Query] The query object is returned.
156
+ def after(options={})
157
+ conditions << SkyDB::Query::AfterCondition.new(options)
158
+ return self
159
+ end
160
+
161
+ # Adds an 'on' condition to the query.
162
+ #
163
+ # @param [Hash] options The options to pass to the 'on' condition.
164
+ #
165
+ # @return [Query] The query object is returned.
166
+ def on(options={})
167
+ conditions << SkyDB::Query::OnCondition.new(options)
168
+ return self
169
+ end
170
+
171
+ # Executes the parent query.
172
+ def execute()
173
+ return query.execute
174
+ end
175
+
142
176
 
143
177
  ####################################
144
178
  # Validation
@@ -165,6 +199,16 @@ class SkyDB
165
199
  # Codegen
166
200
  ####################################
167
201
 
202
+ # Generates Lua code for the entire selection including conditions and
203
+ # merging.
204
+ def codegen
205
+ return [
206
+ codegen_select(),
207
+ codegen_select_all(),
208
+ codegen_merge()
209
+ ].join("\n")
210
+ end
211
+
168
212
  # Generates Lua code for the aggregation based on the selection.
169
213
  def codegen_select
170
214
  header, body, footer = "function select(cursor, data)\n", [], "end\n"
@@ -176,7 +220,7 @@ class SkyDB
176
220
  # Initialize groups.
177
221
  groups.each do |group|
178
222
  body << "group_value = #{group.accessor}"
179
- body << "if cursor:eos() or cursor:eof() then group_value = -1 end" if group.expression == 'action_id'
223
+ body << "if cursor:eos() or cursor:eof() then group_value = 'exit' end" if group.expression == 'action_id'
180
224
  body << "if target[group_value] == nil then"
181
225
  body << " target[group_value] = {}"
182
226
  body << "end"
@@ -213,6 +257,42 @@ class SkyDB
213
257
  return header + body.join("\n") + "\n" + footer
214
258
  end
215
259
 
260
+ # Generates Lua code for the aggregation based on the selection.
261
+ def codegen_select_all
262
+ header, body, footer = "function select_all(cursor, data)\n", [], "end\n"
263
+
264
+ # Generate the invocation of the conditions.
265
+ conditional_functions = codegen_conditional_functions()
266
+ conditionals = conditions.map {|condition| "#{condition.function_name}(cursor, data)"}.join(' and ')
267
+ conditionals = "true" if conditions.length == 0
268
+
269
+ body << "while cursor:next_session() do"
270
+ body << " while cursor:next() do"
271
+ body << " if #{conditionals} then"
272
+ body << " select(cursor, data)"
273
+ body << " end"
274
+ body << " end"
275
+ body << "end"
276
+
277
+ # Indent body and return.
278
+ body.map! {|line| " " + line}
279
+ return conditional_functions + "\n" + header + body.join("\n") + "\n" + footer
280
+ end
281
+
282
+ # Generates Lua code for the conditional functions.
283
+ def codegen_conditional_functions
284
+ code = []
285
+
286
+ # Generate condition functions.
287
+ conditions.each_with_index do |condition, index|
288
+ condition.function_name ||= "__condition#{query.nextseq}"
289
+ code << condition.codegen
290
+ end
291
+
292
+ return code.join("\n")
293
+ end
294
+
295
+
216
296
  # Generates Lua code for the merge function.
217
297
  def codegen_merge
218
298
  header, body, footer = "function merge(results, data)\n", [], "end\n"
@@ -262,6 +342,56 @@ class SkyDB
262
342
  body.map! {|line| " " + line}
263
343
  return header + body.join("\n") + "\n" + footer
264
344
  end
345
+
346
+
347
+ ####################################
348
+ # Serialization
349
+ ####################################
350
+
351
+ # Serializes the selection object into a JSON string.
352
+ def to_json(*a); to_hash.to_json(*a); end
353
+
354
+ # Serializes the selection object into a hash.
355
+ def to_hash(*a)
356
+ {
357
+ 'fields' => fields.to_a.map {|f| f.to_hash},
358
+ 'groups' => groups.to_a.map {|g| g.to_hash},
359
+ 'conditions' => conditions.to_a.map {|c| c.to_hash}
360
+ }
361
+ end
362
+
363
+ # Deserializes the selection object from a hash.
364
+ def from_hash(hash, *a)
365
+ return nil if hash.nil?
366
+ self.fields = hash['fields'].to_a.map {|h| SkyDB::Query::SelectionField.new.from_hash(h, *a)}
367
+ self.groups = hash['groups'].to_a.map {|h| SkyDB::Query::SelectionGroup.new.from_hash(h, *a)}
368
+ self.conditions = hash['conditions'].to_a.map do |h|
369
+ if h['type'] == 'on'
370
+ SkyDB::Query::OnCondition.new.from_hash(h, *a)
371
+ else
372
+ SkyDB::Query::AfterCondition.new.from_hash(h, *a)
373
+ end
374
+ end
375
+ return self
376
+ end
377
+
378
+
379
+ ####################################
380
+ # Identifier Management
381
+ ####################################
382
+
383
+ # Retrieves a list of all action objects.
384
+ def get_identifiers
385
+ actions = []
386
+
387
+ conditions.each do |condition|
388
+ if condition.action.is_a?(SkyDB::Action) && condition.action.id.to_i == 0
389
+ actions << condition.action
390
+ end
391
+ end
392
+
393
+ return actions
394
+ end
265
395
  end
266
396
  end
267
397
  end
@@ -69,6 +69,31 @@ class SkyDB
69
69
  raise SkyDB::Query::ValidationError.new("Invalid expression for selection field: '#{expression.to_s}'")
70
70
  end
71
71
  end
72
+
73
+ ####################################
74
+ # Serialization
75
+ ####################################
76
+
77
+ # Serializes the selection field object into a JSON string.
78
+ def to_json(*a); to_hash.to_json(*a); end
79
+
80
+ # Serializes the selection field object into a hash.
81
+ def to_hash(*a)
82
+ {
83
+ 'expression' => expression.to_s,
84
+ 'aliasName' => alias_name.to_s,
85
+ 'aggregationType' => aggregation_type.to_s
86
+ }.delete_if {|k,v| v == ''}
87
+ end
88
+
89
+ # Deserializes the selection field object from a hash.
90
+ def from_hash(hash, *a)
91
+ return nil if hash.nil?
92
+ self.expression = hash['expression']
93
+ self.alias_name = hash['aliasName']
94
+ self.aggregation_type = hash['aggregationType'].to_s != '' ? hash['aggregationType'].to_s.to_sym : nil
95
+ return self
96
+ end
72
97
  end
73
98
  end
74
99
  end
@@ -52,6 +52,27 @@ class SkyDB
52
52
  raise SkyDB::Query::ValidationError.new("Invalid expression for selection group: '#{expression.to_s}'")
53
53
  end
54
54
  end
55
+
56
+ ####################################
57
+ # Serialization
58
+ ####################################
59
+
60
+ # Serializes the selection group into a JSON string.
61
+ def to_json(*a); to_hash.to_json(*a); end
62
+
63
+ # Serializes the selection group into a hash.
64
+ def to_hash(*a)
65
+ {
66
+ 'expression' => expression.to_s
67
+ }.delete_if {|k,v| v == ''}
68
+ end
69
+
70
+ # Deserializes the selection field object from a hash.
71
+ def from_hash(hash, *a)
72
+ return nil if hash.nil?
73
+ self.expression = hash['expression']
74
+ return self
75
+ end
55
76
  end
56
77
  end
57
78
  end
data/lib/skydb/table.rb CHANGED
@@ -65,5 +65,12 @@ class SkyDB
65
65
  tablet_count: tablet_count
66
66
  }.to_msgpack
67
67
  end
68
+
69
+ # Encodes the table into JSON format.
70
+ def to_json(*a)
71
+ {
72
+ 'name' => name
73
+ }.to_json(*a)
74
+ end
68
75
  end
69
76
  end
data/lib/skydb/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class SkyDB
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -0,0 +1,102 @@
1
+ require 'test_helper'
2
+
3
+ class TestQuery < MiniTest::Unit::TestCase
4
+ ##############################################################################
5
+ #
6
+ # Setup / Teardown
7
+ #
8
+ ##############################################################################
9
+
10
+ def setup
11
+ @query = SkyDB::Query.new()
12
+ end
13
+
14
+
15
+ ##############################################################################
16
+ #
17
+ # Tests
18
+ #
19
+ ##############################################################################
20
+
21
+ ######################################
22
+ # Aggregation
23
+ ######################################
24
+
25
+ def test_select_count
26
+ import("integration/query/count.json")
27
+ results = SkyDB.select('count()').execute()
28
+ assert_equal ({"count" => 7}), results
29
+ end
30
+
31
+ def test_select_count_by_action_id
32
+ import("integration/query/count.json")
33
+ results = SkyDB.select('count()').group_by("action_id").execute()
34
+ assert_equal({
35
+ 1=>{"count"=>2}, # /
36
+ 2=>{"count"=>1}, # /signup
37
+ 3=>{"count"=>2}, # /login
38
+ 4=>{"count"=>1}, # /about
39
+ 5=>{"count"=>1} # /cancel_account
40
+ }, results)
41
+ end
42
+
43
+
44
+ ######################################
45
+ # Sessions
46
+ ######################################
47
+
48
+ def test_select_count_by_action_id_on_enter
49
+ import("integration/query/count.json")
50
+ results = SkyDB.select('count()')
51
+ .group_by("action_id")
52
+ .on(:enter)
53
+ .execute()
54
+ assert_equal ({1 => {"count" => 2}}), results
55
+ end
56
+
57
+ def test_select_count_by_action_id_on_enter_and_after
58
+ import("integration/query/count.json")
59
+ results = SkyDB.select('count()')
60
+ .group_by("action_id")
61
+ .on(:enter)
62
+ .after("/")
63
+ .execute()
64
+ assert_equal ({2=>{"count"=>1}, 3=>{"count"=>1}}), results
65
+ end
66
+
67
+ def test_select_count_by_action_id_on_enter_and_after_sessionized
68
+ import("integration/query/count.json")
69
+ query = SkyDB.query.session(7200)
70
+ results = query.select('count()')
71
+ .group_by("action_id")
72
+ .on(:enter)
73
+ .after("/login")
74
+ .execute()
75
+ assert_equal ({"exit"=>{"count"=>1}, 5=>{"count"=>1}}), results
76
+ end
77
+
78
+ def test_select_count_by_action_id_on_enter_and_after_within_sessionized
79
+ import("integration/query/count.json")
80
+ query = SkyDB.query.session(7200)
81
+ results = query.select('count()')
82
+ .group_by("action_id")
83
+ .on(:enter)
84
+ .after(:action => "/", :within => {:quantity => 1, :unit => 'step'})
85
+ .after(:action => "/login", :within => {:quantity => 1, :unit => 'step'})
86
+ .execute()
87
+ assert_equal ({5=>{"count"=>1}}), results
88
+ end
89
+
90
+ def test_double_after
91
+ import("integration/query/double_after.json")
92
+ query = SkyDB.query.session(7200)
93
+ results = query.select('count()')
94
+ .group_by("action_id")
95
+ .on(:enter)
96
+ .after(:action => "/", :within => {:quantity => 1, :unit => 'step'})
97
+ .after(:action => "/login", :within => {:quantity => 1, :unit => 'step'})
98
+ .after(:action => "/login", :within => {:quantity => 1, :unit => 'step'})
99
+ .execute()
100
+ assert_equal ({"exit"=>{"count"=>1}}), results
101
+ end
102
+ end
data/test/test_helper.rb CHANGED
@@ -1,15 +1,56 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_group "Query", "skydb/query"
4
+ end
5
+
1
6
  require 'bundler/setup'
2
7
  require 'minitest/autorun'
3
8
  require 'mocha'
4
9
  require 'unindentable'
10
+ require "stringio"
5
11
  require 'skydb'
6
12
  require 'skydb/import'
7
13
 
8
14
  class MiniTest::Unit::TestCase
15
+ # The name of the table to import into.
16
+ def default_table_name; "sky-integration-tests"; end
17
+
9
18
  def assert_bytes exp, act, msg = nil
10
19
  exp = exp.to_hex
11
20
  act = act.string.to_hex
12
21
  assert_equal(exp, act, msg)
13
22
  end
14
- end
15
23
 
24
+ # Captures STDIN & STDERR and returns them as strings.
25
+ def capture_io
26
+ _stdout, $stdout = $stdout, StringIO.new
27
+ _stderr, $stderr = $stderr, StringIO.new
28
+ yield
29
+ [$stdout.string, $stderr.string]
30
+ ensure
31
+ $stdout = _stdout
32
+ $stderr = _stderr
33
+ end
34
+
35
+ def import(filename, options={})
36
+ # Default options.
37
+ options = {
38
+ :table_name => default_table_name,
39
+ :transform => 'sky'
40
+ }.merge(options)
41
+
42
+ # Prepend fixture directory.
43
+ filename = File.expand_path(File.join(File.dirname(__FILE__), "../fixtures", filename))
44
+
45
+ # Delete the table if it exists.
46
+ table = SkyDB.get_table(options[:table_name])
47
+ SkyDB.delete_table(SkyDB::Table.new(options[:table_name])) unless table.nil?
48
+ SkyDB.create_table(SkyDB::Table.new(options[:table_name]))
49
+
50
+ # Import.
51
+ importer = SkyDB::Import::Importer.new()
52
+ importer.table_name = options[:table_name]
53
+ importer.load_transform_file(options[:transform]) unless options[:transform].nil?
54
+ importer.import([filename], :progress_bar => false)
55
+ end
56
+ end