skydb 0.2.2 → 0.2.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.
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