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.
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