simply_stored 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/lib/simply_stored/class_methods_base.rb +31 -0
  2. data/lib/simply_stored/couch/belongs_to.rb +117 -0
  3. data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
  4. data/lib/simply_stored/couch/has_many.rb +148 -0
  5. data/lib/simply_stored/couch/has_one.rb +93 -0
  6. data/lib/simply_stored/couch/validations.rb +74 -0
  7. data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
  8. data/lib/simply_stored/couch/views.rb +1 -0
  9. data/lib/simply_stored/couch.rb +278 -0
  10. data/lib/simply_stored/instance_methods.rb +143 -0
  11. data/lib/simply_stored/simpledb/associations.rb +196 -0
  12. data/lib/simply_stored/simpledb/attributes.rb +173 -0
  13. data/lib/simply_stored/simpledb/storag.rb +85 -0
  14. data/lib/simply_stored/simpledb/validations.rb +88 -0
  15. data/lib/simply_stored/simpledb.rb +212 -0
  16. data/lib/simply_stored/storage.rb +93 -0
  17. data/lib/simply_stored.rb +9 -0
  18. data/test/custom_views_test.rb +33 -0
  19. data/test/fixtures/couch.rb +182 -0
  20. data/test/fixtures/simpledb/item.rb +11 -0
  21. data/test/fixtures/simpledb/item_daddy.rb +8 -0
  22. data/test/fixtures/simpledb/log_item.rb +3 -0
  23. data/test/fixtures/simpledb/namespace_bar.rb +5 -0
  24. data/test/fixtures/simpledb/namespace_foo.rb +7 -0
  25. data/test/fixtures/simpledb/protected_item.rb +3 -0
  26. data/test/simply_stored_couch_test.rb +1684 -0
  27. data/test/simply_stored_simpledb_test.rb +1341 -0
  28. data/test/test_helper.rb +22 -0
  29. data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
  30. data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
  31. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
  32. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
  33. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
  34. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
  35. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
  36. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
  37. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
  38. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
  39. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
  40. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
  41. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
  42. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
  43. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
  44. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
  45. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
  46. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
  47. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
  48. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
  49. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
  50. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
  51. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
  52. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
  53. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
  54. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
  55. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
  56. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
  57. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
  58. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
  59. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
  60. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
  61. data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
  62. data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
  63. data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
  64. data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
  65. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
  66. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
  67. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
  68. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
  69. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
  70. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
  71. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
  72. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
  73. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
  74. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
  75. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
  76. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
  77. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
  78. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
  79. data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
  80. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
  81. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
  82. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
  83. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
  84. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
  85. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
  86. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
  87. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
  88. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
  89. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
  90. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
  91. data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
  92. data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
  93. data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
  94. data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
  95. data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
  96. data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
  97. data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
  98. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
  99. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
  100. data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
  101. data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
  102. data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
  103. data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
  104. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
  105. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
  106. data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
  107. data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
  108. data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
  109. data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
  110. data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
  111. data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
  112. data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
  113. data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
  114. data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
  115. data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
  116. data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
  117. data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
  118. data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
  119. data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
  120. data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
  121. 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,3 @@
1
+ class SimplerDB
2
+ VERSION = '0.2'
3
+ 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