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