simply_stored 0.1.4
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/lib/simply_stored/class_methods_base.rb +31 -0
- data/lib/simply_stored/couch/belongs_to.rb +117 -0
- data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
- data/lib/simply_stored/couch/has_many.rb +148 -0
- data/lib/simply_stored/couch/has_one.rb +93 -0
- data/lib/simply_stored/couch/validations.rb +74 -0
- data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
- data/lib/simply_stored/couch/views.rb +1 -0
- data/lib/simply_stored/couch.rb +278 -0
- data/lib/simply_stored/instance_methods.rb +143 -0
- data/lib/simply_stored/simpledb/associations.rb +196 -0
- data/lib/simply_stored/simpledb/attributes.rb +173 -0
- data/lib/simply_stored/simpledb/storag.rb +85 -0
- data/lib/simply_stored/simpledb/validations.rb +88 -0
- data/lib/simply_stored/simpledb.rb +212 -0
- data/lib/simply_stored/storage.rb +93 -0
- data/lib/simply_stored.rb +9 -0
- data/test/custom_views_test.rb +33 -0
- data/test/fixtures/couch.rb +182 -0
- data/test/fixtures/simpledb/item.rb +11 -0
- data/test/fixtures/simpledb/item_daddy.rb +8 -0
- data/test/fixtures/simpledb/log_item.rb +3 -0
- data/test/fixtures/simpledb/namespace_bar.rb +5 -0
- data/test/fixtures/simpledb/namespace_foo.rb +7 -0
- data/test/fixtures/simpledb/protected_item.rb +3 -0
- data/test/simply_stored_couch_test.rb +1684 -0
- data/test/simply_stored_simpledb_test.rb +1341 -0
- data/test/test_helper.rb +22 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
- data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
- data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
- data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
- data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
- data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
- data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
- data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
- data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
- data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
- data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
- data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
- data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
- data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
- data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
- data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
- data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
- data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
- data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
- data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
- data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
- data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
- data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
- data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
- data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
- data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
- data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
- data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
- data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
- metadata +182 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'dhaka'
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module SimplerDB
|
|
6
|
+
# Uses the lexer/parser/evaluator to perform the query and do simple paging
|
|
7
|
+
class QueryExecutor
|
|
8
|
+
ERROR_MARKER = ">>>"
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@lexer = Dhaka::Lexer.new(QueryLexerSpec)
|
|
12
|
+
@parser = Dhaka::Parser.new(QueryGrammar)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Execute the query
|
|
16
|
+
def do_query(query, domain, max = 100, token = 0)
|
|
17
|
+
parse_result = @parser.parse(@lexer.lex(query))
|
|
18
|
+
token = 0 if token.nil?
|
|
19
|
+
|
|
20
|
+
case parse_result
|
|
21
|
+
when Dhaka::TokenizerErrorResult
|
|
22
|
+
raise tokenize_error_message(parse_result.unexpected_char_index, query)
|
|
23
|
+
when Dhaka::ParseErrorResult
|
|
24
|
+
raise parse_error_message(parse_result.unexpected_token, query)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
items = QueryEvaluator.new(domain).evaluate(parse_result)
|
|
28
|
+
results = []
|
|
29
|
+
count = 0
|
|
30
|
+
items.each do |item|
|
|
31
|
+
break if results.size == max
|
|
32
|
+
results << item if count >= token
|
|
33
|
+
count += 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if (count == items.size)
|
|
37
|
+
return results,nil
|
|
38
|
+
else
|
|
39
|
+
return results,count
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# From dhaka examples
|
|
44
|
+
def parse_error_message(unexpected_token, program)
|
|
45
|
+
if unexpected_token.symbol_name == Dhaka::END_SYMBOL_NAME
|
|
46
|
+
"Unexpected end of file."
|
|
47
|
+
else
|
|
48
|
+
"Unexpected token #{unexpected_token.symbol_name}:\n#{program.dup.insert(unexpected_token.input_position - 1, ERROR_MARKER)}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def tokenize_error_message(unexpected_char_index, program)
|
|
53
|
+
"Unexpected character #{program[unexpected_char_index - 1].chr}:\n#{program.dup.insert(unexpected_char_index - 1, ERROR_MARKER)}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def evaluation_error_message(evaluation_result, program)
|
|
57
|
+
"#{evaluation_result.exception}:\n#{program.dup.insert(evaluation_result.node.tokens[0].input_position, ERROR_MARKER)}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The SimpleDB query language grammar, as defined at
|
|
63
|
+
# http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_Query.html
|
|
64
|
+
class QueryGrammar < Dhaka::Grammar
|
|
65
|
+
for_symbol(Dhaka::START_SYMBOL_NAME) do
|
|
66
|
+
start %w| predicates |
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
for_symbol('predicates') do
|
|
70
|
+
single_predicate %w| predicate |
|
|
71
|
+
not_predicate %w| not predicate |
|
|
72
|
+
intersection %w| predicate intersection predicates |
|
|
73
|
+
not_intersection %w| not predicate intersection predicates |
|
|
74
|
+
union %w| predicate union predicates |
|
|
75
|
+
not_union %w| not predicate union predicates |
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
for_symbol('predicate') do
|
|
79
|
+
attribute_comparison %w| [ attribute_comparison ] |
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
for_symbol('attribute_comparison') do
|
|
83
|
+
single_comparison %w| identifier comp_op constant |
|
|
84
|
+
not_comparison %w| not identifier comp_op constant |
|
|
85
|
+
and_comparison %w| identifier comp_op constant and attribute_comparison |
|
|
86
|
+
not_and_comparison %w| not identifier comp_op constant and attribute_comparison |
|
|
87
|
+
or_comparison %w| identifier comp_op constant or attribute_comparison |
|
|
88
|
+
not_or_comparison %w| not identifier comp_op constant or attribute_comparison |
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
for_symbol('comp_op') do
|
|
92
|
+
equal %w| = |
|
|
93
|
+
greater_than %w| > |
|
|
94
|
+
less_than %w| < |
|
|
95
|
+
greater_or_equal %w| >= |
|
|
96
|
+
less_or_equal %w| <= |
|
|
97
|
+
not_equal %w| != |
|
|
98
|
+
starts_with %w| starts-with |
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
for_symbol('identifier') do
|
|
102
|
+
identifier %w| quoted_string |
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
for_symbol('constant') do
|
|
106
|
+
constant %w| quoted_string |
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The lexer for the query language.
|
|
112
|
+
class QueryLexerSpec < Dhaka::LexerSpecification
|
|
113
|
+
|
|
114
|
+
%w| = > < >= <= != starts-with |.each do |op|
|
|
115
|
+
for_pattern(op) do
|
|
116
|
+
create_token(op)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
for_pattern('\[') do
|
|
121
|
+
create_token('[')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
for_pattern('\]') do
|
|
125
|
+
create_token(']')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
for_pattern('\s+') do
|
|
129
|
+
# ignore whitespace
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
KEYWORDS = %w| not and or union intersection |
|
|
133
|
+
KEYWORDS.each do |keyword|
|
|
134
|
+
for_pattern(keyword) do
|
|
135
|
+
create_token(keyword)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
for_pattern("'(\\\\'|[^'])+'") do
|
|
140
|
+
create_token 'quoted_string'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The query evaluator. This class acts on the parse tree and will return
|
|
146
|
+
# a list of item names that match the query from the evaluate method.
|
|
147
|
+
class QueryEvaluator < Dhaka::Evaluator
|
|
148
|
+
self.grammar = QueryGrammar
|
|
149
|
+
|
|
150
|
+
def initialize(domain = nil)
|
|
151
|
+
@domain = domain
|
|
152
|
+
@predicate_identifier = nil
|
|
153
|
+
@all_items = nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
define_evaluation_rules do
|
|
157
|
+
|
|
158
|
+
for_single_predicate do
|
|
159
|
+
evaluate(child_nodes[0])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
for_not_predicate do
|
|
163
|
+
results = evaluate(child_nodes[1])
|
|
164
|
+
all_items.difference(results)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
for_intersection do
|
|
168
|
+
results = evaluate(child_nodes[0])
|
|
169
|
+
results.intersection(evaluate(child_nodes[2]))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# TODO Nots are probably not handled correctly. Need to play with AWS to find out for sure.
|
|
173
|
+
for_not_intersection do
|
|
174
|
+
results = evaluate(child_nodes[1])
|
|
175
|
+
all_items.difference(results.intersection(evaluate(child_nodes[3])))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
for_union do
|
|
179
|
+
results = evaluate(child_nodes[0])
|
|
180
|
+
results.union(evaluate(child_nodes[2]))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
for_not_union do
|
|
184
|
+
results = evaluate(child_nodes[1])
|
|
185
|
+
all_items.difference(results.union(evaluate(child_nodes[3])))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
for_attribute_comparison do
|
|
189
|
+
evaluate(child_nodes[1])
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
for_single_comparison do
|
|
193
|
+
do_comparison(evaluate(child_nodes[0]), evaluate(child_nodes[1]), evaluate(child_nodes[2]))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
for_not_comparison do
|
|
197
|
+
do_comparison(evaluate(child_nodes[1]), evaluate(child_nodes[2]), evaluate(child_nodes[3]), true)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
for_and_comparison do
|
|
201
|
+
results = do_comparison(evaluate(child_nodes[0]), evaluate(child_nodes[1]), evaluate(child_nodes[2]))
|
|
202
|
+
results.intersection(evaluate(child_nodes[4]))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
for_not_and_comparison do
|
|
206
|
+
results = do_comparison(evaluate(child_nodes[1]), evaluate(child_nodes[2]), evaluate(child_nodes[3]), true)
|
|
207
|
+
results.intersection(evaluate(child_nodes[5]))
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
for_or_comparison do
|
|
211
|
+
results = do_comparison(evaluate(child_nodes[0]), evaluate(child_nodes[1]), evaluate(child_nodes[2]))
|
|
212
|
+
results.union(evaluate(child_nodes[4]))
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
for_not_or_comparison do
|
|
216
|
+
results = do_comparison(evaluate(child_nodes[1]), evaluate(child_nodes[2]), evaluate(child_nodes[3]), true)
|
|
217
|
+
results.union(evaluate(child_nodes[5]))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
for_equal { lambda { |v1, v2| v1 == v2 } }
|
|
221
|
+
for_greater_than { lambda { |v1, v2| v1 > v2 } }
|
|
222
|
+
for_less_than { lambda { |v1, v2| v1 < v2 } }
|
|
223
|
+
for_greater_or_equal { lambda { |v1, v2| v1 >= v2 } }
|
|
224
|
+
for_less_or_equal { lambda { |v1, v2| v1 <= v2 } }
|
|
225
|
+
# TODO ['a1' != 'v2'] should return false if a1 has values v1 AND v2
|
|
226
|
+
for_not_equal { lambda { |v1, v2| v1 != v2 } }
|
|
227
|
+
for_starts_with { lambda { |v1, v2| v2[0...v1.length] == v1 } }
|
|
228
|
+
|
|
229
|
+
for_identifier { val(child_nodes[0]) }
|
|
230
|
+
for_constant { val(child_nodes[0]) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def val(node)
|
|
234
|
+
node.token.value.to_s[1..-2]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def all_items
|
|
238
|
+
unless @all_items
|
|
239
|
+
@all_items = @domain.items.collect { |k,v| k }.to_set
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
return @all_items
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Apply the given comparison params to every item in the domain
|
|
246
|
+
def do_comparison(identifier, op, constant, negate = false)
|
|
247
|
+
results = Set.new
|
|
248
|
+
|
|
249
|
+
if @domain
|
|
250
|
+
@domain.items.each_value do |item|
|
|
251
|
+
attrs = item.attributes[identifier]
|
|
252
|
+
|
|
253
|
+
attrs.each do |attr|
|
|
254
|
+
match = op.call(constant, attr.value)
|
|
255
|
+
if (match && !negate) || (negate && !match)
|
|
256
|
+
results << item
|
|
257
|
+
break
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
results
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'simplerdb/servlet'
|
|
3
|
+
require 'webrick'
|
|
4
|
+
|
|
5
|
+
# Runs the SimplerDB server
|
|
6
|
+
module SimplerDB
|
|
7
|
+
class Server
|
|
8
|
+
|
|
9
|
+
# Create the server running on the given port
|
|
10
|
+
def initialize(port = nil)
|
|
11
|
+
@port = port || 8087
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Run the server on the configured port.
|
|
15
|
+
def start
|
|
16
|
+
config = { :Port => @port, :AccessLog => [] }
|
|
17
|
+
@server = WEBrick::HTTPServer.new(config)
|
|
18
|
+
@server.mount("/", SimplerDB::RESTServlet)
|
|
19
|
+
|
|
20
|
+
['INT', 'TERM'].each do |signal|
|
|
21
|
+
trap(signal) { @server.shutdown }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@server.start
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Shut down the server
|
|
28
|
+
def shutdown
|
|
29
|
+
@server.shutdown
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'webrick'
|
|
3
|
+
require 'builder'
|
|
4
|
+
require 'simplerdb/db'
|
|
5
|
+
require 'simplerdb/client_exception'
|
|
6
|
+
|
|
7
|
+
module SimplerDB
|
|
8
|
+
|
|
9
|
+
# A WEBrick servlet to handle API requests over REST
|
|
10
|
+
class RESTServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
11
|
+
|
|
12
|
+
def do_GET(req, res)
|
|
13
|
+
action = req.query["Action"]
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
if action.nil?
|
|
17
|
+
# Raise an error
|
|
18
|
+
raise ClientException.new(:MissingAction, "No action was supplied with this request")
|
|
19
|
+
else
|
|
20
|
+
# Process the action appropriately
|
|
21
|
+
xml = send("do_#{action.downcase}", req)
|
|
22
|
+
res.body = xml
|
|
23
|
+
res.status = 200
|
|
24
|
+
end
|
|
25
|
+
rescue ClientException => e
|
|
26
|
+
res.body = error_xml(e.code, e.msg)
|
|
27
|
+
res.status = 400 # What is the right status?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
res['content-type'] = 'text/xml'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias do_POST do_GET
|
|
34
|
+
alias do_DELETE do_GET
|
|
35
|
+
|
|
36
|
+
# Handle CreateDomain requests
|
|
37
|
+
def do_createdomain(req)
|
|
38
|
+
name = req.query["DomainName"]
|
|
39
|
+
DB.instance.create_domain(name)
|
|
40
|
+
build_result("CreateDomain")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Handle ListDomains requests
|
|
44
|
+
def do_listdomains(req)
|
|
45
|
+
max = req.query["MaxNumberOfDomains"]
|
|
46
|
+
next_token = req.query["NextToken"]
|
|
47
|
+
|
|
48
|
+
max = max.to_i if max
|
|
49
|
+
domains,token = DB.instance.list_domains(max, next_token)
|
|
50
|
+
|
|
51
|
+
build_result("ListDomains") do |doc|
|
|
52
|
+
doc.ListDomainsResult do
|
|
53
|
+
if domains
|
|
54
|
+
domains.each do |domain|
|
|
55
|
+
doc.DomainName domain
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if token
|
|
60
|
+
doc.NextToken token
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# DeleteDomains requests
|
|
68
|
+
def do_deletedomain(req)
|
|
69
|
+
name = req.query["DomainName"]
|
|
70
|
+
DB.instance.delete_domain(name)
|
|
71
|
+
build_result("DeleteDomain")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# PutAttributes requests
|
|
75
|
+
def do_putattributes(req)
|
|
76
|
+
domain_name = req.query["DomainName"]
|
|
77
|
+
item_name = req.query["ItemName"]
|
|
78
|
+
attrs = parse_attribute_args(req)
|
|
79
|
+
DB.instance.put_attributes(domain_name, item_name, attrs)
|
|
80
|
+
build_result("PutAttributes")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# DeleteAttributes requests
|
|
84
|
+
def do_deleteattributes(req)
|
|
85
|
+
domain_name = req.query["DomainName"]
|
|
86
|
+
item_name = req.query["ItemName"]
|
|
87
|
+
attrs = parse_attribute_args(req)
|
|
88
|
+
DB.instance.delete_attributes(domain_name, item_name, attrs)
|
|
89
|
+
build_result("DeleteAttributes")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# GetAttributes request
|
|
93
|
+
def do_getattributes(req)
|
|
94
|
+
domain_name = req.query["DomainName"]
|
|
95
|
+
item_name = req.query["ItemName"]
|
|
96
|
+
attr_name = req.query["AttributeName"]
|
|
97
|
+
attrs = DB.instance.get_attributes(domain_name, item_name)
|
|
98
|
+
|
|
99
|
+
build_result("GetAttributes") do |doc|
|
|
100
|
+
doc.GetAttributesResult do
|
|
101
|
+
if attrs
|
|
102
|
+
attrs.each do |attr|
|
|
103
|
+
doc.Attribute do
|
|
104
|
+
doc.Name attr.name
|
|
105
|
+
doc.Value attr.value
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Query request
|
|
114
|
+
def do_query(req)
|
|
115
|
+
domain_name = req.query["DomainName"]
|
|
116
|
+
max_items = req.query["MaxItems"].to_i
|
|
117
|
+
max_items = 100 if (max_items < 1 || max_items > 250)
|
|
118
|
+
next_token = req.query["NextToken"]
|
|
119
|
+
query = req.query["QueryExpression"]
|
|
120
|
+
|
|
121
|
+
results,token = DB.instance.query(domain_name, query, max_items, next_token)
|
|
122
|
+
build_result("Query") do |doc|
|
|
123
|
+
doc.QueryResult do
|
|
124
|
+
if results
|
|
125
|
+
results.each do |res|
|
|
126
|
+
doc.ItemName res.name
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if token
|
|
131
|
+
doc.NextToken token
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Build a result for the given action.
|
|
138
|
+
# The given block will add action-specific return fields.
|
|
139
|
+
def build_result(action)
|
|
140
|
+
xml = ''
|
|
141
|
+
doc = Builder::XmlMarkup.new(:target => xml)
|
|
142
|
+
doc.tag!("#{action}Response", :xmlns => "http://sdb.amazonaws.com/doc/2007-11-07") do
|
|
143
|
+
if block_given?
|
|
144
|
+
yield doc
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
doc.ResponseMetadata do
|
|
148
|
+
doc.RequestId "1234"
|
|
149
|
+
doc.BoxUsage "0"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
xml
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Return the error response for the given error code and message
|
|
157
|
+
def error_xml(code, msg)
|
|
158
|
+
xml = ''
|
|
159
|
+
doc = Builder::XmlMarkup.new(:target => xml)
|
|
160
|
+
|
|
161
|
+
doc.Response do
|
|
162
|
+
doc.Errors do
|
|
163
|
+
doc.Error do
|
|
164
|
+
doc.Code code.to_s
|
|
165
|
+
doc.Message msg
|
|
166
|
+
doc.BoxUsage "0"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
doc.RequestID "1234"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
xml
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def parse_attribute_args(req)
|
|
177
|
+
args = []
|
|
178
|
+
for i in (0...100)
|
|
179
|
+
name = req.query["Attribute.#{i}.Name"]
|
|
180
|
+
value = req.query["Attribute.#{i}.Value"]
|
|
181
|
+
replace = (req.query["Attribute.#{i}.Replace"] == "true")
|
|
182
|
+
if name && value
|
|
183
|
+
args << AttributeParam.new(name, value, replace)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
args
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require "test/unit"
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'right_aws'
|
|
4
|
+
require 'simplerdb/server'
|
|
5
|
+
|
|
6
|
+
# Test the live, running service over HTTP
|
|
7
|
+
class FunctionalTest < Test::Unit::TestCase
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@server = SimplerDB::Server.new(8087)
|
|
11
|
+
@thread = Thread.new { @server.start }
|
|
12
|
+
@sdb = RightAws::SdbInterface.new('access','secret',
|
|
13
|
+
{:server => 'localhost', :port => 8087, :protocol => 'http'})
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def teardown
|
|
18
|
+
@server.shutdown
|
|
19
|
+
@thread.join
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_domains
|
|
23
|
+
# Create/list domains
|
|
24
|
+
@sdb.create_domain("d1")
|
|
25
|
+
@sdb.create_domain("d2")
|
|
26
|
+
result = @sdb.list_domains
|
|
27
|
+
assert result[:domains].index("d1")
|
|
28
|
+
assert result[:domains].index("d2")
|
|
29
|
+
|
|
30
|
+
# List domains with paging
|
|
31
|
+
domains = []
|
|
32
|
+
count = 0
|
|
33
|
+
@sdb.list_domains(1) do |result|
|
|
34
|
+
domains += result[:domains]
|
|
35
|
+
count += 1
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
assert count == 2
|
|
40
|
+
assert domains.index("d1")
|
|
41
|
+
assert domains.index("d2")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_items
|
|
45
|
+
# Create some bands with albums + genre
|
|
46
|
+
@sdb.create_domain("bands")
|
|
47
|
+
|
|
48
|
+
attrs = {:albums => ['Being There', 'Summer Teeth'], :genre => "rock"}
|
|
49
|
+
@sdb.put_attributes("bands", "Wilco", attrs)
|
|
50
|
+
|
|
51
|
+
attrs = {:albums => ['OK Computer', 'Kid A'], :genre => "alternative"}
|
|
52
|
+
@sdb.put_attributes("bands", "Radiohead", attrs)
|
|
53
|
+
|
|
54
|
+
attrs = {:albums => ['The Soft Bulletin'], :genre => "alternative"}
|
|
55
|
+
@sdb.put_attributes("bands", "The Flaming Lips", attrs)
|
|
56
|
+
|
|
57
|
+
# Read the attributes back
|
|
58
|
+
results = @sdb.get_attributes("bands", "Wilco")
|
|
59
|
+
assert results[:attributes]["albums"].sort == ['Being There', 'Summer Teeth']
|
|
60
|
+
assert results[:attributes]["genre"] == ["rock"]
|
|
61
|
+
|
|
62
|
+
# Query the bands domain
|
|
63
|
+
results = @sdb.query("bands", "['genre' = 'alternative']")
|
|
64
|
+
assert results[:items].sort == ["Radiohead", "The Flaming Lips"]
|
|
65
|
+
|
|
66
|
+
results = @sdb.query("bands", "['albums' = 'Being There']")
|
|
67
|
+
assert results[:items] == ["Wilco"]
|
|
68
|
+
|
|
69
|
+
results = @sdb.query("bands", "['albums' starts-with 'OK' or 'albums' = 'The Soft Bulletin']")
|
|
70
|
+
assert results[:items].sort == ["Radiohead", "The Flaming Lips"]
|
|
71
|
+
|
|
72
|
+
# Modify and re-read attributes
|
|
73
|
+
@sdb.put_attributes("bands", "The Flaming Lips", {:albums => "Yoshimi Battles the Pink Robots"})
|
|
74
|
+
@sdb.put_attributes("bands", "The Flaming Lips", {:genre => "rock"}, :replace)
|
|
75
|
+
|
|
76
|
+
results = @sdb.get_attributes("bands", "The Flaming Lips")
|
|
77
|
+
assert results[:attributes]["albums"].sort == ["The Soft Bulletin", "Yoshimi Battles the Pink Robots"]
|
|
78
|
+
assert results[:attributes]["genre"] == ["rock"]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "test/unit"
|
|
2
|
+
require 'simplerdb/query_language'
|
|
3
|
+
require 'simplerdb/db'
|
|
4
|
+
|
|
5
|
+
# Tests the query evaluator
|
|
6
|
+
class QueryEvaluatorTest < Test::Unit::TestCase
|
|
7
|
+
include SimplerDB
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@db = DB.instance
|
|
11
|
+
@db.create_domain("test")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def teardown
|
|
15
|
+
@db.reset
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_single_predicate
|
|
19
|
+
bulk_insert('i1' => [["a1", "v1"], ["a2", "v2"], ["a3", "bcd"]],
|
|
20
|
+
'i2' => [["a1", "vv1"]])
|
|
21
|
+
|
|
22
|
+
assert_items 'i1', @db.query("test", "['a1' = 'v1']")
|
|
23
|
+
assert_items 'i1', @db.query("test", "['a1' != 'v2']")
|
|
24
|
+
assert_items 'i1', @db.query("test", "['a1' >= 'v1']")
|
|
25
|
+
assert_items 'i1', @db.query("test", "['a1' <= 'v1']")
|
|
26
|
+
assert_items 'i1', @db.query("test", "['a3' starts-with 'bc']")
|
|
27
|
+
assert_items 'i1', @db.query("test", "['a3' > 'cde']")
|
|
28
|
+
assert_items 'i1', @db.query("test", "['a3' < 'abc']")
|
|
29
|
+
assert_items ['i1', 'i2'], @db.query("test", "['a1' = 'v1' or 'a1' = 'vv1']")
|
|
30
|
+
assert_items ['i1', 'i2'], @db.query("test", "['a1' <= 'v1']")
|
|
31
|
+
|
|
32
|
+
assert_empty @db.query("test", "['a2' != 'v2']")
|
|
33
|
+
assert_empty @db.query("test", "['a2' > 'v2']")
|
|
34
|
+
assert_empty @db.query("test", "['a2' = 'v1']")
|
|
35
|
+
assert_empty @db.query("testxxxxx", "['a2' = 'v2']")
|
|
36
|
+
assert_empty @db.query("test", "['a2' = 'v1']")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_ops
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def bulk_insert(items)
|
|
44
|
+
items.each do |name, attrs|
|
|
45
|
+
params = []
|
|
46
|
+
for attr in attrs
|
|
47
|
+
params << AttributeParam.new(attr[0], attr[1])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@db.put_attributes("test", name, params)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def assert_empty(results)
|
|
55
|
+
assert results[0].empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def assert_items(items, results)
|
|
59
|
+
results = results[0]
|
|
60
|
+
for item in items
|
|
61
|
+
found = false
|
|
62
|
+
for result in results
|
|
63
|
+
if result.name == item
|
|
64
|
+
found = true
|
|
65
|
+
break
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
assert found, "Missing item #{item}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|