skydb 0.2.1 → 0.2.2
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 +85 -0
- data/lib/ext/hash.rb +11 -0
- data/lib/ext/treetop.rb +19 -0
- data/lib/skydb.rb +10 -3
- data/lib/skydb/client.rb +92 -28
- data/lib/skydb/import.rb +7 -0
- data/lib/skydb/import/importer.rb +258 -0
- data/lib/skydb/import/transforms/sky.yml +20 -0
- data/lib/skydb/import/transforms/snowplow.yml +1 -0
- data/lib/skydb/import/translator.rb +119 -0
- data/lib/skydb/message.rb +17 -12
- data/lib/skydb/message/create_table.rb +64 -0
- data/lib/skydb/message/delete_table.rb +66 -0
- data/lib/skydb/message/get_table.rb +74 -0
- data/lib/skydb/message/lookup.rb +79 -0
- data/lib/skydb/property.rb +5 -5
- data/lib/skydb/query.rb +198 -0
- data/lib/skydb/query/after.rb +103 -0
- data/lib/skydb/query/ast/selection_field_syntax_node.rb +26 -0
- data/lib/skydb/query/ast/selection_fields_syntax_node.rb +16 -0
- data/lib/skydb/query/ast/selection_group_syntax_node.rb +16 -0
- data/lib/skydb/query/ast/selection_groups_syntax_node.rb +16 -0
- data/lib/skydb/query/selection.rb +268 -0
- data/lib/skydb/query/selection_field.rb +74 -0
- data/lib/skydb/query/selection_fields_grammar.treetop +46 -0
- data/lib/skydb/query/selection_fields_parse_error.rb +30 -0
- data/lib/skydb/query/selection_group.rb +57 -0
- data/lib/skydb/query/selection_groups_grammar.treetop +31 -0
- data/lib/skydb/query/selection_groups_parse_error.rb +30 -0
- data/lib/skydb/query/validation_error.rb +8 -0
- data/lib/skydb/table.rb +69 -0
- data/lib/skydb/version.rb +1 -1
- data/test/import/importer_test.rb +42 -0
- data/test/import/translator_test.rb +88 -0
- data/test/message/add_event_message_test.rb +1 -1
- data/test/message/add_property_message_test.rb +2 -2
- data/test/message/create_table_message_test.rb +34 -0
- data/test/message/delete_table_message_test.rb +34 -0
- data/test/message/get_table_message_test.rb +19 -0
- data/test/message/lookup_message_test.rb +27 -0
- data/test/message_test.rb +1 -1
- data/test/query/after_test.rb +71 -0
- data/test/query/selection_test.rb +273 -0
- data/test/query_test.rb +156 -0
- data/test/test_helper.rb +3 -0
- metadata +129 -3
data/lib/skydb/property.rb
CHANGED
@@ -9,11 +9,11 @@ class SkyDB
|
|
9
9
|
##########################################################################
|
10
10
|
|
11
11
|
# Initializes the property.
|
12
|
-
def initialize(
|
13
|
-
self.id = id
|
14
|
-
self.type = type
|
15
|
-
self.data_type = data_type
|
16
|
-
self.name = name
|
12
|
+
def initialize(options={})
|
13
|
+
self.id = options[:id]
|
14
|
+
self.type = options[:type]
|
15
|
+
self.data_type = options[:data_type]
|
16
|
+
self.name = options[:name]
|
17
17
|
end
|
18
18
|
|
19
19
|
|
data/lib/skydb/query.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'skydb/query/selection'
|
2
|
+
require 'skydb/query/after'
|
3
|
+
require 'skydb/query/validation_error'
|
4
|
+
|
5
|
+
class SkyDB
|
6
|
+
# The Query object represents a high level abstraction of how data is
|
7
|
+
# processed and retrieved from the database. It is inspired by ActiveRecord
|
8
|
+
# in the sense that commands can be chained together.
|
9
|
+
#
|
10
|
+
# The query is not executed until the execute() method is called.
|
11
|
+
class Query
|
12
|
+
##########################################################################
|
13
|
+
#
|
14
|
+
# Constructor
|
15
|
+
#
|
16
|
+
##########################################################################
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
self.client = options[:client]
|
20
|
+
self.selection = options[:selection] || SkyDB::Query::Selection.new()
|
21
|
+
self.conditions = options[:conditions] || []
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
##########################################################################
|
26
|
+
#
|
27
|
+
# Attributes
|
28
|
+
#
|
29
|
+
##########################################################################
|
30
|
+
|
31
|
+
# The client that is used for executing the query.
|
32
|
+
attr_accessor :client
|
33
|
+
|
34
|
+
# The properties that should be selected from the database.
|
35
|
+
attr_accessor :selection
|
36
|
+
|
37
|
+
# A list of conditions that must be fulfilled before selection can occur.
|
38
|
+
attr_accessor :conditions
|
39
|
+
|
40
|
+
# The number of idle seconds that separates sessions.
|
41
|
+
attr_accessor :session_idle_time
|
42
|
+
|
43
|
+
|
44
|
+
##########################################################################
|
45
|
+
#
|
46
|
+
# Methods
|
47
|
+
#
|
48
|
+
##########################################################################
|
49
|
+
|
50
|
+
####################################
|
51
|
+
# Helpers
|
52
|
+
####################################
|
53
|
+
|
54
|
+
# Adds a list of fields to the selection.
|
55
|
+
#
|
56
|
+
# @param [String] fields A list of fields to add to the selection.
|
57
|
+
#
|
58
|
+
# @return [Query] The query object is returned.
|
59
|
+
def select(*fields)
|
60
|
+
selection.select(*fields)
|
61
|
+
return self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds one or more grouping fields to the selection of the query.
|
65
|
+
#
|
66
|
+
# @param [String] groups A list of groups to add to the selection.
|
67
|
+
#
|
68
|
+
# @return [Query] The query object is returned.
|
69
|
+
def group_by(*groups)
|
70
|
+
selection.group_by(*groups)
|
71
|
+
return self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Adds an 'after' condition to the query.
|
75
|
+
#
|
76
|
+
# @param [Hash] options The options to pass to the 'after' condition.
|
77
|
+
#
|
78
|
+
# @return [Query] The query object is returned.
|
79
|
+
def after(options={})
|
80
|
+
conditions << SkyDB::Query::After.new(options)
|
81
|
+
return self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Sets the session idle seconds and returns the query object.
|
85
|
+
#
|
86
|
+
# @param [Fixnum] seconds The number of idle seconds.
|
87
|
+
#
|
88
|
+
# @return [Query] The query object is returned.
|
89
|
+
def session(seconds)
|
90
|
+
self.session_idle_time = seconds
|
91
|
+
return self
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
####################################
|
96
|
+
# Execution
|
97
|
+
####################################
|
98
|
+
|
99
|
+
# Executes the query and returns the resulting data.
|
100
|
+
def execute
|
101
|
+
# Generate the Lua code for this query.
|
102
|
+
code = codegen()
|
103
|
+
|
104
|
+
# Send it to the server.
|
105
|
+
results = client.aggregate(code)
|
106
|
+
|
107
|
+
# Return the results.
|
108
|
+
return results
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
####################################
|
113
|
+
# Validation
|
114
|
+
####################################
|
115
|
+
|
116
|
+
# Validates that all the elements of the query are valid.
|
117
|
+
def validate!
|
118
|
+
selection.validate!
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
####################################
|
123
|
+
# Codegen
|
124
|
+
####################################
|
125
|
+
|
126
|
+
# Generates the Lua code that represents the query.
|
127
|
+
def codegen
|
128
|
+
# Lookup all actions & properties.
|
129
|
+
lookup_identifiers()
|
130
|
+
|
131
|
+
# Validate everything in query before proceeding.
|
132
|
+
validate!
|
133
|
+
|
134
|
+
# Generate selection.
|
135
|
+
code = []
|
136
|
+
code << selection.codegen_select()
|
137
|
+
|
138
|
+
# Generate condition functions.
|
139
|
+
conditions.each_with_index do |condition, index|
|
140
|
+
condition.function_name ||= "__condition#{nextseq}"
|
141
|
+
code << condition.codegen
|
142
|
+
end
|
143
|
+
|
144
|
+
# Generate the invocation of the conditions.
|
145
|
+
conditionals = conditions.map {|condition| "#{condition.function_name}(cursor, data)"}.join(' and ')
|
146
|
+
conditionals = "true" if conditions.length == 0
|
147
|
+
|
148
|
+
# Generate aggregate() function.
|
149
|
+
code << "function aggregate(cursor, data)"
|
150
|
+
code << " cursor:set_session_idle(#{session_idle_time.to_i})" if session_idle_time.to_i > 0
|
151
|
+
code << " while cursor:next_session() do"
|
152
|
+
code << " while cursor:next() do"
|
153
|
+
code << " if #{conditionals} then"
|
154
|
+
code << " select(cursor, data)"
|
155
|
+
code << " end"
|
156
|
+
code << " end"
|
157
|
+
code << " end"
|
158
|
+
code << "end"
|
159
|
+
code << ""
|
160
|
+
|
161
|
+
# Generate merge function.
|
162
|
+
code << selection.codegen_merge()
|
163
|
+
|
164
|
+
return code.join("\n")
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
####################################
|
169
|
+
# Utility
|
170
|
+
####################################
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
# Generates a sequence number used for uniquely naming objects and
|
175
|
+
# functions in the query.
|
176
|
+
def nextseq
|
177
|
+
@sequence = (@sequence || 0) + 1
|
178
|
+
end
|
179
|
+
|
180
|
+
# Looks up all actions and properties that are missing an identifier.
|
181
|
+
def lookup_identifiers
|
182
|
+
# Find all the actions on conditions that are missing an id.
|
183
|
+
actions = []
|
184
|
+
conditions.each do |condition|
|
185
|
+
if condition.action.is_a?(SkyDB::Action) && condition.action.id.to_i == 0
|
186
|
+
actions << condition.action
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Lookup all the actions.
|
191
|
+
if actions.length > 0
|
192
|
+
client.lookup(:actions => actions)
|
193
|
+
end
|
194
|
+
|
195
|
+
return nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
class SkyDB
|
2
|
+
class Query
|
3
|
+
# The 'after' condition filters out selection only after the condition
|
4
|
+
# has been fulfilled.
|
5
|
+
class After
|
6
|
+
##########################################################################
|
7
|
+
#
|
8
|
+
# Constructor
|
9
|
+
#
|
10
|
+
##########################################################################
|
11
|
+
|
12
|
+
def initialize(action=nil, options={})
|
13
|
+
options.merge!(action.is_a?(Hash) ? action : {:action => action})
|
14
|
+
self.action = options[:action]
|
15
|
+
self.function_name = options[:function_name]
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
##########################################################################
|
20
|
+
#
|
21
|
+
# Attributes
|
22
|
+
#
|
23
|
+
##########################################################################
|
24
|
+
|
25
|
+
# The function name to use when generating the code.
|
26
|
+
attr_accessor :function_name
|
27
|
+
|
28
|
+
# The action to match. If set to a string or id then it is automatically
|
29
|
+
# wrapped in an Action object.
|
30
|
+
attr_reader :action
|
31
|
+
|
32
|
+
def action=(value)
|
33
|
+
if value.is_a?(Symbol)
|
34
|
+
@action = :enter
|
35
|
+
elsif value.is_a?(String)
|
36
|
+
@action = SkyDB::Action.new(:name => value)
|
37
|
+
elsif value.is_a?(Fixnum)
|
38
|
+
@action = SkyDB::Action.new(:id => value)
|
39
|
+
elsif value.is_a?(SkyDB::Action)
|
40
|
+
@action = value
|
41
|
+
else
|
42
|
+
@action = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
##########################################################################
|
48
|
+
#
|
49
|
+
# Methods
|
50
|
+
#
|
51
|
+
##########################################################################
|
52
|
+
|
53
|
+
##################################
|
54
|
+
# Validation
|
55
|
+
##################################
|
56
|
+
|
57
|
+
# Validates that the object is correct before executing a codegen.
|
58
|
+
def validate!
|
59
|
+
# Require the action identifier.
|
60
|
+
if action.nil? || action.id.to_i == 0
|
61
|
+
raise SkyDB::Query::ValidationError.new("Action with non-zero identifier required.")
|
62
|
+
end
|
63
|
+
|
64
|
+
# Require the function name. This should be set automatically by the
|
65
|
+
# query.
|
66
|
+
if function_name.to_s.index(/^\w+$/).nil?
|
67
|
+
raise SkyDB::Query::ValidationError.new("Invalid function name '#{function_name.to_s}'.")
|
68
|
+
end
|
69
|
+
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
##################################
|
75
|
+
# Codegen
|
76
|
+
##################################
|
77
|
+
|
78
|
+
# Generates Lua code to match a given action.
|
79
|
+
def codegen(options={})
|
80
|
+
header, body, footer = "function #{function_name.to_s}(cursor, data)\n", [], "end\n"
|
81
|
+
|
82
|
+
# If the action is :enter then just check for the beginning of a session.
|
83
|
+
if action == :enter
|
84
|
+
body << "return (cursor.session_event_index == 0)"
|
85
|
+
else
|
86
|
+
# Only move to the next event if directed to by the options.
|
87
|
+
body << "repeat"
|
88
|
+
body << " if cursor.event.action_id == #{action.id.to_i} then"
|
89
|
+
body << " cursor:next()"
|
90
|
+
body << " return true"
|
91
|
+
body << " end"
|
92
|
+
body << "until not cursor:next()"
|
93
|
+
body << "return false"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Indent body and return.
|
97
|
+
body.map! {|line| " " + line}
|
98
|
+
return header + body.join("\n") + "\n" + footer
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class SkyDB
|
2
|
+
class Query
|
3
|
+
class Ast
|
4
|
+
module SelectionFieldSyntaxNode
|
5
|
+
# Generates the SelectionField object from the node.
|
6
|
+
def generate
|
7
|
+
field = SkyDB::Query::SelectionField.new()
|
8
|
+
|
9
|
+
# If there is an expression present then use it.
|
10
|
+
if respond_to?('expression')
|
11
|
+
field.expression = expression.text_value
|
12
|
+
|
13
|
+
# Otherwise we'll typically use the whole value unless there is an
|
14
|
+
# aggregation type mentioned. An example of this is: "count()".
|
15
|
+
elsif !respond_to?('aggregation_type')
|
16
|
+
field.expression = text_value
|
17
|
+
end
|
18
|
+
|
19
|
+
field.alias_name = alias_name.text_value if respond_to?('alias_name')
|
20
|
+
field.aggregation_type = aggregation_type.text_value.downcase.to_sym if respond_to?('aggregation_type')
|
21
|
+
return field
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class SkyDB
|
2
|
+
class Query
|
3
|
+
class Ast
|
4
|
+
module SelectionFieldsSyntaxNode
|
5
|
+
# Generates a list of selection fields.
|
6
|
+
def generate
|
7
|
+
fields = []
|
8
|
+
Treetop.search(self, SelectionFieldSyntaxNode).each do |field_node|
|
9
|
+
fields << field_node.generate()
|
10
|
+
end
|
11
|
+
return fields
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class SkyDB
|
2
|
+
class Query
|
3
|
+
class Ast
|
4
|
+
module SelectionGroupSyntaxNode
|
5
|
+
# Generates the SelectionGroup object from the node.
|
6
|
+
def generate
|
7
|
+
group = SkyDB::Query::SelectionGroup.new(
|
8
|
+
:expression => (respond_to?('expression') ? expression.text_value : text_value)
|
9
|
+
)
|
10
|
+
group.alias_name = alias_name.text_value if respond_to?('alias_name')
|
11
|
+
return group
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class SkyDB
|
2
|
+
class Query
|
3
|
+
class Ast
|
4
|
+
module SelectionGroupsSyntaxNode
|
5
|
+
# Generates a list of selection groups.
|
6
|
+
def generate
|
7
|
+
groups = []
|
8
|
+
Treetop.search(self, SelectionGroupSyntaxNode).each do |group_node|
|
9
|
+
groups << group_node.generate()
|
10
|
+
end
|
11
|
+
return groups
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'skydb/query/selection_fields_parse_error'
|
2
|
+
require 'skydb/query/selection_field'
|
3
|
+
|
4
|
+
require 'skydb/query/selection_groups_parse_error'
|
5
|
+
require 'skydb/query/selection_group'
|
6
|
+
|
7
|
+
require 'skydb/query/ast/selection_fields_syntax_node'
|
8
|
+
require 'skydb/query/ast/selection_field_syntax_node'
|
9
|
+
require 'skydb/query/ast/selection_groups_syntax_node'
|
10
|
+
require 'skydb/query/ast/selection_group_syntax_node'
|
11
|
+
require 'skydb/query/selection_fields_grammar'
|
12
|
+
require 'skydb/query/selection_groups_grammar'
|
13
|
+
|
14
|
+
class SkyDB
|
15
|
+
class Query
|
16
|
+
# The selection object contains a list of all fields and their aliases.
|
17
|
+
# Selection fields can include simple properties as well as aggregation
|
18
|
+
# functions.
|
19
|
+
class Selection
|
20
|
+
##########################################################################
|
21
|
+
#
|
22
|
+
# Static Methods
|
23
|
+
#
|
24
|
+
##########################################################################
|
25
|
+
|
26
|
+
# Parses a string into a list of selection fields.
|
27
|
+
#
|
28
|
+
# @param [String] str A formatted list of fields to select.
|
29
|
+
#
|
30
|
+
# @return [Array] An array of selection fields.
|
31
|
+
def self.parse_fields(str)
|
32
|
+
# Parse the selection fields string.
|
33
|
+
parser = SelectionFieldsGrammarParser.new()
|
34
|
+
ast = parser.parse(str)
|
35
|
+
|
36
|
+
# If there was a problem then throw a parse error.
|
37
|
+
if ast.nil?
|
38
|
+
raise SkyDB::Query::SelectionFieldsParseError.new(parser.failure_reason,
|
39
|
+
:line => parser.failure_line,
|
40
|
+
:column => parser.failure_column
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
return ast.generate
|
45
|
+
end
|
46
|
+
|
47
|
+
# Parses a string into a list of selection groups.
|
48
|
+
#
|
49
|
+
# @param [String] str A formatted list of fields to group by.
|
50
|
+
#
|
51
|
+
# @return [Array] An array of selection groups.
|
52
|
+
def self.parse_groups(str)
|
53
|
+
# Parse the selection groups string.
|
54
|
+
parser = SelectionGroupsGrammarParser.new()
|
55
|
+
ast = parser.parse(str)
|
56
|
+
|
57
|
+
# If there was a problem then throw a parse error.
|
58
|
+
if ast.nil?
|
59
|
+
raise SkyDB::Query::SelectionGroupsParseError.new(parser.failure_reason,
|
60
|
+
:line => parser.failure_line,
|
61
|
+
:column => parser.failure_column
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
return ast.generate
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
##########################################################################
|
70
|
+
#
|
71
|
+
# Constructor
|
72
|
+
#
|
73
|
+
##########################################################################
|
74
|
+
|
75
|
+
def initialize(options={})
|
76
|
+
self.fields = options[:fields] || []
|
77
|
+
self.groups = options[:groups] || []
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
##########################################################################
|
82
|
+
#
|
83
|
+
# Attributes
|
84
|
+
#
|
85
|
+
##########################################################################
|
86
|
+
|
87
|
+
# A list of fields that will be returned from the server.
|
88
|
+
attr_accessor :fields
|
89
|
+
|
90
|
+
# A list of expressions to group the returned data by.
|
91
|
+
attr_accessor :groups
|
92
|
+
|
93
|
+
|
94
|
+
##########################################################################
|
95
|
+
#
|
96
|
+
# Methods
|
97
|
+
#
|
98
|
+
##########################################################################
|
99
|
+
|
100
|
+
####################################
|
101
|
+
# Helpers
|
102
|
+
####################################
|
103
|
+
|
104
|
+
# Adds a list of fields to the selection.
|
105
|
+
#
|
106
|
+
# @param [String] args A list of fields to add to the selection.
|
107
|
+
#
|
108
|
+
# @return [Selection] The selection object is returned.
|
109
|
+
def select(*args)
|
110
|
+
args.each do |arg|
|
111
|
+
if arg.is_a?(String)
|
112
|
+
self.fields = self.fields.concat(SkyDB::Query::Selection.parse_fields(arg))
|
113
|
+
elsif arg.is_a?(Symbol)
|
114
|
+
self.fields << SelectionField.new(:expression => arg.to_s)
|
115
|
+
else
|
116
|
+
raise "Invalid selection argument: #{arg} (#{arg.class})"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
return self
|
121
|
+
end
|
122
|
+
|
123
|
+
# Adds one or more grouping fields to the selection of the query.
|
124
|
+
#
|
125
|
+
# @param [String] args A list of groups to add to the selection.
|
126
|
+
#
|
127
|
+
# @return [Selection] The selection object is returned.
|
128
|
+
def group_by(*args)
|
129
|
+
args.each do |arg|
|
130
|
+
if arg.is_a?(String)
|
131
|
+
self.groups = self.groups.concat(SkyDB::Query::Selection.parse_groups(arg))
|
132
|
+
elsif arg.is_a?(Symbol)
|
133
|
+
self.groups << SelectionGroup.new(:expression => arg.to_s)
|
134
|
+
else
|
135
|
+
raise "Invalid group by argument: #{arg} (#{arg.class})"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
return self
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
####################################
|
144
|
+
# Validation
|
145
|
+
####################################
|
146
|
+
|
147
|
+
# Validates that all the elements of the query are valid.
|
148
|
+
def validate!
|
149
|
+
# Require that at least one field exist.
|
150
|
+
if fields.length == 0
|
151
|
+
raise SkyDB::Query::ValidationError.new("At least one selection field is required for #{self.inspect}.")
|
152
|
+
end
|
153
|
+
|
154
|
+
fields.each do |field|
|
155
|
+
field.validate!
|
156
|
+
end
|
157
|
+
|
158
|
+
groups.each do |group|
|
159
|
+
group.validate!
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
####################################
|
165
|
+
# Codegen
|
166
|
+
####################################
|
167
|
+
|
168
|
+
# Generates Lua code for the aggregation based on the selection.
|
169
|
+
def codegen_select
|
170
|
+
header, body, footer = "function select(cursor, data)\n", [], "end\n"
|
171
|
+
|
172
|
+
# Setup target object.
|
173
|
+
body << "target = data"
|
174
|
+
body << "" if groups.length > 0
|
175
|
+
|
176
|
+
# Initialize groups.
|
177
|
+
groups.each do |group|
|
178
|
+
body << "group_value = #{group.accessor}"
|
179
|
+
body << "if cursor:eos() or cursor:eof() then group_value = -1 end" if group.expression == 'action_id'
|
180
|
+
body << "if target[group_value] == nil then"
|
181
|
+
body << " target[group_value] = {}"
|
182
|
+
body << "end"
|
183
|
+
body << "target = target[group_value]"
|
184
|
+
body << ""
|
185
|
+
end
|
186
|
+
|
187
|
+
# Generate the assignment for each field.
|
188
|
+
fields.each do |field|
|
189
|
+
alias_name = field.target_name
|
190
|
+
|
191
|
+
case field.aggregation_type
|
192
|
+
when nil
|
193
|
+
body << "target.#{alias_name} = #{field.accessor}"
|
194
|
+
when :count
|
195
|
+
body << "target.#{alias_name} = (target.#{alias_name} or 0) + 1"
|
196
|
+
when :sum
|
197
|
+
body << "target.#{alias_name} = (target.#{alias_name} or 0) + #{field.accessor}"
|
198
|
+
when :min
|
199
|
+
body << "if(target.#{alias_name} == nil or target.#{alias_name} > #{field.accessor}) then"
|
200
|
+
body << " target.#{alias_name} = #{field.accessor}"
|
201
|
+
body << "end"
|
202
|
+
when :max
|
203
|
+
body << "if(target.#{alias_name} == nil or target.#{alias_name} < #{field.accessor}) then"
|
204
|
+
body << " target.#{alias_name} = #{field.accessor}"
|
205
|
+
body << "end"
|
206
|
+
else
|
207
|
+
raise StandardError.new("Invalid aggregation type: #{field.aggregation_type}")
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Indent body and return.
|
212
|
+
body.map! {|line| " " + line}
|
213
|
+
return header + body.join("\n") + "\n" + footer
|
214
|
+
end
|
215
|
+
|
216
|
+
# Generates Lua code for the merge function.
|
217
|
+
def codegen_merge
|
218
|
+
header, body, footer = "function merge(results, data)\n", [], "end\n"
|
219
|
+
|
220
|
+
# Open group loops.
|
221
|
+
groups.each_with_index do |group, index|
|
222
|
+
data_item = "data" + (0...index).to_a.map {|i| "[k#{i}]"}.join('')
|
223
|
+
results_item = "results" + (0..index).to_a.map {|i| "[k#{i}]"}.join('')
|
224
|
+
body << "#{' ' * index}for k#{index},v#{index} in pairs(#{data_item}) do"
|
225
|
+
body << "#{' ' * index} if #{results_item} == nil then #{results_item} = {} end"
|
226
|
+
end
|
227
|
+
|
228
|
+
indent = ' ' * groups.length
|
229
|
+
body << "#{indent}a = results" + (0...groups.length).to_a.map {|i| "[k#{i}]"}.join('')
|
230
|
+
body << "#{indent}b = data" + (0...groups.length).to_a.map {|i| "[k#{i}]"}.join('')
|
231
|
+
|
232
|
+
# Generate the merge for each field.
|
233
|
+
fields.each do |field|
|
234
|
+
alias_name = field.target_name
|
235
|
+
|
236
|
+
case field.aggregation_type
|
237
|
+
when nil
|
238
|
+
body << "#{indent}a.#{alias_name} = b.#{alias_name}"
|
239
|
+
when :count
|
240
|
+
body << "#{indent}a.#{alias_name} = (a.#{alias_name} or 0) + (b.#{alias_name} or 0)"
|
241
|
+
when :sum
|
242
|
+
body << "#{indent}a.#{alias_name} = (a.#{alias_name} or 0) + (b.#{alias_name} or 0)"
|
243
|
+
when :min
|
244
|
+
body << "#{indent}if(a.#{alias_name} == nil or a.#{alias_name} > b.#{alias_name}) then"
|
245
|
+
body << "#{indent} a.#{alias_name} = b.#{alias_name}"
|
246
|
+
body << "#{indent}end"
|
247
|
+
when :max
|
248
|
+
body << "#{indent}if(a.#{alias_name} == nil or a.#{alias_name} < b.#{alias_name}) then"
|
249
|
+
body << "#{indent} a.#{alias_name} = b.#{alias_name}"
|
250
|
+
body << "#{indent}end"
|
251
|
+
else
|
252
|
+
raise StandardError.new("Invalid aggregation type: #{field.aggregation_type}")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Close group loops.
|
257
|
+
groups.reverse.each_with_index do |group, index|
|
258
|
+
body << "#{' ' * (groups.length-index-1)}end"
|
259
|
+
end
|
260
|
+
|
261
|
+
# Indent body and return.
|
262
|
+
body.map! {|line| " " + line}
|
263
|
+
return header + body.join("\n") + "\n" + footer
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|