├── .classpath
├── .gitignore
├── .project
├── .settings
└── ch.epfl.lamp.sdt.core.prefs
├── Jetty.launch
├── README.markdown
├── buildfile
├── fakesdb.rb
├── ivy.xml
├── ivysettings.xml
└── src
├── main
├── resources
│ └── manifest.mf
├── scala
│ └── fakesdb
│ │ ├── Data.scala
│ │ ├── FakeSdbServlet.scala
│ │ ├── Jetty.scala
│ │ ├── Params.scala
│ │ ├── SelectParser.scala
│ │ ├── actions
│ │ ├── Action.scala
│ │ ├── BatchDeleteAttributes.scala
│ │ ├── BatchPutAttributes.scala
│ │ ├── ConditionalChecking.scala
│ │ ├── CreateDomain.scala
│ │ ├── DeleteAttributes.scala
│ │ ├── DeleteDomain.scala
│ │ ├── DomainMetadata.scala
│ │ ├── Errors.scala
│ │ ├── GetAttributes.scala
│ │ ├── ItemUpdates.scala
│ │ ├── ListDomains.scala
│ │ ├── PutAttributes.scala
│ │ └── Select.scala
│ │ └── stub
│ │ └── AmazonSimpleDbStub.scala
└── webapp
│ └── WEB-INF
│ └── web.xml
└── test
└── scala
└── fakesdb
├── AbstractFakeSdbTest.scala
├── BatchDeleteAttributesTest.scala
├── BatchPutAttributesTest.scala
├── CreateDomainTest.scala
├── DeleteAttributesTest.scala
├── DomainMetadataTest.scala
├── ErrorTest.scala
├── FlushDomainsTest.scala
├── GetAttributesTest.scala
├── ListDomainsTest.scala
├── PutAttributesTest.scala
├── SelectParserTest.scala
└── SelectTest.scala
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | bin
3 | .manager
4 | target
5 | boot
6 | lib
7 | lib_managed
8 | .scala_dependencies
9 | reports
10 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | fakesdb
4 |
5 |
6 |
7 |
8 |
9 | org.scala-ide.sdt.core.scalabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.scala-ide.sdt.core.scalanature
16 | org.eclipse.jdt.core.javanature
17 | org.apache.ivyde.eclipse.ivynature
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.settings/ch.epfl.lamp.sdt.core.prefs:
--------------------------------------------------------------------------------
1 | #Mon Jan 18 23:12:20 CST 2010
2 | =false
3 | Xcheck-null=false
4 | Xcheckinit=false
5 | Xdisable-assertions=false
6 | Xexperimental=false
7 | Xfuture=false
8 | Xlog-implicits=false
9 | Xno-uescape=false
10 | Xno-varargs-conversion=false
11 | Xstrict-warnings=false
12 | Xwarninit=false
13 | Yclosure-elim=false
14 | Ydead-code=false
15 | Ydetach=false
16 | Yinline=false
17 | Ylinearizer=rpo
18 | Yno-generic-signatures=false
19 | Yno-imports=false
20 | Yno-predefs=false
21 | Yself-in-annots=false
22 | Yspecialize=false
23 | Ysqueeze=on
24 | Ytailrecommend=false
25 | Ywarn-catches=false
26 | Ywarn-dead-code=false
27 | Ywarn-shadowing=false
28 | deprecation=true
29 | eclipse.preferences.version=1
30 | g=vars
31 | optimise=false
32 | scala.compiler.useProjectSettings=true
33 | target=jvm-1.5
34 | unchecked=false
35 |
--------------------------------------------------------------------------------
/Jetty.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Overview
2 | ========
3 |
4 | A fake version of Amazon's SimpleDB. Local, in-memory, consistent, deployed as a war.
5 |
6 | The entire REST API (Query, Select, etc.) is implemented in ~750 lines of Scala.
7 |
8 | Install
9 | =======
10 |
11 | You can get fakesdb from:
12 |
13 | 1. The [downloads](http://github.com/stephenh/fakesdb/downloads) page
14 |
15 | 2. The [http://repo.joist.ws](joist.ws) Maven repository, e.g. `com.bizo` + `fakesdb-testing_2.9.1` + `2.6.1` [here](http://repo.joist.ws/com/bizo/fakesdb-testing_2.9.1/)
16 |
17 | There are a few different modules:
18 |
19 | * `fakesdb-standalone` has all of the dependencies (including Scala) as an uber-jar for running from the CLI.
20 |
21 | You can run this as:
22 |
23 | `java -jar fakesdb-standalone-1.5.jar`
24 |
25 | And it will start up an embedded instance of Jetty on port 8080. You can pass `-Dport=X` to specify a different port.
26 |
27 | * `fakesdb-testing` is the dependencies (without Scala) as an uber-jar for embedding.
28 |
29 | From within a unit test, you can call:
30 |
31 | fakesdb.Jetty.apply(8080)
32 |
33 | To start up fakesdb on port 8080.
34 |
35 | * `fakesdb-servlet` is just the fakesdb classes (no dependencies) for running within your own webapp. E.g. add the `fakesdb.FakeSdbServlet` to your own `web.xml` file.
36 |
37 | You could also use this version for `AmazonSimpleDbStub` if you want to do in-memory only testing.
38 |
39 | * `fakesdb.war` is (was--it's not being published right now) to drop into your Tomcat/etc.
40 |
41 | Notes
42 | =====
43 |
44 | * To facilitate testing, issuing a `CreateDomain` command with `DomainName=_flush` will reset all of the data
45 |
46 | * If you're using the `typica` Java SimpleDB client, versions through 1.6 only use port 80, even when given a non-80 setting. So you'll either have to run `fakesdb` on port 80 or else redirect port 80 traffic to 8080 with a firewall rule.
47 |
48 | Changelog
49 | =========
50 |
51 | * 2.6.1 - 15 November 2012
52 | * Add `` to error responses (Alex Boisvert)
53 | * Build improvements (Alex Boisvert)
54 | * 2.5 - 6 Oct 2012
55 | * Add AmazonSimpleDbStub for in-memory testing of clients using the AWS Java SDK
56 | * Switch from sbt to buildr
57 | * 2.4 - 23 Aug 2011
58 | * Upgrade to Scala 2.9.0-1 and Jetty 8.0.0.RC0
59 | * Make new `fakesdb-testing.jar` which has jetty but not scala-library
60 | * 2.3 - 23 Aug 2011
61 | * Remove `Jetty` bootstrap classes from `fakesdb-servlet.jar`
62 | * 2.2 - 25 Apr 2011
63 | * BatchDeleteAttributes support (Alexander Gorkunov)
64 | * [Partial Select](http://aws.amazon.com/about-aws/whats-new/2009/02/19/new-features-for-amazon-simpledb/) support (Alexander Gorkunov)
65 | * Pre-2.2 various releases
66 |
67 | Todo
68 | ====
69 |
70 | * Loading the SDB URL in a browser (e.g. without a REST action) should display all of the current data
71 | * [Release It](http://www.pragprog.com/titles/mnee/release-it) talks about having "fake" (better term?) versions of systems like `fakesdb` purposefully lock up, fail, etc., to test how your real application responds--it would be cool to flip `fakesdb` into several error modes either via a web UI or meta-domains (like the current `_flush` domain)
72 |
73 | ## License
74 |
75 | Licensed under the terms of the Apache Software License v2.0.
76 |
77 | http://www.apache.org/licenses/LICENSE-2.0.html
78 |
--------------------------------------------------------------------------------
/buildfile:
--------------------------------------------------------------------------------
1 | Buildr.settings.build['scala.version'] = '2.10.4'
2 |
3 | require 'buildr/ivy_extension'
4 | require 'buildr/scala'
5 | require './fakesdb.rb'
6 |
7 | VERSION_NUMBER = ENV['version'] || 'SNAPSHOT'
8 |
9 | # to resolve the ${version} in the ivy.xml
10 | Java.java.lang.System.setProperty("version", VERSION_NUMBER)
11 |
12 | repositories.remote << "http://mirrors.ibiblio.org/maven2"
13 | repositories.release_to = 'sftp://joist.ws/var/www/joist.repo'
14 | repositories.release_to[:permissions] = 0644
15 |
16 | define FakeSDB::fakesdb do
17 | extend FakeSDB # project-specific extensions
18 |
19 | project.version = VERSION_NUMBER
20 | project.group = 'com.bizo'
21 | ivy.compile_conf(['servlet', 'war', 'buildtime']).test_conf('test')
22 |
23 | test.using :junit
24 |
25 | task "retrieve" do
26 | ivy.ivy4r.retrieve
27 | end
28 |
29 | package_with_sources
30 |
31 | file 'target/pom.xml' => task('ivy:makepom')
32 | package(:jar).pom.tap do |pom|
33 | pom.from 'target/pom.xml'
34 | end
35 |
36 | all_in_one_jar :id => "standalone",
37 | :libs => ["aws-java-sdk", "jetty", "servlet-api", "scala-library", "scala-reflect"]
38 |
39 | # without scala-library
40 | all_in_one_jar :id => "testing",
41 | :libs => ["aws-java-sdk", "jetty", "servlet-api"]
42 |
43 | # fakesdb-servlet is just fakesdb so you can set it up with your own web.xml
44 | package(:jar, :id => fakesdb("servlet")).tap do |pkg|
45 | pkg.exclude Dir[_(:target, :classes, "fakesdb") + "/Jetty*"]
46 | end
47 | end
48 |
49 |
--------------------------------------------------------------------------------
/fakesdb.rb:
--------------------------------------------------------------------------------
1 |
2 | # Returns scala version (from settings or else from $SCALA_HOME)
3 | def scala_version
4 | Buildr.settings.build['scala.version'] || begin
5 | fail "Unable to infer scala version" unless ENV["SCALA_HOME"] =~ /scala-([^\/]*)/
6 | version = Buildr.settings.build['scala.version'] = $1
7 | puts "Inferred Scala version #{version}"
8 | return version
9 | end
10 | end
11 |
12 | # Project-specific extensions
13 | module FakeSDB
14 | extend self
15 |
16 | # Return fakesdb artifact name embedding scala version, with optional suffix
17 | # e.g. fakesdb => "fakesdb_2.9.1"
18 | # fakesdb("standalone") => "fakesdb-standalone_2.9.1"
19 | def fakesdb(suffix = nil)
20 | short_scala_version = (scala_version =~ /^(\d+)\.(\d+).*/) ? "#{$1}.#{$2}" : fail # strip .RC suffix (if any)
21 | "fakesdb#{suffix ? "-" + suffix : ""}_#{short_scala_version}"
22 | end
23 |
24 | # Create all-in-one jar by merging dependencies from `lib` using prefixes
25 | #
26 | # e.g. all_in_one_jar :id => "standlone", :libs => ["aws-java-sdk", "jetty", ...]
27 | #
28 | def all_in_one_jar(options)
29 | name = options[:id] || fail("Missing :id")
30 | libs = options[:libs] || fail("Missing :id")
31 | package(:jar, :id => fakesdb(name)).tap do |pkg|
32 | pkg.enhance [task(:retrieve)]
33 | # double-enhance so merge happens after base jar is created
34 | pkg.enhance do
35 | pkg.enhance do
36 | libs.each do |prefix|
37 | Dir[_("lib") + "/#{prefix}-*.jar"].each do |dep|
38 | fast_merge_jar pkg, dep if dep !~ /-sources/
39 | end
40 | end
41 | end
42 | end
43 | end
44 | end
45 |
46 | # Merge jars using `jar` tool since rubyzip is kinda slow
47 | #
48 | # e.g. fast_merge_jar("my-super.jar, "some-dependency.jar")
49 | #
50 | def fast_merge_jar(target_jar, merge_jar)
51 | info "merging #{merge_jar}"
52 | tmp = _(:target, "tmp")
53 | sh "rm -rf #{tmp}"
54 | sh "mkdir -p #{tmp}; cd #{tmp}; jar -xf #{merge_jar}; jar -uf #{target_jar} *"
55 | sh "rm -rf #{tmp}"
56 | end
57 | end
58 |
59 |
60 |
--------------------------------------------------------------------------------
/ivy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/ivysettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/manifest.mf:
--------------------------------------------------------------------------------
1 | Main-Class: fakesdb.Jetty
2 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/Data.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import fakesdb.actions.NumberItemAttributesExceededException
4 | import fakesdb.actions.EmptyAttributeNameException
5 | import fakesdb.actions.InvalidParameterValue
6 | import scala.collection.mutable.LinkedHashSet
7 | import scala.collection.mutable.LinkedHashMap
8 | import com.amazonaws.services.simpledb.model.AttributeDoesNotExistException
9 | import fakesdb.actions.ConditionalCheckFailedException
10 |
11 | class Data {
12 | private val domains = new LinkedHashMap[String, Domain]()
13 |
14 | def getDomains(): Iterator[Domain] = domains.valuesIterator
15 |
16 | def getDomain(name: String): Option[Domain] = domains.get(name)
17 |
18 | def getOrCreateDomain(name: String): Domain = {
19 | if (!name.matches("[a-zA-Z0-9_\\-\\.]{3,255}")) {
20 | throw new InvalidParameterValue("Value (\"%s\") for parameter DomainName is invalid.".format(name))
21 | }
22 | domains.getOrElseUpdate(name, new Domain(name))
23 | }
24 |
25 | def deleteDomain(domain: Domain): Unit = domains.remove(domain.name)
26 |
27 | def flush(): Unit = domains.clear
28 | }
29 |
30 | class Domain(val name: String) {
31 | private val items = new LinkedHashMap[String, Item]()
32 |
33 | def getItems(): Iterator[Item] = items.valuesIterator
34 |
35 | def getItem(name: String): Option[Item] = items.get(name)
36 |
37 | def getOrCreateItem(name: String): Item = {
38 | InvalidParameterValue.failIfOver1024("Name", name);
39 | items.getOrElseUpdate(name, new Item(name))
40 | }
41 |
42 | def deleteIfEmpty(item: Item) = if (!item.getAttributes.hasNext) items.remove(item.name)
43 |
44 | def deleteItem(item: Item) = items.remove(item.name)
45 | }
46 |
47 | class Item(val name: String) {
48 | private val attributes = new LinkedHashMap[String, Attribute]()
49 |
50 | def getAttributes(): Iterator[Attribute] = attributes.valuesIterator
51 |
52 | def getAttribute(name: String): Option[Attribute] = attributes.get(name)
53 |
54 | def getOrCreateAttribute(name: String): Attribute = attributes.getOrElseUpdate(name, new Attribute(name))
55 |
56 | // this put overload is used in a lot of tests
57 | def put(name: String, value: String, replace: Boolean): Unit = {
58 | put(name, List(value), replace)
59 | }
60 |
61 | def put(name: String, values: Seq[String], replace: Boolean) {
62 | // the limit is 256 (name,value) unique pairs, so make (name,value) pairs and then combine them
63 | val existingPairs = attributes.toList.flatMap((e) => { e._2.values.map((v) => (e._1, v)) })
64 | val newPairs = values.map((v) => (name, v))
65 | if ((existingPairs ++ newPairs).toSet.size > 256) {
66 | throw new NumberItemAttributesExceededException
67 | }
68 | if (name == "") {
69 | throw new EmptyAttributeNameException
70 | }
71 | InvalidParameterValue.failIfOver1024("Name", name);
72 | this.getOrCreateAttribute(name).put(values, replace)
73 | }
74 |
75 | def delete(name: String): Unit = attributes.remove(name)
76 |
77 | def delete(name: String, value: String): Unit = {
78 | getAttribute(name) match {
79 | case Some(a) => a.deleteValues(value) ; removeIfNoValues(a)
80 | case None =>
81 | }
82 | }
83 |
84 | def assertCondition(condition: Tuple2[String, Option[String]]) = {
85 | condition match {
86 | case (name, None) => for (f <- getAttributes.find(_.name == name)) throw new ConditionalCheckFailedException(condition)
87 | case (name, Some(value)) => getAttributes find (_.name == name) match {
88 | case None => throw new AttributeDoesNotExistException(name)
89 | case Some(attr) => if (attr.getValues.toList != List(value)) throw new ConditionalCheckFailedException(condition, attr.getValues.toList)
90 | }
91 | }
92 | }
93 |
94 | private def removeIfNoValues(attribute: Attribute) = {
95 | if (attribute.empty) attributes.remove(attribute.name)
96 | }
97 | }
98 |
99 | class Attribute(val name: String) {
100 | private val _values = new LinkedHashSet[String]()
101 |
102 | def values(): Traversable[String] = _values
103 |
104 | def getValues(): Iterator[String] = _values.iterator
105 |
106 | def empty(): Boolean = values.size == 0
107 |
108 | def deleteValues(value: String) = {
109 | _values.remove(value)
110 | }
111 |
112 | def put(__values: Seq[String], replace: Boolean) = {
113 | if (replace) _values.clear
114 | __values.foreach((v) => {
115 | InvalidParameterValue.failIfOver1024("Value", v);
116 | _values += v
117 | })
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/FakeSdbServlet.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import java.io.{PrintWriter, StringWriter}
4 | import javax.servlet.http._
5 | import fakesdb.actions._
6 |
7 | class FakeSdbServlet extends HttpServlet {
8 |
9 | val data = new Data
10 |
11 | override def doGet(request: HttpServletRequest, response: HttpServletResponse): Unit = synchronized {
12 | val params = Params(request)
13 | if (!params.contains("Action")) {
14 | response.setStatus(404)
15 | return
16 | }
17 |
18 | var xml = ""
19 | try {
20 | val action = params("Action") match {
21 | case "CreateDomain" => new CreateDomain(data)
22 | case "DeleteDomain" => new DeleteDomain(data)
23 | case "DomainMetadata" => new DomainMetadata(data)
24 | case "ListDomains" => new ListDomains(data)
25 | case "GetAttributes" => new GetAttributes(data)
26 | case "PutAttributes" => new PutAttributes(data)
27 | case "BatchPutAttributes" => new BatchPutAttributes(data)
28 | case "BatchDeleteAttributes" => new BatchDeleteAttributes(data)
29 | case "DeleteAttributes" => new DeleteAttributes(data)
30 | case "Select" => new Select(data)
31 | case other => throw new InvalidActionException(other)
32 | }
33 | xml = action.handle(params).toString
34 | } catch {
35 | case e: Exception => {
36 | xml = toXML(e).toString
37 | response.setStatus(e match {
38 | case se: SDBException => se.httpStatus
39 | case _ => 400
40 | })
41 | }
42 | }
43 |
44 | response.setContentType("text/xml")
45 | response.getWriter.write(xml)
46 | }
47 |
48 | private def toXML(t: Throwable) = {
49 | val xmlCode = t match {
50 | case se: SDBException => se.xmlCode
51 | case _ => "InternalError"
52 | }
53 |
54 | val stacktrace = new StringWriter()
55 | t.printStackTrace(new PrintWriter(stacktrace))
56 |
57 |
58 | {xmlCode}
{t.getClass.getSimpleName}: {t.getMessage}0
59 | 0
60 | {stacktrace.toString}
61 |
62 | }
63 |
64 | override def doPost(request: HttpServletRequest, response: HttpServletResponse): Unit = doGet(request, response)
65 |
66 | class InvalidActionException(action: String)
67 | extends SDBException(400, "InvalidAction", "The action %s is not valid for this web service.".format(action))
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/Jetty.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.eclipse.jetty.server.Server
4 | import org.eclipse.jetty.server.bio.SocketConnector
5 | import org.eclipse.jetty.server.handler.ContextHandlerCollection
6 | import org.eclipse.jetty.servlet.ServletHandler
7 | import org.eclipse.jetty.servlet.ServletContextHandler
8 |
9 | class Jetty(val server: Server)
10 |
11 | object Jetty {
12 | def apply(port: Int): Jetty = {
13 | val server = new Server
14 | // Use SocketConnector because SelectChannelConnector locks files
15 | val connector = new SocketConnector
16 | connector.setPort(port)
17 | connector.setMaxIdleTime(60000)
18 | connector.setRequestBufferSize(24 * 1024)
19 |
20 | val handler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
21 | handler.addServlet(classOf[FakeSdbServlet].getName(), "/*")
22 |
23 | server.setConnectors(Array(connector))
24 | server.setHandler(handler)
25 | server.setAttribute("org.mortbay.jetty.Request.maxFormContentSize", 0);
26 | server.setStopAtShutdown(true);
27 |
28 | new Jetty(server)
29 | }
30 |
31 | def main(args: Array[String]): Unit = {
32 | val port = System.getProperty("port", "8080").toInt
33 | Jetty(port).server.start()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/Params.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import javax.servlet.http.HttpServletRequest
4 | import scala.collection.mutable.HashMap
5 |
6 | class Params extends HashMap[String, String] {
7 | }
8 |
9 | object Params {
10 | def apply(request: HttpServletRequest): Params = {
11 | val p = new Params
12 | val i = request.getParameterMap.asInstanceOf[java.util.Map[String, Array[String]]].entrySet.iterator
13 | while (i.hasNext) {
14 | val e = i.next
15 | p.update(e.getKey(), e.getValue()(0))
16 | }
17 | return p
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/SelectParser.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import scala.util.parsing.combinator.syntactical._
4 | import scala.util.parsing.combinator.syntactical._
5 | import scala.util.parsing.combinator.lexical._
6 | import scala.util.parsing.input.CharArrayReader.EofCh
7 |
8 | case class SelectEval(output: OutputEval, from: String, where: WhereEval, order: OrderEval, limit: LimitEval) {
9 | def select(data: Data, nextToken: Option[Int] = None): (List[(String, List[(String,String)])], Int, Boolean) = {
10 | val domain = data.getDomain(from).getOrElse(sys.error("Invalid from "+from))
11 | val drop = new SomeDrop(nextToken getOrElse 0)
12 | val (items, hasMore) = limit.limit(drop.drop(order.sort(where.filter(domain, domain.getItems.toList))))
13 | (output.what(domain, items), items.length, hasMore)
14 | }
15 | }
16 |
17 | abstract class OutputEval {
18 | type OutputList = List[(String, List[(String, String)])]
19 | def what(domain: Domain, items: List[Item]): OutputList
20 |
21 | protected def flatAttrs(attrs: Iterator[Attribute]): List[(String, String)] = {
22 | attrs.flatMap((a: Attribute) => a.getValues.map((v: String) => (a.name, v))).toList
23 | }
24 | }
25 | case class CompoundOutput(attrNames: List[String]) extends OutputEval {
26 | def what(domain: Domain, items: List[Item]): OutputList = {
27 | items.map((item: Item) => {
28 | var i = (item.name, flatAttrs(item.getAttributes.filter((a: Attribute) => attrNames.contains(a.name))))
29 | if (attrNames.contains("itemName()")) { // ugly
30 | i = (i._1, ("itemName()", item.name) :: i._2)
31 | }
32 | i
33 | }).filter(_._2.size > 0)
34 | }
35 | }
36 | class AllOutput extends OutputEval {
37 | def what(domain: Domain, items: List[Item]): OutputList = {
38 | items.map((item: Item) => {
39 | (item.name, flatAttrs(item.getAttributes))
40 | }).filter(_._2.size > 0)
41 | }
42 | }
43 | class CountOutput extends OutputEval {
44 | def what(domain: Domain, items: List[Item]): OutputList = {
45 | List(("Domain", List(("Count", items.size.toString))))
46 | }
47 | }
48 |
49 | abstract class WhereEval {
50 | def filter(domain: Domain, items: List[Item]): List[Item]
51 |
52 | protected def getFunc(op: String): Function2[String, String, Boolean] = op match {
53 | case "=" => _ == _
54 | case "!=" => _ != _
55 | case ">" => _ > _
56 | case "<" => _ < _
57 | case ">=" => _ >= _
58 | case "<=" => _ <= _
59 | case "like" => (v1, v2) => v1.matches(v2.replaceAll("%", ".*"))
60 | case "not-like" => (v1, v2) => !v1.matches(v2.replaceAll("%", ".*"))
61 | }
62 | }
63 | case class NoopWhere() extends WhereEval {
64 | def filter(domain: Domain, items: List[Item]): List[Item] = items
65 | }
66 | case class SimpleWhereEval(name: String, op: String, value: String) extends WhereEval {
67 | def filter(domain: Domain, items: List[Item]): List[Item] = {
68 | val func = getFunc(op)
69 | items.filter((i: Item) => i.getAttribute(name) match {
70 | case Some(a) => a.getValues.find(func(_, value)).isDefined
71 | case None => false
72 | }).toList
73 | }
74 | }
75 |
76 | abstract class LimitEval {
77 | def limit(items: List[Item]): (List[Item], Boolean)
78 | }
79 | case class NoopLimit() extends LimitEval {
80 | def limit(items: List[Item]) = (items, false)
81 | }
82 | case class SomeLimit(limit: Int) extends LimitEval {
83 | def limit(items: List[Item]) = (items take limit, items.size > limit)
84 | }
85 |
86 | case class SomeDrop(count: Int) {
87 | def drop(items: List[Item]) = items drop count
88 | }
89 |
90 | case class EveryEval(name: String, op: String, value: String) extends WhereEval {
91 | override def filter(domain: Domain, items: List[Item]): List[Item] = {
92 | val func = getFunc(op)
93 | items.filter((i: Item) => i.getAttribute(name) match {
94 | case Some(a) => a.getValues.forall(func(_, value))
95 | case None => false
96 | }).toList
97 | }
98 | }
99 | case class CompoundWhereEval(sp: WhereEval, op: String, rest: WhereEval) extends WhereEval {
100 | def filter(domain: Domain, items: List[Item]): List[Item] = {
101 | op match {
102 | case "intersection" => sp.filter(domain, items).toList intersect rest.filter(domain, items).toList
103 | case "and" => sp.filter(domain, items).toList intersect rest.filter(domain, items).toList
104 | case "or" => sp.filter(domain, items).toList union rest.filter(domain, items).toList
105 | case _ => sys.error("Invalid operator "+op)
106 | }
107 | }
108 | }
109 | case class IsNullEval(name: String, isNull: Boolean) extends WhereEval {
110 | def filter(domain: Domain, items: List[Item]): List[Item] = {
111 | items.filter((i: Item) => if (isNull) {
112 | i.getAttribute(name).isEmpty
113 | } else {
114 | i.getAttribute(name).isDefined
115 | }).toList
116 | }
117 | }
118 | case class IsBetweenEval(name: String, lower: String, upper: String) extends WhereEval {
119 | def filter(domain: Domain, items: List[Item]): List[Item] = {
120 | items.filter((i: Item) => i.getAttribute(name) match {
121 | case Some(a) => a.getValues.exists(_ >= lower) && a.getValues.exists(_ <= upper)
122 | case None => false
123 | }).toList
124 | }
125 | }
126 | case class InEval(name: String, values: List[String]) extends WhereEval {
127 | def filter(domain: Domain, items: List[Item]): List[Item] = {
128 | items.filter((i: Item) => i.getAttribute(name) match {
129 | case Some(a) => a.getValues.exists(values.contains(_))
130 | case None => false
131 | }).toList
132 | }
133 | }
134 |
135 | abstract class OrderEval {
136 | def sort(items: List[Item]): List[Item]
137 | }
138 | case class NoopOrder() extends OrderEval {
139 | def sort(items: List[Item]) = items
140 | }
141 | case class SimpleOrderEval(name: String, way: String) extends OrderEval {
142 | def sort(items: List[Item]): List[Item] = {
143 | val comp = (lv: String, rv: String) => way match {
144 | case "desc" => lv > rv
145 | case _ => lv < rv
146 | }
147 | items.sortWith((l, r) => {
148 | comp(resolveValue(l), resolveValue(r))
149 | })
150 | }
151 | def resolveValue(item: Item) = {
152 | if (name == "itemName()") {
153 | item.name
154 | } else {
155 | item.getAttribute(name) match {
156 | case Some(a) => a.getValues.next
157 | case None => "" // default value
158 | }
159 | }
160 | }
161 | }
162 |
163 | class SelectLexical extends StdLexical {
164 | override def token: Parser[Token] =
165 | ( accept("itemName()".toList) ^^^ { Identifier("itemName()") }
166 | | acceptInsensitiveSeq("count(*)".toList) ^^^ { Keyword("count(*)") }
167 | | '\'' ~> rep(chrWithDoubleTicks) <~ '\'' ^^ { chars => StringLit(chars mkString "") }
168 | | '"' ~> rep(chrWithDoubleQuotes) <~ '"' ^^ { chars => StringLit(chars mkString "") }
169 | | '`' ~> rep(chrWithDoubleBackTicks) <~ '`' ^^ { chars => Identifier(chars mkString "") }
170 | | super.token
171 | )
172 |
173 | // Add $ to letters and _ as acceptable first characters of unquoted identifiers
174 | override def identChar = letter | elem('_') | elem('$')
175 |
176 | // Allow case insensitive keywords by lower casing everything
177 | override protected def processIdent(name: String) =
178 | if (reserved contains name.toLowerCase) Keyword(name.toLowerCase) else Identifier(name)
179 |
180 | // Wow this works--inline acceptSeq and acceptIf, but adds _.toLowerCase
181 | def acceptInsensitiveSeq[ES <% Iterable[Elem]](es: ES): Parser[List[Elem]] =
182 | es.foldRight[Parser[List[Elem]]](success(Nil)){(x, pxs) => acceptIf(_.toLower == x)("`"+x+"' expected but " + _ + " found") ~ pxs ^^ mkList}
183 |
184 | def chrWithDoubleTicks = ('\'' ~ '\'') ^^^ '\'' | chrExcept('\'', EofCh)
185 |
186 | def chrWithDoubleQuotes = ('"' ~ '"') ^^^ '"' | chrExcept('"', EofCh)
187 |
188 | def chrWithDoubleBackTicks = ('`' ~ '`') ^^^ '`' | chrExcept('`', EofCh)
189 | }
190 |
191 | object SelectParser extends StandardTokenParsers {
192 | override val lexical = new SelectLexical
193 | lexical.delimiters ++= List("*", ",", "=", "!=", ">", "<", ">=", "<=", "(", ")")
194 | lexical.reserved ++= List(
195 | "select", "from", "where", "and", "or", "like", "not", "is", "null", "between",
196 | "every", "in", "order", "by", "asc", "desc", "intersection", "limit", "count(*)"
197 | )
198 |
199 | def expr = ("select" ~> outputList) ~ ("from" ~> ident) ~ whereClause ~ order ~ limit ^^ { case ol ~ i ~ w ~ o ~ l => SelectEval(ol, i, w, o, l) }
200 |
201 | def order: Parser[OrderEval] =
202 | ( "order" ~> "by" ~> ident ~ ("asc" | "desc") ^^ { case i ~ way => SimpleOrderEval(i, way) }
203 | | "order" ~> "by" ~> ident ^^ { i => SimpleOrderEval(i, "asc") }
204 | | success(NoopOrder())
205 | )
206 |
207 | def limit: Parser[LimitEval] =
208 | ( "limit" ~> numericLit ^^ { num => SomeLimit(num.toInt) }
209 | | success(NoopLimit())
210 | )
211 |
212 | def whereClause: Parser[WhereEval] =
213 | ( "where" ~> where
214 | | success(new NoopWhere)
215 | )
216 | def where: Parser[WhereEval] =
217 | ( simplePredicate ~ setOp ~ where ^^ { case sp ~ op ~ rp => CompoundWhereEval(sp, op, rp) }
218 | | simplePredicate
219 | )
220 |
221 | def simplePredicate: Parser[WhereEval] =
222 | ( ident <~ "is" <~ "null" ^^ { i => IsNullEval(i, true) }
223 | | ident <~ "is" <~ "not" <~ "null" ^^ { i => IsNullEval(i, false) }
224 | | ident ~ ("between" ~> stringLit) ~ ("and" ~> stringLit) ^^ { case i ~ a ~ b => IsBetweenEval(i, a, b) }
225 | | ident ~ ("in" ~> "(" ~> repsep(stringLit, ",") <~ ")") ^^ { case i ~ strs => InEval(i, strs) }
226 | | ("every" ~> "(" ~> ident <~ ")") ~ op ~ stringLit ^^ { case i ~ o ~ v => EveryEval(i, o, v)}
227 | | ident ~ op ~ stringLit ^^ { case i ~ o ~ v => SimpleWhereEval(i, o, v) }
228 | | "(" ~> where <~ ")"
229 | )
230 | def setOp = "and" | "or" | "intersection"
231 | def op = "=" | "!=" | ">" | "<" | ">=" | "<=" | "like" | "not" ~ "like" ^^^ { "not-like" }
232 |
233 | def outputList: Parser[OutputEval] =
234 | ( "*" ^^^ { new AllOutput }
235 | | "count(*)" ^^^ { new CountOutput }
236 | | repsep(ident, ",") ^^ { attrNames => CompoundOutput(attrNames) }
237 | )
238 |
239 | def makeSelectEval(input: String): SelectEval = {
240 | val tokens = new lexical.Scanner(input)
241 | phrase(expr)(tokens) match {
242 | case Success(selectEval, _) => selectEval
243 | case Failure(msg, _) => sys.error(msg)
244 | case Error(msg, _) => sys.error(msg)
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/Action.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | abstract class Action(data: Data) {
7 |
8 | def handle(params: Params): NodeSeq
9 |
10 | protected def responseMetaData() = {
11 | {requestId}0
12 | }
13 |
14 | protected def parseDomain(params: Params): Domain = {
15 | val domainName = params.getOrElse("DomainName", sys.error("No domain name"))
16 | return data.getDomain(domainName).getOrElse(sys.error("Invalid domain name "+domainName))
17 | }
18 |
19 | val namespace = "http://sdb.amazonaws.com/doc/2009-04-15/"
20 |
21 | val requestId = Action.requestCounter.incrementAndGet()
22 |
23 | }
24 |
25 | object Action {
26 | private val requestCounter = new java.util.concurrent.atomic.AtomicInteger()
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/BatchDeleteAttributes.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.collection.mutable.ListBuffer
4 | import scala.collection.mutable.LinkedHashMap
5 | import scala.xml.NodeSeq
6 | import fakesdb._
7 |
8 | class BatchDeleteAttributes(data: Data) extends Action(data) {
9 |
10 | def handle(params: Params): NodeSeq = {
11 | val domain = parseDomain(params)
12 | discoverAttributes(params).delete(domain)
13 |
14 | {responseMetaData}
15 |
16 | }
17 |
18 | private def discoverAttributes(params: Params): ItemUpdates = {
19 | val updates = new ItemUpdates()
20 | var i = 0
21 | var stop = false
22 | while (!stop) {
23 | val itemName = params.get("Item."+i+".ItemName")
24 | if (itemName.isEmpty) {
25 | if (i > 1) stop = true
26 | } else {
27 | var j = 0
28 | var stop2 = false
29 | while (!stop2) {
30 | val attrName = params.get("Item."+i+".Attribute."+j+".Name")
31 | //values currently not supported.
32 | //val attrValue = params.get("Item."+i+".Attribute."+j+".Value")
33 | if (attrName.isEmpty) {
34 | if (j > 1) stop2 = true
35 | updates.add(itemName.get)
36 | } else {
37 | updates.add(itemName.get, attrName.get)
38 | }
39 | j += 1
40 | }
41 | }
42 | i += 1
43 | }
44 | updates
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/BatchPutAttributes.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.collection.mutable.ListBuffer
4 | import scala.collection.mutable.LinkedHashMap
5 | import scala.xml.NodeSeq
6 | import fakesdb._
7 |
8 | class BatchPutAttributes(data: Data) extends Action(data) {
9 |
10 | def handle(params: Params): NodeSeq = {
11 | val domain = parseDomain(params)
12 | discoverAttributes(params).update(domain)
13 |
14 | {responseMetaData}
15 |
16 | }
17 |
18 | private def discoverAttributes(params: Params): ItemUpdates = {
19 | val updates = new ItemUpdates
20 | var i = 0
21 | var stop = false
22 | while (!stop) {
23 | val itemName = params.get("Item."+i+".ItemName")
24 | if (itemName.isEmpty) {
25 | if (i > 1) stop = true
26 | } else {
27 | var j = 0
28 | var stop2 = false
29 | while (!stop2) {
30 | val attrName = params.get("Item."+i+".Attribute."+j+".Name")
31 | val attrValue = params.get("Item."+i+".Attribute."+j+".Value")
32 | val attrReplace = params.get("Item."+i+".Attribute."+j+".Replace")
33 | if (attrName.isEmpty || attrValue.isEmpty) {
34 | if (j > 1) stop2 = true
35 | } else {
36 | val replace = attrReplace.getOrElse("false").toBoolean
37 | updates.add(itemName.get, attrName.get, attrValue.get, replace)
38 | }
39 | j += 1
40 | }
41 | }
42 | i += 1
43 | }
44 | updates
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/ConditionalChecking.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import fakesdb._
4 |
5 | trait ConditionalChecking {
6 |
7 | private val expectedNamePattern = """Expected\.(\d+)\.Name""".r
8 | private val expectedNamePattern2 = """Expected\.Name""".r
9 |
10 | def checkConditionals(item: Item, params: Params) {
11 | for (condition <- discoverConditional(params)) {
12 | condition match {
13 | case (name, None) => for (f <- item.getAttributes.find(_.name == name)) throw new ConditionalCheckFailedException(condition)
14 | case (name, Some(value)) => item.getAttributes find (_.name == name) match {
15 | case None => throw new AttributeDoesNotExistException(name)
16 | case Some(attr) => if (attr.getValues.toList != List(value)) throw new ConditionalCheckFailedException(condition, attr.getValues.toList)
17 | }
18 | }
19 | }
20 | }
21 |
22 | private def discoverConditional(params: Params): Option[Tuple2[String, Option[String]]] = {
23 | val keys = params.keys find (k => k.startsWith("Expected") && k.endsWith("Name"))
24 | if (keys.isEmpty) {
25 | return None
26 | }
27 | if (keys.size > 1) {
28 | sys.error("Only one condition may be specified")
29 | }
30 | val name = params.get(keys.head).get
31 | keys.head match {
32 | case expectedNamePattern2() => {
33 | // the aws jdk sends "Expected.Name" with no digit
34 | for (v <- params.get("Expected.Exists")) {
35 | if (v == "false") {
36 | return Some((name, None))
37 | }
38 | }
39 | for (v <- params.get("Expected.Value")) {
40 | return Some((name, Some(v)))
41 | }
42 | }
43 | case expectedNamePattern(digit) => {
44 | // typica sent "Expected.x.Name" with a digit
45 | for (v <- params.get("Expected.%s.Exists".format(digit))) {
46 | if (v == "false") {
47 | return Some((name, None))
48 | }
49 | }
50 | for (v <- params.get("Expected.%s.Value".format(digit))) {
51 | return Some((name, Some(v)))
52 | }
53 | }
54 | }
55 | None
56 | }
57 | }
58 |
59 | class ConditionalCheckFailedException(message: String) extends SDBException(409, "ConditionalCheckFailed", message) {
60 | def this(condition: Tuple2[String, Option[String]]) = {
61 | this("Attribute (%s) value exists".format(condition._1))
62 | }
63 |
64 | def this(condition: Tuple2[String, Option[String]], actual: List[String]) = {
65 | this("Attribute (%s) value is (%s) but was expected (%s)".format(condition._1, actual, condition._2.get))
66 | }
67 | }
68 |
69 | class AttributeDoesNotExistException(name: String)
70 | extends SDBException(404, "AttributeDoesNotExist", "Attribute (%s) does not exist".format(name))
71 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/CreateDomain.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | class CreateDomain(data: Data) extends Action(data) {
7 |
8 | def handle(params: Params): NodeSeq = {
9 | val domainName = params.getOrElse("DomainName", sys.error("No domain name"))
10 | if (domainName == "_flush") {
11 | data.flush() // The special one
12 | } else if (domainName == "_dump") {
13 | dump(domainName)
14 | } else {
15 | data.getOrCreateDomain(domainName)
16 | }
17 |
18 | {responseMetaData}
19 |
20 | }
21 |
22 | def dump(domainName: String) {
23 | for (d <- data.getDomains) {
24 | println("Domain "+d.name)
25 | for (i <- d.getItems) {
26 | println("\tItem "+i.name)
27 | for (a <- i.getAttributes) {
28 | println("\t\t"+a.name+" = "+a.getValues.mkString(", "))
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/DeleteAttributes.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.collection.mutable.ListBuffer
4 | import scala.xml.NodeSeq
5 | import fakesdb._
6 |
7 | class DeleteAttributes(data: Data) extends Action(data) with ConditionalChecking {
8 |
9 | def handle(params: Params): NodeSeq = {
10 | val domain = parseDomain(params)
11 | val itemName = params.getOrElse("ItemName", sys.error("No item name"))
12 | val item = domain.getItem(itemName) match {
13 | case Some(item) => {
14 | checkConditionals(item, params)
15 | doDelete(params, domain, item)
16 | }
17 | case _ =>
18 | }
19 |
20 | {responseMetaData}
21 |
22 | }
23 |
24 | private def doDelete(params: Params, domain: Domain, item: Item) = {
25 | val destroy = discoverAttributes(params)
26 | if (destroy.isEmpty) {
27 | domain.deleteItem(item)
28 | } else {
29 | for (attr <- destroy) attr._2 match {
30 | case Some(value) => item.delete(attr._1, value)
31 | case None => item.delete(attr._1)
32 | }
33 | domain.deleteIfEmpty(item)
34 | }
35 | }
36 |
37 | private def discoverAttributes(params: Params): List[(String, Option[String])] = {
38 | val attrs = new ListBuffer[(String, Option[String])]()
39 | var i = 0
40 | var stop = false
41 | while (!stop) {
42 | val attrName = params.get("Attribute."+i+".Name")
43 | val attrValue = params.get("Attribute."+i+".Value")
44 | if (attrName.isEmpty && attrValue.isEmpty) {
45 | if (i > 1) stop = true
46 | } else {
47 | attrs += Tuple2(attrName.get, attrValue)
48 | }
49 | i += 1
50 | }
51 | attrs.toList
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/DeleteDomain.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | class DeleteDomain(data: Data) extends Action(data) {
7 |
8 | def handle(params: Params): NodeSeq = {
9 | data.deleteDomain(parseDomain(params))
10 |
11 | {responseMetaData}
12 |
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/DomainMetadata.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | class DomainMetadata(data: Data) extends Action(data) {
7 |
8 | def handle(params: Params): NodeSeq = {
9 | def sum(list: List[Int]) = list.foldLeft(0)(_ + _)
10 | val allItems = data.getDomains.flatMap(_.getItems).toList
11 | val allAttrs = allItems.flatMap(_.getAttributes.toList)
12 | val allValues = allAttrs.flatMap(_.getValues.toList)
13 |
14 |
15 | {allItems.size}
16 | {sum(allItems.map(_.name.size))}
17 | {allAttrs.toList.size}
18 | {sum(allAttrs.map(_.name.size))}
19 | {allValues.size}
20 | {sum(allValues.map(_.size))}
21 | 0
22 |
23 | {responseMetaData}
24 |
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/Errors.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | // http://docs.amazonwebservices.com/AmazonSimpleDB/latest/DeveloperGuide/index.html?APIError.html
4 | class SDBException(val httpStatus: Int, val xmlCode: String, val message: String) extends RuntimeException(message)
5 |
6 | class NumberItemAttributesExceededException
7 | extends SDBException(409, "NumberItemAttributesExceeded", "Too many attributes in this item")
8 |
9 | class NumberSubmittedItemsExceeded
10 | extends SDBException(409, "NumberSubmittedItemsExceeded", "Too many items in a single call. Up to 25 items per call allowed.")
11 |
12 | class EmptyAttributeNameException
13 | extends SDBException(400, "InvalidParameterValue", "Value () for parameter Name is invalid. The empty string is an illegal attribute name")
14 |
15 | class MissingItemNameException
16 | extends SDBException(400, "MissingParameter", "The request must contain the parameter ItemName.")
17 |
18 | class InvalidParameterValue(message: String)
19 | extends SDBException(400, "InvalidParameterValue", message)
20 |
21 | object InvalidParameterValue {
22 | def failIfOver1024(name: String, value: String): Unit = {
23 | if (value.getBytes.size > 1024) {
24 | throw new InvalidParameterValue("Value (\"%s\") for parameter %s is invalid. Value exceeds maximum length of 1024.".format(value, name));
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/GetAttributes.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.collection.mutable.ListBuffer
4 | import scala.xml.NodeSeq
5 | import fakesdb._
6 |
7 | class GetAttributes(data: Data) extends Action(data) {
8 |
9 | def handle(params: Params): NodeSeq = {
10 | val domain = parseDomain(params)
11 | val itemName = params.getOrElse("ItemName", sys.error("No item name"))
12 | val items = domain.getItem(itemName) match {
13 | case Some(item) => List(item)
14 | case None => List()
15 | }
16 | val requested = discoverAttributes(params)
17 |
18 |
19 | {for (item <- items) yield
20 | {for (nv <- filter(item, requested)) yield
21 | {nv._1.name}{nv._2}
22 | }
23 | }
24 |
25 | {responseMetaData}
26 |
27 | }
28 |
29 | protected def filter(item: Item, requested: List[String]): Iterator[(Attribute, String)] = {
30 | val attrs = item.getAttributes.filter((a: Attribute) => requested.isEmpty || requested.contains(a.name))
31 | attrs.flatMap((a: Attribute) => a.getValues.map((v: String) => (a, v)))
32 | }
33 |
34 | protected def discoverAttributes(params: Params): List[String] = {
35 | val requested = new ListBuffer[String]()
36 | var i = 0
37 | var stop = false
38 | while (!stop) {
39 | val attrName = params.get("AttributeName."+i)
40 | if (attrName.isEmpty) {
41 | if (i > 1) stop = true
42 | } else {
43 | requested += attrName.get
44 | }
45 | i += 1
46 | }
47 | requested.toList
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/ItemUpdates.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.collection.mutable.ListBuffer
4 | import scala.collection.mutable.LinkedHashMap
5 | import fakesdb._
6 |
7 | /** itemName -> [attrName -> AttributeUpdate] */
8 | class ItemUpdates extends LinkedHashMap[String, LinkedHashMap[String, AttributeUpdate]] {
9 | def add(itemName: String, attrName: String, attrValue: String, replace: Boolean): Unit = {
10 | val attrs = getOrElseUpdate(itemName, new LinkedHashMap[String, AttributeUpdate])
11 | val attr = attrs.getOrElseUpdate(attrName, new AttributeUpdate(replace))
12 | attr.values += attrValue
13 | }
14 |
15 | def add(itemName: String, attrName: String): Unit = {
16 | val attrs = getOrElseUpdate(itemName, new LinkedHashMap[String, AttributeUpdate])
17 | val attr = attrs.getOrElseUpdate(attrName, new AttributeUpdate(false))
18 | }
19 |
20 | def add(itemName: String): Unit = {
21 | val attrs = getOrElseUpdate(itemName, new LinkedHashMap[String, AttributeUpdate])
22 | }
23 |
24 | def update(domain: Domain): Unit = {
25 | checkSize()
26 | foreach { case (itemName, attrs) => {
27 | attrs.foreach { case (attrName, attrUpdate) => {
28 | domain.getOrCreateItem(itemName).put(attrName, attrUpdate.values, attrUpdate.replace)
29 | }}
30 | }}
31 | }
32 |
33 | def delete(domain: Domain): Unit = {
34 | checkSize()
35 | foreach { case (itemName, attrs) => {
36 | val item = domain.getItem(itemName) match {
37 | case Some(item) => {
38 | if (attrs.isEmpty) {
39 | domain.deleteItem(item)
40 | } else {
41 | attrs.foreach { case (attrName, replace) => {
42 | item.delete(attrName)
43 | }}
44 | domain.deleteIfEmpty(item)
45 | }
46 | }
47 | case _ =>
48 | }
49 | }}
50 | }
51 |
52 | private def checkSize() = {
53 | if (size > 25) {
54 | throw new NumberSubmittedItemsExceeded
55 | }
56 | }
57 | }
58 |
59 | class AttributeUpdate(val replace: Boolean) {
60 | val values = new ListBuffer[String]()
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/ListDomains.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | class ListDomains(data: Data) extends Action(data) {
7 |
8 | def handle(params: Params): NodeSeq = {
9 |
10 |
11 | {for (domain <- data.getDomains) yield
12 | {domain.name}
13 | }
14 |
15 | {responseMetaData}
16 |
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/PutAttributes.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | class PutAttributes(data: Data) extends Action(data) with ConditionalChecking {
7 |
8 | def handle(params: Params): NodeSeq = {
9 | val domain = parseDomain(params)
10 | val itemName = params.getOrElse("ItemName", throw new MissingItemNameException)
11 | val item = domain.getOrCreateItem(itemName)
12 |
13 | checkConditionals(item, params)
14 |
15 | discoverAttributes(itemName, params).update(domain)
16 |
17 |
18 | {responseMetaData}
19 |
20 | }
21 |
22 | private def discoverAttributes(itemName: String, params: Params): ItemUpdates = {
23 | val updates = new ItemUpdates()
24 | var i = 0
25 | var stop = false
26 | while (!stop) {
27 | val attrName = params.get("Attribute."+i+".Name")
28 | val attrValue = params.get("Attribute."+i+".Value")
29 | val attrReplace = params.get("Attribute."+i+".Replace")
30 | if (attrName.isEmpty || attrValue.isEmpty) {
31 | if (i > 1) stop = true
32 | } else {
33 | val replace = attrReplace.getOrElse("false").toBoolean
34 | updates.add(itemName, attrName.get, attrValue.get, replace)
35 | }
36 | i += 1
37 | }
38 | updates
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/actions/Select.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.actions
2 |
3 | import scala.xml.NodeSeq
4 | import fakesdb._
5 |
6 | class Select(data: Data) extends Action(data) {
7 |
8 | override def handle(params: Params): NodeSeq = {
9 | val nextToken = params.get("NextToken") map { _.toInt }
10 | val itemsData = params.get("SelectExpression") match {
11 | case Some(s) => val se = SelectParser.makeSelectEval(s) ; se.select(data, nextToken)
12 | case None => sys.error("No select expression")
13 | }
14 | val items = itemsData._1
15 | val itemsLength = itemsData._2
16 | val newNextToken = if (itemsData._3) List(itemsLength) else List()
17 |
18 |
19 | {for (item <- items) yield
20 | -
21 | {item._1}
22 | {for (nv <- item._2) yield
23 | {nv._1}{nv._2}
24 | }
25 |
26 | }
27 | {for (token <- newNextToken) yield
28 | {token}
29 | }
30 |
31 | {responseMetaData}
32 |
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/scala/fakesdb/stub/AmazonSimpleDbStub.scala:
--------------------------------------------------------------------------------
1 | package fakesdb.stub
2 |
3 | import fakesdb.actions.ItemUpdates
4 | import fakesdb.SelectParser
5 | import com.amazonaws.regions.Region
6 | import com.amazonaws.services.simpledb.AmazonSimpleDB
7 | import com.amazonaws.services.simpledb.model.UpdateCondition
8 | import com.amazonaws.services.simpledb.model.Attribute
9 | import com.amazonaws.services.simpledb.model.Item
10 | import com.amazonaws.services.simpledb.model.SelectRequest
11 | import com.amazonaws.services.simpledb.model.ListDomainsRequest
12 | import com.amazonaws.services.simpledb.model.GetAttributesRequest
13 | import com.amazonaws.services.simpledb.model.PutAttributesRequest
14 | import com.amazonaws.services.simpledb.model.CreateDomainRequest
15 | import com.amazonaws.services.simpledb.model.SelectResult
16 | import com.amazonaws.services.simpledb.model.BatchDeleteAttributesRequest
17 | import com.amazonaws.services.simpledb.model.DeleteDomainRequest
18 | import com.amazonaws.services.simpledb.model.DeleteAttributesRequest
19 | import com.amazonaws.services.simpledb.model.GetAttributesResult
20 | import com.amazonaws.services.simpledb.model.DomainMetadataResult
21 | import com.amazonaws.services.simpledb.model.DomainMetadataRequest
22 | import com.amazonaws.services.simpledb.model.ListDomainsResult
23 | import com.amazonaws.services.simpledb.model.BatchPutAttributesRequest
24 | import scala.collection.JavaConversions._
25 |
26 | /** Stubs out the {@link AmazonSimpleDB} interface in memory. */
27 | class AmazonSimpleDbStub extends AmazonSimpleDB {
28 |
29 | private val data = new fakesdb.Data()
30 |
31 | def flush() {
32 | data.flush
33 | }
34 |
35 | def dump() {
36 | data.getDomains.foreach { domain =>
37 | domain.getItems.foreach { item =>
38 | item.getAttributes.foreach { attribute =>
39 | println(domain.name + " " + item.name + " " + attribute.name + " " + attribute.values.mkString(", "))
40 | }
41 | }
42 | }
43 | }
44 |
45 | override def setEndpoint(endpoint: String): Unit = {
46 | // noop
47 | }
48 |
49 | override def setRegion(region: Region): Unit = {
50 | // noop
51 | }
52 |
53 | override def select(req: SelectRequest): SelectResult = {
54 | val se = SelectParser.makeSelectEval(req.getSelectExpression)
55 | val result = se.select(data, Option(req.getNextToken).map(_.toInt))
56 | val nextToken = if (result._3) result._2.toString else null
57 | return new SelectResult()
58 | .withNextToken(nextToken)
59 | .withItems(result._1.map { i => new Item(i._1, i._2.map(a => new Attribute(a._1, a._2))) } )
60 | }
61 |
62 | override def deleteAttributes(req: DeleteAttributesRequest): Unit = {
63 | for (c <- toConditional(req.getExpected)) {
64 | data.getDomain(req.getDomainName).get.getItem(req.getItemName).get.assertCondition(c)
65 | }
66 | val updates = new ItemUpdates()
67 | for (attribute <- req.getAttributes) {
68 | updates.add(req.getItemName, attribute.getName)
69 | }
70 | updates.delete(data.getDomain(req.getDomainName).get)
71 | }
72 |
73 | override def putAttributes(req: PutAttributesRequest): Unit = {
74 | for (c <- toConditional(req.getExpected)) {
75 | data.getDomain(req.getDomainName).get.getItem(req.getItemName).get.assertCondition(c)
76 | }
77 | val updates = new ItemUpdates()
78 | for (attribute <- req.getAttributes) {
79 | updates.add(req.getItemName, attribute.getName, attribute.getValue, attribute.getReplace)
80 | }
81 | updates.update(data.getDomain(req.getDomainName).get)
82 | }
83 |
84 | override def batchDeleteAttributes(req: BatchDeleteAttributesRequest): Unit = {
85 | val updates = new ItemUpdates()
86 | for (item <- req.getItems) {
87 | for (attribute <- item.getAttributes) {
88 | updates.add(item.getName, attribute.getName)
89 | }
90 | }
91 | updates.delete(data.getDomain(req.getDomainName).get)
92 | }
93 |
94 | override def batchPutAttributes(req: BatchPutAttributesRequest): Unit = {
95 | val updates = new ItemUpdates()
96 | for (item <- req.getItems) {
97 | for (attribute <- item.getAttributes) {
98 | updates.add(item.getName, attribute.getName, attribute.getValue, attribute.getReplace)
99 | }
100 | }
101 | updates.update(data.getDomain(req.getDomainName).get)
102 | }
103 |
104 | override def deleteDomain(req: DeleteDomainRequest): Unit = {
105 | data.deleteDomain(data.getDomain(req.getDomainName).get)
106 | }
107 |
108 | override def createDomain(req: CreateDomainRequest): Unit = {
109 | data.getOrCreateDomain(req.getDomainName)
110 | }
111 |
112 | override def listDomains(): ListDomainsResult = {
113 | new ListDomainsResult().withDomainNames(data.getDomains.map { _.name }.toList)
114 | }
115 |
116 | override def listDomains(req: ListDomainsRequest): ListDomainsResult = {
117 | new ListDomainsResult().withDomainNames(data.getDomains.map { _.name }.toList)
118 | }
119 |
120 | override def getAttributes(req: GetAttributesRequest): GetAttributesResult = {
121 | data.getDomain(req.getDomainName).get.getItem(req.getItemName) match {
122 | case Some(item) => new GetAttributesResult().withAttributes(item.getAttributes
123 | .filter(a => req.getAttributeNames.size == 0 || req.getAttributeNames.contains(a.name))
124 | .flatMap(a => a.getValues.map(v => new Attribute(a.name, v))).toList)
125 | case None => new GetAttributesResult()
126 | }
127 | }
128 |
129 | override def domainMetadata(req: DomainMetadataRequest): DomainMetadataResult = {
130 | val domain = data.getDomain(req.getDomainName).get
131 | return new DomainMetadataResult()
132 | .withItemCount(domain.getItems.size)
133 | .withAttributeNameCount(domain.getItems.foldLeft(0)(_ + _.getAttributes.size))
134 | .withAttributeValueCount(domain.getItems.foldLeft(0)(_ + _.getAttributes.foldLeft(0)(_ + _.getValues.size)))
135 | }
136 |
137 | private def toConditional(condition: UpdateCondition): Option[Tuple2[String, Option[String]]] = {
138 | if (condition == null) {
139 | None
140 | } else if (condition.getExists != null && !condition.getExists) {
141 | Some((condition.getName, None))
142 | } else if (condition.getValue != null) {
143 | Some((condition.getName, Some(condition.getValue)))
144 | } else if (condition.getName != null) {
145 | sys.error("Expected value or false exists")
146 | } else {
147 | None
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | fakesdb
4 |
5 | FakeSdbServlet
6 | fakesdb.FakeSdbServlet
7 | 1
8 |
9 |
10 | FakeSdbServlet
11 | /*
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/AbstractFakeSdbTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import org.hamcrest.Matchers._
6 | import com.amazonaws.AmazonServiceException
7 | import com.amazonaws.auth.BasicAWSCredentials
8 | import com.amazonaws.services.simpledb.AmazonSimpleDBClient
9 | import com.amazonaws.services.simpledb.model._
10 | import scala.collection.JavaConversions._
11 |
12 | object AbstractFakeSdbTest {
13 | val jetty = Jetty(8080)
14 | jetty.server.start()
15 | }
16 |
17 | abstract class AbstractFakeSdbTest {
18 |
19 | // start jetty
20 | AbstractFakeSdbTest.jetty
21 |
22 | val sdb = new AmazonSimpleDBClient(new BasicAWSCredentials("ignored", "ignored"))
23 | sdb.setEndpoint("http://127.0.0.1:8080")
24 |
25 | val domaina = "domaina"
26 |
27 | @Before
28 | def setUp(): Unit = {
29 | flush()
30 | }
31 |
32 | def flush(): Unit = {
33 | createDomain("_flush")
34 | }
35 |
36 | type KV = Tuple2[String, String]
37 |
38 | def add(domain: String, itemName: String, attrs: KV*): Unit = {
39 | _add(domain, itemName, attrs, None)
40 | }
41 |
42 | def add(domain: String, itemName: String, cond: UpdateCondition, attrs: KV*): Unit = {
43 | _add(domain, itemName, attrs, Some(cond))
44 | }
45 |
46 | def doesNotExist(attrName: String) = new UpdateCondition(attrName, null, false)
47 |
48 | def hasValue(attrName: String, attrValue: String) = new UpdateCondition(attrName, attrValue, true)
49 |
50 | def assertFails(code: String, message: String, block: => Unit) {
51 | try {
52 | block
53 | fail("Should have failed with " + message)
54 | } catch {
55 | case e: AmazonServiceException => {
56 | assertThat(e.getErrorCode, is(code))
57 | assertThat(e.getMessage, startsWith(message))
58 | }
59 | case e: Exception => {
60 | assertThat(e.getMessage, startsWith(message))
61 | }
62 | }
63 | }
64 |
65 | def assertItems(domain: String, item: String, expectedItems: String*) {
66 | val result = sdb.getAttributes(new GetAttributesRequest(domain, item))
67 | val actualItems = result.getAttributes.map { a => a.getName + " = " + a.getValue }
68 | assertEquals(expectedItems.mkString("\n"), actualItems.mkString("\n"))
69 | }
70 |
71 | private def _add(domain: String, itemName: String, attrs: Seq[KV], cond: Option[UpdateCondition]): Unit = {
72 | val req = new PutAttributesRequest()
73 | .withDomainName(domain)
74 | .withItemName(itemName)
75 | for (a <- attrs) {
76 | val replace = asScalaBuffer(req.getAttributes).exists { _.getName == a._1 }
77 | req.withAttributes(new ReplaceableAttribute(a._1, a._2, replace))
78 | }
79 | cond match {
80 | case Some(c) => req.withExpected(c)
81 | case None =>
82 | }
83 | sdb.putAttributes(req)
84 | }
85 |
86 | protected def select(query: String): SelectResult = {
87 | sdb.select(new SelectRequest(query, true))
88 | }
89 |
90 | protected def select(query: String, nextToken: String): SelectResult = {
91 | sdb.select(new SelectRequest(query, true).withNextToken(nextToken))
92 | }
93 |
94 | protected def createDomain(name: String) {
95 | sdb.createDomain(new CreateDomainRequest(name))
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/BatchDeleteAttributesTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import com.amazonaws.services.simpledb.model._
6 |
7 | class BatchDeleteAttributesTest extends AbstractFakeSdbTest {
8 |
9 | @Before
10 | def createDomain(): Unit = {
11 | createDomain(domaina)
12 | }
13 |
14 | @Test
15 | def testDelete(): Unit = {
16 | add(domaina, "itema", "a" -> "1")
17 | add(domaina, "itemb", "a" -> "1", "b" -> "1")
18 |
19 | sdb.batchDeleteAttributes(new BatchDeleteAttributesRequest().withDomainName(domaina).withItems(
20 | new DeletableItem().withName("itema"),
21 | new DeletableItem().withName("itemb")
22 | ))
23 |
24 | assertEquals(0, select("SELECT * FROM domaina").getItems.size)
25 | }
26 |
27 | @Test
28 | def testDeleteTooMany(): Unit = {
29 | val req = new BatchDeleteAttributesRequest().withDomainName(domaina)
30 | for (i <- 1.to(26)) {
31 | req.withItems(new DeletableItem().withName("item" + i))
32 | }
33 | assertFails("NumberSubmittedItemsExceeded", "NumberSubmittedItemsExceeded: Too many items in a single call. Up to 25 items per call allowed.", {
34 | sdb.batchDeleteAttributes(req)
35 | })
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/BatchPutAttributesTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import com.amazonaws.services.simpledb.model._
5 |
6 | class BatchPutAttributesTest extends AbstractFakeSdbTest {
7 |
8 | @Before
9 | def createDomain(): Unit = {
10 | createDomain("domaina")
11 | }
12 |
13 | @Test
14 | def testPut(): Unit = {
15 | val req = new BatchPutAttributesRequest().withDomainName("domaina").withItems(
16 | new ReplaceableItem("itema").withAttributes(
17 | new ReplaceableAttribute("a", "1", true),
18 | new ReplaceableAttribute("b", "2", true)
19 | ),
20 | new ReplaceableItem("itemb").withAttributes(
21 | new ReplaceableAttribute("c", "3", true)
22 | )
23 | )
24 | sdb.batchPutAttributes(req)
25 |
26 | // Check results
27 | assertItems("domaina", "itema", "a = 1", "b = 2")
28 | assertItems("domaina", "itemb", "c = 3")
29 | }
30 |
31 | @Test
32 | def testPutTooMany(): Unit = {
33 | val req = new BatchPutAttributesRequest().withDomainName("domaina")
34 | for (i <- 1.to(26)) {
35 | req.withItems(new ReplaceableItem("item"+i).withAttributes(
36 | new ReplaceableAttribute("a", "1", true)
37 | ))
38 | }
39 | assertFails("NumberSubmittedItemsExceeded", "NumberSubmittedItemsExceeded: Too many items in a single call. Up to 25 items per call allowed.", {
40 | sdb.batchPutAttributes(req)
41 | })
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/CreateDomainTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 |
5 | class CreateDomainTest extends AbstractFakeSdbTest {
6 |
7 | @Test
8 | def invalidDomainName(): Unit = {
9 | assertFails("InvalidParameterValue", "InvalidParameterValue: Value (\"foo!\") for parameter DomainName is invalid.", {
10 | createDomain("foo!")
11 | })
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/DeleteAttributesTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import com.amazonaws.services.simpledb.model._
6 | import com.amazonaws.services.simpledb.model.{Attribute => AwsAttribute}
7 | import scala.collection.JavaConversions._
8 |
9 | class DeleteAttributesTest extends AbstractFakeSdbTest {
10 |
11 | @Before
12 | def createDomain(): Unit = {
13 | createDomain("domaina")
14 | }
15 |
16 | @Test
17 | def testDeleteOneEntireAttribute(): Unit = {
18 | add(domaina, "itema", "a" -> "1", "b" -> "2")
19 | assertItems(domaina, "itema", "a = 1", "b = 2")
20 |
21 | delete("a")
22 | assertItems(domaina, "itema", "b = 2")
23 | }
24 |
25 | @Test
26 | def testDeleteBothAttributesDeletesTheItem(): Unit = {
27 | add(domaina, "itema", "a" -> "1", "b" -> "2")
28 | delete("a", "b")
29 | assertEquals(0, select("SELECT * FROM domaina").getItems.size)
30 | }
31 |
32 | @Test
33 | def testDeleteNoAttributesDeletesTheItem(): Unit = {
34 | add(domaina, "itema", "a" -> "1", "b" -> "2")
35 | delete()
36 | assertEquals(0, select("SELECT * FROM domaina").getItems.size)
37 | }
38 |
39 | @Test
40 | def testDeleteConditionalValueSuccess(): Unit = {
41 | add(domaina, "itema", "a" -> "1", "b" -> "2")
42 | delete(hasValue("a", "1"), "a")
43 | assertHas("b = 2")
44 | }
45 |
46 | @Test
47 | def testDeleteConditionalDoesNotExistSuccess(): Unit = {
48 | add(domaina, "itema", "a" -> "1", "b" -> "2")
49 | delete(doesNotExist("c"), "a")
50 | assertHas("b = 2")
51 | }
52 |
53 | @Test
54 | def testDeleteConditionalValueFailure(): Unit = {
55 | add(domaina, "itema", "a" -> "1", "b" -> "2")
56 | assertFails("ConditionalCheckFailed", "ConditionalCheckFailedException: Attribute (a) value is (List(1)) but was expected (2)", {
57 | delete(hasValue("a", "2"), "a")
58 | })
59 | assertHas("a = 1", "b = 2")
60 | }
61 |
62 | @Test
63 | def testDeleteConditionalDoesNotExistFailure(): Unit = {
64 | add(domaina, "itema", "a" -> "1", "b" -> "2")
65 | assertFails("ConditionalCheckFailed", "ConditionalCheckFailedException: Attribute (b) value exists", {
66 | delete(doesNotExist("b"), "a")
67 | })
68 | assertHas("a = 1", "b = 2")
69 | }
70 |
71 | private def assertHas(attrs: String*): Unit = {
72 | assertItems(domaina, "itema", attrs: _*)
73 | }
74 |
75 | private def delete(attrNames: String*): Unit = {
76 | sdb.deleteAttributes(new DeleteAttributesRequest()
77 | .withDomainName(domaina)
78 | .withItemName("itema")
79 | .withAttributes(attrNames.map { new AwsAttribute(_, null) }))
80 | }
81 |
82 | private def delete(cond: UpdateCondition, attrNames: String*): Unit = {
83 | sdb.deleteAttributes(new DeleteAttributesRequest()
84 | .withDomainName(domaina)
85 | .withItemName("itema")
86 | .withAttributes(attrNames.map { new AwsAttribute(_, null) })
87 | .withExpected(cond))
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/DomainMetadataTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import com.amazonaws.services.simpledb.model._
6 |
7 | class DomainMetadataTest extends AbstractFakeSdbTest {
8 |
9 | @Before
10 | def createDomain(): Unit = {
11 | createDomain(domaina)
12 | }
13 |
14 | @Test
15 | def testFoo(): Unit = {
16 | add(domaina, "itema", "aa" -> "111", "bb" -> "222", "bb" -> "333")
17 |
18 | val result = sdb.domainMetadata(new DomainMetadataRequest(domaina))
19 | assertEquals(1, result.getItemCount)
20 | assertEquals(5l, result.getItemNamesSizeBytes)
21 | assertEquals(2, result.getAttributeNameCount)
22 | assertEquals(4l, result.getAttributeNamesSizeBytes)
23 | assertEquals(3, result.getAttributeValueCount)
24 | assertEquals(9l, result.getAttributeValuesSizeBytes)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/ErrorTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 |
6 | class ErrorTest extends AbstractFakeSdbTest {
7 | @Test
8 | def testEmptyQuery(): Unit = {
9 | assertFails("InternalError", "RuntimeException: Invalid from domaina", {
10 | select("SELECT * FROM domaina")
11 | })
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/FlushDomainsTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 |
6 | class FlushDomainsTest extends AbstractFakeSdbTest {
7 |
8 | @Test
9 | def testFoo(): Unit = {
10 | // Start with a flush
11 | createDomain("_flush")
12 |
13 | // Now two real ones
14 | createDomain("one")
15 | createDomain("two")
16 |
17 | // See that we've got 2
18 | val domains = sdb.listDomains.getDomainNames
19 | assertEquals(2, domains.size)
20 | assertEquals("one", domains.get(0))
21 | assertEquals("two", domains.get(1))
22 |
23 | // Re-flush
24 | createDomain("_flush")
25 |
26 | // Now 0
27 | assertEquals(0, sdb.listDomains.getDomainNames.size)
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/GetAttributesTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import com.amazonaws.services.simpledb.model._
6 |
7 | class GetAttributesTest extends AbstractFakeSdbTest {
8 |
9 | @Before
10 | def createDomain(): Unit = {
11 | createDomain(domaina)
12 | }
13 |
14 | @Test
15 | def testGetMultipleValues(): Unit = {
16 | add(domaina, "itema", "a" -> "1", "a" -> "2", "b" -> "3")
17 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, "itema")).getAttributes
18 | assertEquals(3, attrs.size)
19 | assertEquals("a", attrs.get(0).getName)
20 | assertEquals("1", attrs.get(0).getValue)
21 | assertEquals("a", attrs.get(1).getName)
22 | assertEquals("2", attrs.get(1).getValue)
23 | assertEquals("b", attrs.get(2).getName)
24 | assertEquals("3", attrs.get(2).getValue)
25 | }
26 |
27 | @Test
28 | def testGetOneAttribute(): Unit = {
29 | add(domaina, "itema",
30 | "a" -> "1",
31 | "b" -> "2")
32 |
33 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, "itema").withAttributeNames("a")).getAttributes
34 | assertEquals(1, attrs.size)
35 | assertEquals("a", attrs.get(0).getName)
36 | assertEquals("1", attrs.get(0).getValue)
37 | }
38 |
39 | @Test
40 | def testGetAttributesDoesNotCreateAnItem(): Unit = {
41 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, "itema")).getAttributes
42 | assertEquals(0, attrs.size)
43 | assertEquals(0, select("SELECT * FROM domaina").getItems.size)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/ListDomainsTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 |
6 | class ListDomainsTest extends AbstractFakeSdbTest {
7 |
8 | @Test
9 | def testFoo(): Unit = {
10 | createDomain("domain1")
11 | createDomain("domain2")
12 |
13 | val domains = sdb.listDomains.getDomainNames
14 | assertEquals(2, domains.size)
15 | assertEquals("domain1", domains.get(0))
16 | assertEquals("domain2", domains.get(1))
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/PutAttributesTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import scala.collection.mutable._
6 | import scala.collection.JavaConversions._
7 | import com.amazonaws.services.simpledb.model._
8 |
9 | class PutAttributesTest extends AbstractFakeSdbTest {
10 |
11 | private val nameOf1025 = "a" * 1025
12 | val itema = "itema"
13 |
14 | @Before
15 | def createDomain(): Unit = {
16 | createDomain(domaina)
17 | }
18 |
19 | @Test
20 | def testPutOne(): Unit = {
21 | add(domaina, itema, "a" -> "1")
22 | assertItems(domaina, itema, "a = 1")
23 | }
24 |
25 | @Test
26 | def testPutMultipleValues(): Unit = {
27 | add(domaina, itema, "a" -> "1", "a" -> "2")
28 | assertItems(domaina, itema, "a = 1", "a = 2")
29 | }
30 |
31 | @Test
32 | def testPutMultipleValuesWithSameValue(): Unit = {
33 | add(domaina, itema, "a" -> "1", "a" -> "1")
34 | assertItems(domaina, itema, "a = 1")
35 | }
36 |
37 | @Test
38 | def testLimitInTwoRequests(): Unit = {
39 | add(domaina, "itema", "a" -> "1", "b" -> "1")
40 | assertFails("NumberItemAttributesExceeded", "NumberItemAttributesExceededException: Too many attributes in this item", {
41 | addLots("itema", 255)
42 | })
43 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, itema)).getAttributes
44 | assertEquals(true, attrs.find { _.getName == "attr1" }.isDefined)
45 | assertEquals(true, attrs.find { _.getName == "attr254" }.isDefined)
46 | assertEquals(false, attrs.find { _.getName == "attr255" }.isDefined)
47 | }
48 |
49 | @Test
50 | def testLimitInTwoRequestsWithOverlappingAttributesIsOkay(): Unit = {
51 | add(domaina, "itema", "attr256" -> "value256") // value255 matches our new value
52 | addLots("itema", 256)
53 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, itema)).getAttributes
54 | assertEquals(256, attrs.size)
55 | assertEquals(true, attrs.find { _.getName == "attr1" }.isDefined)
56 | assertEquals(true, attrs.find { _.getName == "attr256" }.isDefined)
57 | }
58 |
59 | @Test
60 | def testLimitInTwoRequestsWithNonOverlappingAttributeValuesFails(): Unit = {
61 | add(domaina, "itema", "attr256" -> "valueFoo") // valueFoo does not match our new value
62 | assertFails("NumberItemAttributesExceeded", "NumberItemAttributesExceededException: Too many attributes in this item", {
63 | addLots("itema", 256)
64 | })
65 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, itema)).getAttributes
66 | assertEquals(256, attrs.size)
67 | assertEquals(true, attrs.find { (a) => a.getName == "attr1" }.isDefined)
68 | assertEquals(true, attrs.find { (a) => a.getName == "attr256" && a.getValue == "value256" }.isEmpty)
69 | }
70 |
71 | @Test
72 | def testLimitInOneRequest(): Unit = {
73 | assertFails("NumberItemAttributesExceeded", "NumberItemAttributesExceededException: Too many attributes in this item", {
74 | addLots("itema", 257)
75 | })
76 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, itema)).getAttributes
77 | assertEquals(true, attrs.find { _.getName == "attr257" }.isEmpty)
78 | }
79 |
80 | @Test
81 | def testFailEmptyAttributeName(): Unit = {
82 | assertFails("InvalidParameterValue", "EmptyAttributeNameException: Value () for parameter Name is invalid. The empty string is an illegal attribute name", {
83 | add(domaina, "itema", "" -> "1")
84 | })
85 | }
86 |
87 | @Test
88 | def testConditionalPutSucceeds(): Unit = {
89 | add(domaina, "itema", "a" -> "1")
90 | add(domaina, "itema", hasValue("a", "1"), "b" -> "1")
91 | val attrs = sdb.getAttributes(new GetAttributesRequest(domaina, itema)).getAttributes
92 | assertEquals(2, attrs.size)
93 | assertEquals("a", attrs.get(0).getName)
94 | assertEquals("1", attrs.get(0).getValue)
95 | assertEquals("b", attrs.get(1).getName)
96 | assertEquals("1", attrs.get(1).getValue)
97 | }
98 |
99 | @Test
100 | def testConditionalPutFailsWithWrongValue(): Unit = {
101 | add(domaina, "itema", "a" -> "1")
102 | assertFails("ConditionalCheckFailed", "ConditionalCheckFailedException: Attribute (a) value is (List(1)) but was expected (2)", {
103 | add(domaina, "itema", hasValue("a", "2"), "b" -> "1")
104 | })
105 | }
106 |
107 | @Test
108 | def testConditionalPutDoesNotExist(): Unit = {
109 | add(domaina, "itema", "a" -> "1")
110 | assertFails("ConditionalCheckFailed", "ConditionalCheckFailedException: Attribute (a) value exists", {
111 | add(domaina, "itema", doesNotExist("a"), "b" -> "1")
112 | })
113 | }
114 |
115 | @Test
116 | def testConditionalPutFailsAgainstMutipleValues(): Unit = {
117 | add(domaina, "itema", "a" -> "1", "a" -> "2")
118 | assertFails("ConditionalCheckFailed", "ConditionalCheckFailedException: Attribute (a) value is (List(1, 2)) but was expected (1)", {
119 | add(domaina, "itema", hasValue("a", "1"), "b" -> "1")
120 | })
121 | }
122 |
123 | @Test
124 | def testConditionalPutFailsAgainstInvalidAttribute(): Unit = {
125 | add(domaina, "itema", "a" -> "1")
126 | assertFails("AttributeDoesNotExist", "AttributeDoesNotExistException: Attribute (c) does not exist", {
127 | add(domaina, "itema", hasValue("c", "1"), "b" -> "1")
128 | })
129 | }
130 |
131 | @Test
132 | def testTooLongItemName(): Unit = {
133 | assertFails("InvalidParameterValue", "InvalidParameterValue: Value (\"%s\") for parameter Name is invalid. Value exceeds maximum length of 1024.".format(nameOf1025), {
134 | add(domaina, nameOf1025, "a" -> "1")
135 | })
136 | }
137 |
138 | @Test
139 | def testTooLongAttributeName(): Unit = {
140 | assertFails("InvalidParameterValue", "InvalidParameterValue: Value (\"%s\") for parameter Name is invalid. Value exceeds maximum length of 1024.".format(nameOf1025), {
141 | add(domaina, "i", nameOf1025 -> "1")
142 | })
143 | }
144 |
145 | @Test
146 | def testTooLongValue(): Unit = {
147 | assertFails("InvalidParameterValue", "InvalidParameterValue: Value (\"%s\") for parameter Value is invalid. Value exceeds maximum length of 1024.".format(nameOf1025), {
148 | add(domaina, "i", "a" -> nameOf1025)
149 | })
150 | }
151 |
152 | private def addLots(itemName: String, number: Int): Unit = {
153 | val req = new PutAttributesRequest().withDomainName(domaina).withItemName(itemName)
154 | for (i <- 1.to(number)) {
155 | req.withAttributes(new ReplaceableAttribute("attr" + i, "value" + i, false))
156 | }
157 | sdb.putAttributes(req)
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/SelectParserTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 |
6 | class SelectParserTest {
7 |
8 | val data = new Data
9 | var domaina: Domain = null
10 |
11 | @Before
12 | def setUp(): Unit = {
13 | data.flush
14 | domaina = data.getOrCreateDomain("domaina")
15 | }
16 |
17 | @Test
18 | def testEmptyFrom(): Unit = {
19 | val se = SelectParser.makeSelectEval("select * from domaina")
20 | assertEquals(0, se.select(data)._1.size)
21 | }
22 |
23 | @Test
24 | def testFromWithData(): Unit = {
25 | domaina.getOrCreateItem("itema").put("a", "1", true)
26 | domaina.getOrCreateItem("itema").put("b", "2", true)
27 | domaina.getOrCreateItem("itemb").put("c", "3", true)
28 | val results = SelectParser.makeSelectEval("select * from domaina").select(data)._1
29 | assertEquals(2, results.size)
30 | assertEquals(("itema", List(("a", "1"), ("b", "2"))), results(0))
31 | assertEquals(("itemb", List(("c", "3"))), results(1))
32 | }
33 |
34 | @Test
35 | def testFromWithOneAttribute(): Unit = {
36 | domaina.getOrCreateItem("itema").put("a", "1", true)
37 | domaina.getOrCreateItem("itema").put("b", "2", true)
38 | domaina.getOrCreateItem("itemb").put("a", "3", true)
39 | // a is in both
40 | val results = SelectParser.makeSelectEval("select a from domaina").select(data)._1
41 | assertEquals(2, results.size)
42 | assertEquals(("itema", List(("a", "1"))), results(0))
43 | assertEquals(("itemb", List(("a", "3"))), results(1))
44 | // b in in only one
45 | val results2 = SelectParser.makeSelectEval("select b from domaina").select(data)._1
46 | assertEquals(1, results2.size)
47 | assertEquals(("itema", List(("b", "2"))), results2(0))
48 | }
49 |
50 | @Test
51 | def testFromWithOneAttributeWithMultipleValues(): Unit = {
52 | domaina.getOrCreateItem("itema").put("a", "1", true)
53 | domaina.getOrCreateItem("itema").put("a", "1", false)
54 | val results = SelectParser.makeSelectEval("select a from domaina").select(data)._1
55 | assertEquals(1, results.size)
56 | assertEquals(("itema", List(("a", "1"))), results(0))
57 | }
58 |
59 | @Test
60 | def testFromWithTwoAttributes(): Unit = {
61 | domaina.getOrCreateItem("itema").put("a", "1", true)
62 | domaina.getOrCreateItem("itema").put("b", "2", true)
63 | domaina.getOrCreateItem("itemb").put("a", "3", true)
64 | val results = SelectParser.makeSelectEval("select a, b from domaina").select(data)._1
65 | assertEquals(2, results.size)
66 | assertEquals(("itema", List(("a", "1"), ("b", "2"))), results(0))
67 | assertEquals(("itemb", List(("a", "3"))), results(1))
68 | }
69 |
70 | @Test
71 | def testFromCount(): Unit = {
72 | domaina.getOrCreateItem("itema").put("a", "1", true)
73 | val results = SelectParser.makeSelectEval("select count(*) from domaina").select(data)._1
74 | assertEquals(1, results.size)
75 | assertEquals(("Domain", List(("Count", "1"))), results(0))
76 | }
77 |
78 | @Test
79 | def testFromCountUpperCase(): Unit = {
80 | domaina.getOrCreateItem("itema").put("a", "1", true)
81 | val results = SelectParser.makeSelectEval("SELECT COUNT(*) FROM domaina").select(data)._1
82 | assertEquals(1, results.size)
83 | assertEquals(("Domain", List(("Count", "1"))), results(0))
84 | }
85 |
86 | @Test
87 | def testFromItemName(): Unit = {
88 | domaina.getOrCreateItem("itema").put("a", "1", true)
89 | val results = SelectParser.makeSelectEval("select itemName(), a from domaina").select(data)._1
90 | assertEquals(1, results.size)
91 | assertEquals(("itema", List(("itemName()", "itema"), ("a", "1"))), results(0))
92 | }
93 |
94 | @Test
95 | def testWhereEquals(): Unit = {
96 | domaina.getOrCreateItem("itema").put("a", "1", true)
97 | domaina.getOrCreateItem("itemb").put("a", "2", true)
98 | val results = SelectParser.makeSelectEval("select * from domaina where a = '1'").select(data)._1
99 | assertEquals(1, results.size)
100 | assertEquals(("itema", List(("a", "1"))), results(0))
101 | }
102 |
103 | @Test
104 | def testWhereDoubleTicks(): Unit = {
105 | domaina.getOrCreateItem("itema").put("a", "1'1", true)
106 | domaina.getOrCreateItem("itemb").put("a", "2'2", true)
107 | val results = SelectParser.makeSelectEval("select * from domaina where a = '1''1'").select(data)._1
108 | assertEquals(1, results.size)
109 | assertEquals(("itema", List(("a", "1'1"))), results(0))
110 | }
111 |
112 | @Test
113 | def testWhereDoubleQuotes(): Unit = {
114 | domaina.getOrCreateItem("itema").put("a", "1\"1", true)
115 | domaina.getOrCreateItem("itemb").put("a", "2\"2", true)
116 | val results = SelectParser.makeSelectEval("select * from domaina where a = \"1\"\"1\"").select(data)._1
117 | assertEquals(1, results.size)
118 | assertEquals(("itema", List(("a", "1\"1"))), results(0))
119 | }
120 |
121 | @Test
122 | def testWhereNotEquals(): Unit = {
123 | domaina.getOrCreateItem("itema").put("a", "1", true)
124 | domaina.getOrCreateItem("itemb").put("a", "2", true)
125 | val results = SelectParser.makeSelectEval("select * from domaina where a != '1'").select(data)._1
126 | assertEquals(1, results.size)
127 | assertEquals(("itemb", List(("a", "2"))), results(0))
128 | }
129 |
130 | @Test
131 | def testWhereGreaterThan(): Unit = {
132 | domaina.getOrCreateItem("itema").put("a", "1", true)
133 | domaina.getOrCreateItem("itemb").put("a", "2", true)
134 | val results = SelectParser.makeSelectEval("select * from domaina where a > '1'").select(data)._1
135 | assertEquals(1, results.size)
136 | assertEquals(("itemb", List(("a", "2"))), results(0))
137 | }
138 |
139 | @Test
140 | def testWhereLessThan(): Unit = {
141 | domaina.getOrCreateItem("itema").put("a", "1", true)
142 | domaina.getOrCreateItem("itemb").put("a", "2", true)
143 | val results = SelectParser.makeSelectEval("select * from domaina where a < '2'").select(data)._1
144 | assertEquals(1, results.size)
145 | assertEquals(("itema", List(("a", "1"))), results(0))
146 | }
147 |
148 | @Test
149 | def testWhereGreaterThanOrEqual(): Unit = {
150 | domaina.getOrCreateItem("itema").put("a", "1", true)
151 | domaina.getOrCreateItem("itemb").put("a", "2", true)
152 | domaina.getOrCreateItem("itemc").put("a", "3", true)
153 | val results = SelectParser.makeSelectEval("select * from domaina where a >= '2'").select(data)._1
154 | assertEquals(2, results.size)
155 | assertEquals(("itemb", List(("a", "2"))), results(0))
156 | assertEquals(("itemc", List(("a", "3"))), results(1))
157 | }
158 |
159 | @Test
160 | def testWhereLessThanOrEqual(): Unit = {
161 | domaina.getOrCreateItem("itema").put("a", "1", true)
162 | domaina.getOrCreateItem("itemb").put("a", "2", true)
163 | domaina.getOrCreateItem("itemc").put("a", "3", true)
164 | val results = SelectParser.makeSelectEval("select * from domaina where a <= '2'").select(data)._1
165 | assertEquals(2, results.size)
166 | assertEquals(("itema", List(("a", "1"))), results(0))
167 | assertEquals(("itemb", List(("a", "2"))), results(1))
168 | }
169 |
170 | @Test
171 | def testWhereLike(): Unit = {
172 | domaina.getOrCreateItem("itema").put("a", "1", true)
173 | domaina.getOrCreateItem("itemb").put("a", "2", true)
174 | val results = SelectParser.makeSelectEval("select * from domaina where a like '1%'").select(data)._1
175 | assertEquals(1, results.size)
176 | assertEquals(("itema", List(("a", "1"))), results(0))
177 | }
178 |
179 | @Test
180 | def testWhereNotLike(): Unit = {
181 | domaina.getOrCreateItem("itema").put("a", "1", true)
182 | domaina.getOrCreateItem("itemb").put("a", "2", true)
183 | val results = SelectParser.makeSelectEval("select * from domaina where a not like '1%'").select(data)._1
184 | assertEquals(1, results.size)
185 | assertEquals(("itemb", List(("a", "2"))), results(0))
186 | }
187 |
188 | @Test
189 | def testWhereIsNull(): Unit = {
190 | domaina.getOrCreateItem("itema").put("a", "1", true)
191 | domaina.getOrCreateItem("itemb").put("b", "2", true)
192 | val results = SelectParser.makeSelectEval("select * from domaina where a is null").select(data)._1
193 | assertEquals(1, results.size)
194 | assertEquals(("itemb", List(("b", "2"))), results(0))
195 | }
196 |
197 | @Test
198 | def testWhereIsNotNull(): Unit = {
199 | domaina.getOrCreateItem("itema").put("a", "1", true)
200 | domaina.getOrCreateItem("itemb").put("b", "2", true)
201 | val results = SelectParser.makeSelectEval("select * from domaina where a is not null").select(data)._1
202 | assertEquals(1, results.size)
203 | assertEquals(("itema", List(("a", "1"))), results(0))
204 | }
205 |
206 | @Test
207 | def testWhereBetween(): Unit = {
208 | domaina.getOrCreateItem("itema").put("a", "1", true)
209 | domaina.getOrCreateItem("itemb").put("a", "2", true)
210 | val results = SelectParser.makeSelectEval("select * from domaina where a between '2' and '3'").select(data)._1
211 | assertEquals(1, results.size)
212 | assertEquals(("itemb", List(("a", "2"))), results(0))
213 | }
214 |
215 | @Test
216 | def testWhereEvery(): Unit = {
217 | domaina.getOrCreateItem("itema").put("a", "1", true)
218 | domaina.getOrCreateItem("itemb").put("a", "1", true)
219 | domaina.getOrCreateItem("itemb").put("a", "2", true)
220 | val results = SelectParser.makeSelectEval("select * from domaina where every(a) = '1'").select(data)._1
221 | assertEquals(1, results.size)
222 | assertEquals(("itema", List(("a", "1"))), results(0))
223 | }
224 |
225 | @Test
226 | def testWhereIn(): Unit = {
227 | domaina.getOrCreateItem("itema").put("a", "1", true)
228 | domaina.getOrCreateItem("itemb").put("a", "2", true)
229 | domaina.getOrCreateItem("itemc").put("a", "3", true)
230 | val results = SelectParser.makeSelectEval("select * from domaina where a in ('1', '2')").select(data)._1
231 | assertEquals(2, results.size)
232 | assertEquals(("itema", List(("a", "1"))), results(0))
233 | assertEquals(("itemb", List(("a", "2"))), results(1))
234 | }
235 |
236 | @Test
237 | def testWhereEqualsAnd(): Unit = {
238 | domaina.getOrCreateItem("itema").put("a", "1", true)
239 | domaina.getOrCreateItem("itema").put("b", "2", true)
240 | domaina.getOrCreateItem("itemb").put("a", "1", true)
241 | val results = SelectParser.makeSelectEval("select * from domaina where a = '1' and b = '2'").select(data)._1
242 | assertEquals(1, results.size)
243 | assertEquals(("itema", List(("a", "1"), ("b", "2"))), results(0))
244 | }
245 |
246 | @Test
247 | def testWhereEqualsOr(): Unit = {
248 | domaina.getOrCreateItem("itema").put("a", "1", true)
249 | domaina.getOrCreateItem("itemb").put("a", "2", true)
250 | val results = SelectParser.makeSelectEval("select * from domaina where a = '1' or a = '2'").select(data)._1
251 | assertEquals(2, results.size)
252 | assertEquals(("itema", List(("a", "1"))), results(0))
253 | assertEquals(("itemb", List(("a", "2"))), results(1))
254 | }
255 |
256 | @Test
257 | def testWhereParens(): Unit = {
258 | domaina.getOrCreateItem("itema").put("a", "1", true)
259 | domaina.getOrCreateItem("itema").put("b", "1", true)
260 | domaina.getOrCreateItem("itemb").put("a", "2", true)
261 | val results = SelectParser.makeSelectEval("select * from domaina where (a = '1' and b = '1') or a = '2'").select(data)._1
262 | assertEquals(2, results.size)
263 | assertEquals(("itema", List(("a", "1"), ("b", "1"))), results(0))
264 | assertEquals(("itemb", List(("a", "2"))), results(1))
265 | }
266 |
267 | @Test
268 | def testOrderBy(): Unit = {
269 | domaina.getOrCreateItem("itema").put("a", "2", true)
270 | domaina.getOrCreateItem("itemb").put("a", "1", true)
271 | val results = SelectParser.makeSelectEval("select * from domaina where a >= '1' order by a").select(data)._1
272 | assertEquals(2, results.size)
273 | assertEquals(("itemb", List(("a", "1"))), results(0))
274 | assertEquals(("itema", List(("a", "2"))), results(1))
275 | }
276 |
277 | @Test
278 | def testOrderByDesc(): Unit = {
279 | domaina.getOrCreateItem("itema").put("a", "1", true)
280 | domaina.getOrCreateItem("itemb").put("a", "2", true)
281 | val results = SelectParser.makeSelectEval("select * from domaina where a >= '1' order by a desc").select(data)._1
282 | assertEquals(2, results.size)
283 | assertEquals(("itemb", List(("a", "2"))), results(0))
284 | assertEquals(("itema", List(("a", "1"))), results(1))
285 | }
286 |
287 | @Test
288 | def testOrderByItemNameDesc(): Unit = {
289 | domaina.getOrCreateItem("itema").put("a", "1", true)
290 | domaina.getOrCreateItem("itemb").put("a", "2", true)
291 | domaina.getOrCreateItem("itemc").put("a", "3", true)
292 | val results = SelectParser.makeSelectEval("select * from domaina where a >= '1' order by itemName() desc").select(data)._1
293 | assertEquals(3, results.size)
294 | assertEquals(("itemc", List(("a", "3"))), results(0))
295 | assertEquals(("itemb", List(("a", "2"))), results(1))
296 | assertEquals(("itema", List(("a", "1"))), results(2))
297 | }
298 |
299 | @Test
300 | def testWhereCrazyAnd(): Unit = {
301 | domaina.getOrCreateItem("itema").put("a", "1", true)
302 | domaina.getOrCreateItem("itema").put("a", "2", false)
303 | val results = SelectParser.makeSelectEval("select * from domaina where a = '1' and a = '2'").select(data)._1
304 | assertEquals(1, results.size) // FAIL
305 | }
306 |
307 | @Test
308 | def testWhereIntersection(): Unit = {
309 | domaina.getOrCreateItem("itema").put("a", "1", true)
310 | domaina.getOrCreateItem("itema").put("a", "2", false)
311 | domaina.getOrCreateItem("itemb").put("a", "1", true)
312 | val results = SelectParser.makeSelectEval("select * from domaina where a = '1' intersection a = '2'").select(data)._1
313 | assertEquals(1, results.size)
314 | assertEquals(("itema", List(("a", "1"), ("a", "2"))), results(0))
315 | }
316 |
317 | @Test
318 | def testKeyWithUnderscores(): Unit = {
319 | domaina.getOrCreateItem("itema").put("foo_bar", "1", true)
320 | val results = SelectParser.makeSelectEval("select * from domaina where foo_bar = '1'").select(data)._1
321 | assertEquals(1, results.size)
322 | assertEquals(("itema", List(("foo_bar", "1"))), results(0))
323 | }
324 |
325 | @Test
326 | def testCaseInsensitiveKeywords(): Unit = {
327 | domaina.getOrCreateItem("itema").put("foo_bar", "1", true)
328 | val results = SelectParser.makeSelectEval("SELECT * FROM domaina WHERE foo_bar = '1'").select(data)._1
329 | assertEquals(1, results.size)
330 | assertEquals(("itema", List(("foo_bar", "1"))), results(0))
331 | }
332 |
333 | @Test
334 | def testLarrysQuery(): Unit = {
335 | val usage = data.getOrCreateDomain("dev.api-web-usage")
336 | val a = usage.getOrCreateItem("itema")
337 | a.put("api_key", "1", true)
338 | a.put("dt", "2010010001", true)
339 | val results = SelectParser.makeSelectEval("select * from `dev.api-web-usage` where api_key = '1' and dt > '2010010000' and dt < '2010013224'").select(data)._1
340 | assertEquals(1, results.size)
341 | }
342 |
343 | @Test
344 | def testLimit(): Unit = {
345 | domaina.getOrCreateItem("itema").put("foo", "1", true)
346 | domaina.getOrCreateItem("itemb").put("foo", "2", true)
347 | domaina.getOrCreateItem("itemc").put("foo", "3", true)
348 | val results = SelectParser.makeSelectEval("SELECT * FROM domaina WHERE foo > '0' limit 2").select(data)._1
349 | assertEquals(2, results.size)
350 | assertEquals(("itema", List(("foo", "1"))), results(0))
351 | assertEquals(("itemb", List(("foo", "2"))), results(1))
352 | // Now with order by
353 | val results2 = SelectParser.makeSelectEval("SELECT * FROM domaina WHERE foo > '0' order by foo desc limit 2").select(data)._1
354 | assertEquals(2, results2.size)
355 | assertEquals(("itemc", List(("foo", "3"))), results2(0))
356 | assertEquals(("itemb", List(("foo", "2"))), results2(1))
357 | }
358 |
359 | @Test
360 | def testAttributeDoubleBacktick(): Unit = {
361 | domaina.getOrCreateItem("itema").put("a`a", "1", true)
362 | val results = SelectParser.makeSelectEval("select `a``a` from domaina where `a``a` = '1'").select(data)._1
363 | assertEquals(1, results.size)
364 | assertEquals(("itema", List(("a`a", "1"))), results(0))
365 | }
366 |
367 | @Test
368 | def testAttributeLegalChars(): Unit = {
369 | domaina.getOrCreateItem("itema").put("a1$_", "1", true)
370 | val results = SelectParser.makeSelectEval("select a1$_ from domaina where a1$_ = '1'").select(data)._1
371 | assertEquals(1, results.size)
372 | assertEquals(("itema", List(("a1$_", "1"))), results(0))
373 | }
374 |
375 | @Test
376 | def testAttributeLegalCharsInTheFirstPosition(): Unit = {
377 | domaina.getOrCreateItem("itema").put("$_", "1", true)
378 | val results = SelectParser.makeSelectEval("select $_ from domaina where $_ = '1'").select(data)._1
379 | assertEquals(1, results.size)
380 | assertEquals(("itema", List(("$_", "1"))), results(0))
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/src/test/scala/fakesdb/SelectTest.scala:
--------------------------------------------------------------------------------
1 | package fakesdb
2 |
3 | import org.junit._
4 | import org.junit.Assert._
5 | import scala.collection.JavaConversions._
6 | import com.amazonaws.services.simpledb.model._
7 |
8 | class SelectTest extends AbstractFakeSdbTest {
9 |
10 | @Before
11 | def createDomain() {
12 | createDomain(domaina)
13 | }
14 |
15 | @Test
16 | def testCount() {
17 | val results = select("select count(*) from domaina")
18 | assertEquals(1, results.getItems.size)
19 | val item = results.getItems.get(0)
20 | assertEquals("Domain", item.getName)
21 | assertEquals("Count", item.getAttributes.get(0).getName)
22 | assertEquals("0", item.getAttributes.get(0).getValue)
23 | assertEquals(null, results.getNextToken)
24 | }
25 |
26 | @Test
27 | def testNextTokenIsNotReturnedIfLimitIsMet() {
28 | for (i <- 1.to(10)) {
29 | add(domaina, i.toString(), "a" -> i.toString())
30 | }
31 | val results = select("select count(*) from domaina limit 10")
32 | assertEquals(null, results.getNextToken)
33 | }
34 |
35 | @Test
36 | def testPartialSelect() {
37 | for (i <- 1.to(10)) {
38 | add(domaina, i.toString(), "a" -> i.toString())
39 | }
40 | // http://stackoverflow.com/questions/1795245/how-to-do-paging-with-simpledb/1832779#1832779
41 | // first query to get a dummy next token
42 | var results = select("select count(*) from domaina limit 5")
43 | // second query to start after that
44 | results = select("select a from domaina limit 5", results.getNextToken)
45 | assertEquals(5, results.getItems.size)
46 | var i = 6
47 | results.getItems foreach { item =>
48 | item.getAttributes foreach { attr =>
49 | assertEquals(i.toString(), attr.getValue);
50 | i += 1
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------