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.
- data/bin/sky +4 -0
- data/lib/skydb.rb +3 -2
- data/lib/skydb/action.rb +19 -0
- data/lib/skydb/client.rb +15 -5
- data/lib/skydb/event.rb +3 -7
- data/lib/skydb/import/importer.rb +236 -59
- data/lib/skydb/import/transforms/apache.yml +4 -0
- data/lib/skydb/import/transforms/sky.yml +20 -12
- data/lib/skydb/message.rb +1 -0
- data/lib/skydb/message/add_event.rb +1 -1
- data/lib/skydb/message/get_actions.rb +4 -0
- data/lib/skydb/message/get_properties.rb +4 -0
- data/lib/skydb/message/get_tables.rb +43 -0
- data/lib/skydb/message/lua/aggregate.rb +4 -0
- data/lib/skydb/property.rb +10 -0
- data/lib/skydb/query.rb +44 -59
- data/lib/skydb/query/after_condition.rb +104 -0
- data/lib/skydb/query/{after.rb → condition.rb} +37 -27
- data/lib/skydb/query/on_condition.rb +53 -0
- data/lib/skydb/query/selection.rb +131 -1
- data/lib/skydb/query/selection_field.rb +25 -0
- data/lib/skydb/query/selection_group.rb +21 -0
- data/lib/skydb/table.rb +7 -0
- data/lib/skydb/version.rb +1 -1
- data/test/integration/query_test.rb +102 -0
- data/test/test_helper.rb +42 -1
- data/test/{client_test.rb → unit/client_test.rb} +0 -0
- data/test/{event_test.rb → unit/event_test.rb} +0 -5
- data/test/unit/import/importer_test.rb +208 -0
- data/test/{import → unit/import}/translator_test.rb +0 -0
- data/test/{message → unit/message}/add_action_message_test.rb +0 -0
- data/test/{message → unit/message}/add_event_message_test.rb +2 -2
- data/test/{message → unit/message}/add_property_message_test.rb +0 -0
- data/test/{message → unit/message}/create_table_message_test.rb +0 -0
- data/test/{message → unit/message}/delete_table_message_test.rb +0 -0
- data/test/{message → unit/message}/get_action_message_test.rb +0 -0
- data/test/{message → unit/message}/get_actions_message_test.rb +0 -0
- data/test/{message → unit/message}/get_properties_message_test.rb +0 -0
- data/test/{message → unit/message}/get_property_message_test.rb +0 -0
- data/test/{message → unit/message}/get_table_message_test.rb +0 -0
- data/test/unit/message/get_tables_message_test.rb +18 -0
- data/test/{message → unit/message}/lookup_message_test.rb +0 -0
- data/test/{message → unit/message}/lua_aggregate_message_test.rb +0 -0
- data/test/{message → unit/message}/multi_message_test.rb +0 -0
- data/test/{message → unit/message}/next_action_message_test.rb +0 -0
- data/test/{message → unit/message}/ping_message_test.rb +0 -0
- data/test/{message_test.rb → unit/message_test.rb} +0 -0
- data/test/unit/query/after_test.rb +89 -0
- data/test/{query/after_test.rb → unit/query/on_test.rb} +10 -10
- data/test/{query → unit/query}/selection_test.rb +2 -2
- data/test/{query_test.rb → unit/query_test.rb} +32 -6
- data/test/{skydb_test.rb → unit/skydb_test.rb} +0 -0
- metadata +165 -53
- 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 =
|
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
data/lib/skydb/version.rb
CHANGED
@@ -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
|