Skip to content
Browse files

Add 'Worlds test'

  • Loading branch information...
1 parent 5d55b4e commit 5dff0ee86506051b2dae2da366b25f8eac755773 @guillaumebort guillaumebort committed Apr 6, 2011
View
4 build.xml
@@ -78,6 +78,10 @@
</antcall>
<antcall target="play-test">
+ <param name="testAppPath" value="${basedir}/samples-and-tests/play-with-scala"/>
+ </antcall>
+
+ <antcall target="play-test">
<param name="testAppPath" value="${basedir}/samples-and-tests/yabe"/>
</antcall>
View
26 documentation/manual/sql.textile
@@ -135,22 +135,22 @@ h2. Dealing with Nullable columns
If a column is defined as **Nullable** in the database schema, you need to manipulate it as an @Option@ type.
-For example, the **indepYear** of the **Country** table being nullable, you need to match it as @Option[Int]@:
+For example, the **indepYear** of the **Country** table being nullable, you need to match it as @Option[Short]@:
bc. SQL("Select name,indepYear from Country")().collect {
- case Row(name:String, Some(year:Int)) => name -> year
+ case Row(name:String, Some(year:Short)) => name -> year
}
-If you try to match this column as @Int@ it won't match. If you try to retrieve the column content as @Int@ directly from the dictionnary:
+If you try to match this column as @Short@ it won't match. If you try to retrieve the column content as @Short@ directly from the dictionnary:
bc. SQL("Select name,indepYear from Country")().map { row =>
- row[String]("name") -> row[Int]("indepYear")
+ row[String]("name") -> row[Short]("indepYear")
}
-It will produce an @UnexpectedNullableFound(COUNTRY.INDEPYEAR)@ exception. So you need to map it properly to an @Option[Int]@, as:
+It will produce an @UnexpectedNullableFound(COUNTRY.INDEPYEAR)@ exception. So you need to map it properly to an @Option[Short]@, as:
bc. SQL("Select name,indepYear from Country")().map { row =>
- row[String]("name") -> row[Option[Int]]("indepYear")
+ row[String]("name") -> row[Option[Short]]("indepYear")
}
This rule is also true for the parser API we will just see.
@@ -159,6 +159,8 @@ h2. Using the Parser combinator API
The "Scala Parsers API":http://www.scala-lang.org/api/current/scala/util/parsing/combinator/Parsers.html provides generic parser combinators. Play Scala can use them to parse the result of any Select query.
+p(note). First you need to import @play.db.sql.SqlParser._@.
+
Use the @as(…)@ method of the SQL statement to specify the parser you want to use. For example @scalar[Long]@ is a simple parser that knows how to parse a single column row as @Long@:
bc. val count:Long = SQL("select count(*) from Country").as(scalar[Long])
@@ -187,7 +189,17 @@ bc. val populations:List[String~Int] = {
)
}
-Let's try with a more complicated example. How to parse the result of the following query?
+When you parse a *ResultSet* using @as(…)@ it must consume all the input. If your parser doesn't consume all the available input, an error will be thrown. It avoids to have your parser fails silently.
+
+If you want to parse only a small part of the input, you can use @parse(…)@ instead of @as(…)@. However use it with caution, as it make it more difficult to detect errors in your code:
+
+bc. val onePopulation:String~Int = {
+ SQL("select * from Country").parse(
+ str("name") ~< int("population")
+ )
+}
+
+Now let's try with a more complicated example. How to parse the result of the following query?
bc. select c.name, c.code, l.language from Country c
join CountryLanguage l on l.CountryCode = c.Code
View
10 samples-and-tests/just-test-cases/conf/application.conf
@@ -78,9 +78,7 @@ application.log=INFO
# To quickly set up a development database, use either:
# - mem : for a transient in memory database (HSQL in memory)
# - fs : for a simple file written database (HSQL file stored)
-#db=mem
-db.driver=org.h2.Driver
-db.url=jdbc:h2:mem:
+db=mem
#
# To connect to a local MySQL5 database, use:
# db=mysql:user:pwd@database_name
@@ -179,11 +177,7 @@ mail.smtp=mock
# Testing. Set up a custom configuration for test mode
# ~~~~~
#%test.module.cobertura=${play.path}/modules/cobertura
-%test.db.driver=org.h2.Driver
-%test.db.url=jdbc:h2:mem:
+%test.db=mem
%test.application.mode=dev
-%test.jpa.dialect=org.hibernate.dialect.H2Dialect
-#%test.db=mem
-%test.jpa.ddl=create-drop
%test.mail.smtp=mock
View
261 samples-and-tests/just-test-cases/test/WorldTests.scala
@@ -0,0 +1,261 @@
+import play._
+import play.test._
+
+import org.junit._
+import org.scalatest.junit._
+import org.scalatest._
+import org.scalatest.matchers._
+
+class WorldTests extends UnitFlatSpec with ShouldMatchers {
+
+ // Load the World (check test/world.sql)
+ Fixtures.deleteDatabase
+ Fixtures.executeSQL(Play.getFile("test/world.sql"))
+
+ import play.db.sql._
+
+ it should "Execute SQL requests" in {
+
+ SQL("Select 1").execute() should be (true)
+
+ SQL("delete from City where id = 99").executeUpdate().fold(
+ e => "Oops, there was an error" ,
+ c => c + " rows were updated!"
+ ) should be ("1 rows were updated!")
+
+ SQL("delete from Country where id = 99").executeUpdate().fold(
+ e => "Oops, there was an error" ,
+ c => c + " rows were updated!"
+ ) should be ("Oops, there was an error")
+
+ SQL(
+ """
+ select * from Country c
+ join CountryLanguage l on l.CountryCode = c.Code
+ where c.code = 'FRA';
+ """
+ )
+
+ SQL(
+ """
+ select * from Country c
+ join CountryLanguage l on l.CountryCode = c.Code
+ where c.code = {countryCode};
+ """
+ ).on("countryCode" -> "FRA")
+
+ SQL(
+ """
+ select * from Country c
+ join CountryLanguage l on l.CountryCode = c.Code
+ where c.code = {countryCode};
+ """
+ ).onParams("FRA")
+
+ }
+
+ it should "Retrieve data using the Stream API" in {
+
+ val selectCountries = SQL("Select * from Country")
+
+ val countries = selectCountries().map(row =>
+ row[String]("code") -> row[String]("name")
+ ).toList
+
+ countries.size should be (239)
+ countries(72) should be ("FRA" -> "France")
+
+ val firstRow = SQL("Select count(*) as c from Country").apply().head
+
+ firstRow[Long]("c") should be (239)
+
+ }
+
+ it should "Use Pattern Matching" in {
+
+ case class SmallCountry(name:String)
+ case class BigCountry(name:String)
+ case class France()
+
+ val countries = SQL("Select name,population from Country")().collect {
+ case Row("France", _) => France()
+ case Row(name:String, pop:Int) if(pop > 1000000) => BigCountry(name)
+ case Row(name:String, _) => SmallCountry(name)
+ }
+
+ countries(0) should be (SmallCountry("Aruba"))
+ countries(50) should be (BigCountry("Costa Rica"))
+ countries(72) should be (France())
+
+ countries.size should be (239)
+
+ }
+
+ it should "Deal with Nullable columns" in {
+
+ val results = SQL("Select name,indepYear from Country")().collect {
+ case Row(name:String, Some(year:Short)) => name -> year
+ }
+
+ results.size should be (192)
+ results(0) should be ("Afghanistan" -> 1919)
+
+ val error = evaluating {
+ SQL("Select name,indepYear from Country")().map { row =>
+ row[String]("name") -> row[Short]("indepYear")
+ }
+ } should produce [RuntimeException]
+
+ error.getMessage should equal ("UnexpectedNullableFound(COUNTRY.INDEPYEAR)")
+
+ val all = SQL("Select name,indepYear from Country")().map { row =>
+ row[String]("name") -> row[Option[Short]]("indepYear")
+ }
+
+ all.size should be (239)
+ all(0) should be ("Aruba" -> None)
+ all(110) should be ("Kazakstan" -> Some(1991))
+
+ }
+
+ import play.db.sql.SqlParser._
+
+ it should "Use the Parser combinator API" in {
+
+ SQL("select count(*) from Country").as(scalar[Long]) should be (239)
+
+ val populations:List[String~Int] = {
+ SQL("select * from Country").as( str("name") ~< int("population") * )
+ }
+
+ populations.size should be (239)
+ populations(72) should be (new ~("France", 59225700))
+
+ val populations2:List[String~Int] = {
+ SQL("select * from Country").as('name.of[String]~<'population.of[Int]*)
+ }
+
+ populations2.size should be (239)
+ populations2(72) should be (new ~("France", 59225700))
+
+ val populations3:List[String~Int] = {
+ SQL("select * from Country").as(
+ get[String]("name") ~< get[Int]("population") *
+ )
+ }
+
+ populations3.size should be (239)
+ populations3(72) should be (new ~("France", 59225700))
+
+ val error = evaluating {
+ val populations4:String~Int = {
+ SQL("select * from Country").as( str("name") ~< int("population") )
+ }
+ } should produce [RuntimeException]
+
+ error.getMessage should be ("end of input expected")
+
+ val population4:String~Int = {
+ SQL("select * from Country").parse( str("name") ~< int("population") )
+ }
+
+ population4 should be (new ~("Aruba", 103000))
+
+ case class SpokenLanguages(country:String, languages:Seq[String])
+
+ val spokenLanguages = { countryCode:String =>
+ SQL(
+ """
+ select c.name, c.code, l.language from Country c
+ join CountryLanguage l on l.CountryCode = c.Code
+ where c.code = {code};
+ """
+ )
+ .on("code" -> countryCode)
+ .as(
+ str("name") ~< spanM(by=str("code"), str("language")) ^^ {
+ case country~languages => SpokenLanguages(country, languages)
+ } ?
+ )
+ }
+
+ spokenLanguages("FRA") should be (Some(SpokenLanguages("France",List("Arabic", "French", "Italian", "Portuguese", "Spanish", "Turkish"))))
+
+ case class SpokenLanguagesWithOfficial(
+ country:String,
+ officialLanguage: Option[String],
+ otherLanguages:Seq[String]
+ )
+
+ val spokenLanguagesWithOfficial = { countryCode:String =>
+ SQL(
+ """
+ select * from Country c
+ join CountryLanguage l on l.CountryCode = c.Code
+ where c.code = 'FRA';
+ """
+ ).as(
+ str("name") ~< spanM(
+ by=str("code"), str("language") ~< str("isOfficial")
+ ) ^^ {
+ case country~languages =>
+ SpokenLanguagesWithOfficial(
+ country,
+ languages.collect { case lang~"T" => lang } headOption,
+ languages.collect { case lang~"F" => lang }
+ )
+ } ?
+ )
+ }
+
+ spokenLanguagesWithOfficial("FRA") should be (Some(SpokenLanguagesWithOfficial("France",Some("French"),List("Arabic", "Italian", "Portuguese", "Spanish", "Turkish"))))
+
+ }
+
+ it should "Add some Magic[T]" in {
+
+ SQL("select * from Country").as(Country*).size should be (239)
+
+ Country.count().single() should be (239)
+ Country.count("population > 1000000").single() should be (154)
+ Country.find().list().size should be (239)
+ Country.find("population > 1000000").list().size should be (154)
+ Country.find("code = {c}").on("c" -> "FRA").first() should be (Some(Country(Id("FRA"), "France", 59225700, Some("Jacques Chirac"))))
+
+ Country.update(Country(Id("FRA"), "France", 59225700, Some("Nicolas S.")))
+
+ Country.find("code = {c}").on("c" -> "FRA").first() should be (Some(Country(Id("FRA"), "France", 59225700, Some("Nicolas S."))))
+
+ val Some(capital~country~languages) = SQL(
+ """
+ select * from Country c
+ join CountryLanguage l on l.CountryCode = c.Code
+ join City v on v.id = c.capital
+ where c.code = {code}
+ """
+ ).on("code" -> "FRA").as( (City ~< Country).span(CountryLanguage*) ? )
+
+ capital should be (City(Id(2974), "Paris"))
+ country should be (Country(Id("FRA"), "France", 59225700, Some("Nicolas S.")))
+ languages should be (List(CountryLanguage("Arabic","F"), CountryLanguage("French","T"), CountryLanguage("Italian","F"), CountryLanguage("Portuguese","F"), CountryLanguage("Spanish","F"), CountryLanguage("Turkish","F")))
+
+ val officialLanguage = languages.collect {
+ case CountryLanguage(lang, "T") => lang
+ }.headOption.getOrElse("No language?")
+
+ officialLanguage should be ("French")
+ }
+
+}
+
+import play.db.sql._
+import play.db.sql.SqlParser._
+
+object CountryLanguage extends Magic[CountryLanguage]
+object City extends Magic[City]
+object Country extends Magic[Country]
+
+case class Country(code:Id[String], name:String, population:Int, headOfState:Option[String])
+case class City(id:Pk[Int], name: String)
+case class CountryLanguage(language:String, isOfficial:String)
+
View
5,378 samples-and-tests/just-test-cases/test/world.sql
5,378 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
2 samples-and-tests/play-with-scala/test/Application.test.html
@@ -3,6 +3,6 @@
#{selenium}
// Open the home page, and check that no error occured
open('/')
- waitForPageToLoad(1000)
assertNotTitle('Application error')
+ assertTextPresent('Howdy, open the app/scrapbook.scala file, and start to Play!')
#{/selenium}
View
17 samples-and-tests/play-with-scala/test/ApplicationTest.java
@@ -1,17 +0,0 @@
-import org.junit.*;
-import play.test.*;
-import play.mvc.*;
-import play.mvc.Http.*;
-import models.*;
-
-public class ApplicationTest extends FunctionalTest {
-
- @Test
- public void testThatIndexPageWorks() {
- Response response = GET("/");
- assertIsOk(response);
- assertContentType("text/html", response);
- assertCharset("utf-8", response);
- }
-
-}
View
13 samples-and-tests/play-with-scala/test/BasicTest.java
@@ -1,13 +0,0 @@
-import org.junit.*;
-import java.util.*;
-import play.test.*;
-import models.*;
-
-public class BasicTest extends UnitTest {
-
- @Test
- public void aVeryImportantThingToTest() {
- assertEquals(2, 1 + 1);
- }
-
-}
View
7 samples-and-tests/play-with-scala/test/data.yml
@@ -1,7 +0,0 @@
-# you describe your data using the YAML notation here
-# and then load them using Fixtures.load("data.yml")
-
-# User(bob):
-# email: bob@gmail.com
-# password: secret
-# fullname: Bob
View
24 src/play/db/sql/Sql.scala
@@ -100,6 +100,16 @@ package sql {
}
})
}
+
+ implicit def rowToShort: Column[Short] = {
+ Column[Short](transformer = { (value, meta) =>
+ val MetaDataItem(qualified,nullable,clazz) = meta
+ value match {
+ case short:Short => Right(short)
+ case _ => Left(TypeDoesNotMatch("Cannot convert " + value + " to Short for column " + qualified))
+ }
+ })
+ }
implicit def rowToBoolean: Column[Boolean] = {
Column[Boolean](transformer = { (value, meta) =>
@@ -554,7 +564,10 @@ package sql {
val m:ClassManifest[T]
val tableName:Option[String] = None
- def clean(fieldName:String) = fieldName.split('$').last.toUpperCase()
+ def clean(scalaName:String) =
+ scalaName.split('$').reverse.find(
+ part => part.size > 0 && !part.matches("^[0-9]+$")
+ ).getOrElse(throw new RuntimeException("Unable to clean " + scalaName)).toUpperCase()
val typeName = clean(m.erasure.getSimpleName)
@@ -573,10 +586,11 @@ package sql {
val (cons,paramTypes,paramNames) = m.erasure
.getConstructors()
+ .filter( _.getGenericParameterTypes().length > 0 )
.sortBy(- _.getGenericParameterTypes().length)
.find(isConstructorSupported)
.map(c=>(c,c.getGenericParameterTypes().map(manifestFor),getParametersNames(c)))
- .getOrElse(throw new java.lang.Error("no supported constructors for type " +m))
+ .getOrElse(throw new java.lang.RuntimeException("no supported constructors for type " +m))
val coherent = paramTypes.length == paramNames.length
@@ -585,13 +599,15 @@ package sql {
)
if(!coherent && names_types.map(_._1).exists(_.contains("outer")))
- throw new java.lang.Error("It seems that your class uses a closure to an outer instance. For MagicParser, please use only top level classes.")
+ throw new java.lang.RuntimeException("It seems that your class uses a closure to an outer instance. For MagicParser, please use only top level classes.")
- if(!coherent) throw new java.lang.Error("not coherent to me!")
+ if(!coherent) throw new java.lang.RuntimeException("not coherent to me!")
val names_methods = handling(classOf[NoSuchMethodException])
.by(e =>throw new RuntimeException( "The elected constructor doesn't have corresponding methods for all its parameters. "+e.toString))
.apply(paramNames.map(name=>(clean(name),m.erasure.getDeclaredMethod(name))))
+
+ play.Logger.trace("Constructor " + cons + " elected for " + typeName)
(cons,names_types,names_methods)
}
View
32 src/play/db/sql/SqlStatementParser.scala
@@ -1,15 +1,29 @@
package play.db.sql
import scala.util.parsing.combinator._
+
object SqlStatementParser extends JavaTokenParsers{
- def parse (in:String):(String,List[String])= {
- val r=parse(instr,in.trim().replace("\n", " ")).get;
- (r.flatMap(_._1).mkString,(r.flatMap(_._2)))}
- def instr= rep( literal | variable | other)
- def literal= (stringLiteral | simpleQuotes) ^^ {case s => (s,None)}
- def variable="{"~>ident<~"}" ^^ {case s => ("?":String,Some(s))}
- def other=""".""".r ^^ {case element => (element,None)}
- def simpleQuotes=("'"+"""([^'\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*"""+"'").r
- override def skipWhitespace=false
+
+ def parse (in:String):(String,List[String]) = {
+ val r=parse(instr,in.trim().replace("\n", " ")).get
+ (r.flatMap(_._1).mkString,(r.flatMap(_._2)))
+ }
+
+ def instr = rep( literal | variable | other)
+
+ def literal = (stringLiteral | simpleQuotes) ^^ {case s => (s,None)}
+
+ def variable = "{"~>ident<~"}" ^^ {
+ case s => ("?":String,Some(s))
+ }
+
+ def other = """.""".r ^^ {
+ case element => (element,None)
+ }
+
+ def simpleQuotes = ("'"+"""([^'\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*"""+"'").r
+
+ override def skipWhitespace = false
+
}
View
16 src/play/modules/crud.scala
@@ -12,14 +12,14 @@ trait CRUDWrapper[T] {
@Before def addType = play.utils.Java.invokeStatic("controllers.CRUD", "addType")
- def index = ActionProxy.deleguate("controllers.CRUD", "index")
- def list(page: Int, search: String, searchFields: String, orderBy: String, order: String) = ActionProxy.deleguate("controllers.CRUD", "list", page, search, searchFields, orderBy, order)
- def blank = ActionProxy.deleguate("controllers.CRUD", "blank")
- def save(id: String) = ActionProxy.deleguate("controllers.CRUD", "save", id)
- def create = ActionProxy.deleguate("controllers.CRUD", "create")
- def delete(id: String) = ActionProxy.deleguate("controllers.CRUD", "delete", id)
- def show(id: String) = ActionProxy.deleguate("controllers.CRUD", "show", id)
- def attachment(id: String, field: String) = ActionProxy.deleguate("controllers.CRUD", "attachment", id, field)
+ def index = ActionProxy.delegate("controllers.CRUD", "index")
+ def list(page: Int, search: String, searchFields: String, orderBy: String, order: String) = ActionProxy.delegate("controllers.CRUD", "list", page, search, searchFields, orderBy, order)
+ def blank = ActionProxy.delegate("controllers.CRUD", "blank")
+ def save(id: String) = ActionProxy.delegate("controllers.CRUD", "save", id)
+ def create = ActionProxy.delegate("controllers.CRUD", "create")
+ def delete(id: String) = ActionProxy.delegate("controllers.CRUD", "delete", id)
+ def show(id: String) = ActionProxy.delegate("controllers.CRUD", "show", id)
+ def attachment(id: String, field: String) = ActionProxy.delegate("controllers.CRUD", "attachment", id, field)
}
View
2 src/play/utils/Scala.scala
@@ -92,7 +92,7 @@ trait Scala {
e.left.map(m => {play.Logger.error(m.toString); m}).right.toOption
}
- def get = e.fold(e => throw new Error(e.toString), a=>a)
+ def get = e.fold(e => throw new RuntimeException(e.toString), a=>a)
}
object MayErr{
View
2 src/play/utils/Utils.scala
@@ -2,7 +2,7 @@ package play.utils
private[play] object ActionProxy {
- def deleguate(controller: String, action: String, args: Any*) {
+ def delegate(controller: String, action: String, args: Any*) {
val m = play.utils.Java.findActionMethod(action, play.Play.classloader.loadClass(controller))
try {
m.invoke(null, args.map(_.asInstanceOf[AnyRef]): _*)

0 comments on commit 5dff0ee

Please sign in to comment.
Something went wrong with that request. Please try again.