Browse Source

Update play-slick and move to play2-scala-slick

- Move to play2-scala directory and generate config
- Update to Play 2.3
- Update to Scala 2.11
- Update to Slick 2.1
- Update other libraries
- Refactor and simplify code so it can be shared by Anorm
Rich Dougherty 11 years ago
parent
commit
b1fe912bf2
26 changed files with 192 additions and 255 deletions
  1. 0 1
      .travis.yml
  2. 1 0
      frameworks/Java/play2-java/generate_config.py
  3. 0 27
      frameworks/Scala/play-slick/README.md
  4. 0 0
      frameworks/Scala/play-slick/__init__.py
  5. 0 94
      frameworks/Scala/play-slick/app/controllers/Application.scala
  6. 0 20
      frameworks/Scala/play-slick/app/utils/PredicatedAction.scala
  7. 0 26
      frameworks/Scala/play-slick/benchmark_config
  8. 0 3
      frameworks/Scala/play-slick/install.sh
  9. 0 20
      frameworks/Scala/play-slick/project/Build.scala
  10. 0 1
      frameworks/Scala/play-slick/project/build.properties
  11. 0 52
      frameworks/Scala/play-slick/setup.py
  12. 21 0
      frameworks/Scala/play2-scala/benchmark_config
  13. 0 0
      frameworks/Scala/play2-scala/play2-scala-slick/.gitignore
  14. 91 0
      frameworks/Scala/play2-scala/play2-scala-slick/app/controllers/Application.scala
  15. 1 1
      frameworks/Scala/play2-scala/play2-scala-slick/app/models/Fortune.scala
  16. 6 7
      frameworks/Scala/play2-scala/play2-scala-slick/app/models/World.scala
  17. 47 0
      frameworks/Scala/play2-scala/play2-scala-slick/app/utils/DbOperation.scala
  18. 0 0
      frameworks/Scala/play2-scala/play2-scala-slick/app/views/fortune.scala.html
  19. 0 0
      frameworks/Scala/play2-scala/play2-scala-slick/app/views/main.scala.html
  20. 11 0
      frameworks/Scala/play2-scala/play2-scala-slick/build.sbt
  21. 3 0
      frameworks/Scala/play2-scala/play2-scala-slick/conf/application.conf
  22. 3 2
      frameworks/Scala/play2-scala/play2-scala-slick/conf/routes
  23. 1 0
      frameworks/Scala/play2-scala/play2-scala-slick/project/build.properties
  24. 1 1
      frameworks/Scala/play2-scala/play2-scala-slick/project/plugins.sbt
  25. 0 0
      frameworks/Scala/play2-scala/play2-scala-slick/source_code
  26. 6 0
      frameworks/Scala/play2-scala/setup_scala_slick.py

+ 0 - 1
.travis.yml

@@ -144,7 +144,6 @@ env:
     - "TESTDIR=Scala/plain"
     - "TESTDIR=Scala/play-activate-mysql"
     - "TESTDIR=Scala/play-scala-mongodb"
-    - "TESTDIR=Scala/play-slick"
     - "TESTDIR=Scala/play2-scala"
     - "TESTDIR=Scala/scalatra"
     - "TESTDIR=Scala/scruffy"

+ 1 - 0
frameworks/Java/play2-java/generate_config.py

@@ -13,6 +13,7 @@ configurations = [
   ('Java',  'Ebean', ['Linux'],            ['db', 'query']),
   ('Scala', None,    ['Linux'],            ['json']),
   ('Scala', 'Anorm', ['Linux', 'Windows'], ['db', 'query', 'fortune', 'update']),
+  ('Scala', 'Slick', ['Linux'],            ['db', 'query', 'fortune', 'update']),
 ]
 
 # All play2 test applications must use the same URLs.

+ 0 - 27
frameworks/Scala/play-slick/README.md

@@ -1,27 +0,0 @@
-#Play Benchmarking Test
-
-This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
-
-### JSON Encoding Test
-
-* [JSON test source](app/controllers/Application.scala)
-
-### Data-Store/Database Mapping Test
-
-* [Database test controller](app/controllers/Application.scala)
-* [Database test model](app/models/World.scala)
-
-## Infrastructure Software Versions
-The tests were run with:
-
-* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
-* [Play 2.1.0](http://http://www.playframework.com/)
-
-## Test URLs
-### JSON Encoding Test
-
-http://localhost/json
-
-### Data-Store/Database Mapping Test
-
-http://localhost/db?queries=5

+ 0 - 0
frameworks/Scala/play-slick/__init__.py


+ 0 - 94
frameworks/Scala/play-slick/app/controllers/Application.scala

@@ -1,94 +0,0 @@
-package controllers
-
-import play.api.Play.current
-import play.api.mvc._
-import play.api.libs.json.Json
-import java.util.concurrent._
-import scala.concurrent._
-import models.{Worlds, World, Fortunes, Fortune, WorldsTableQuery, FortunesTableQuery}
-import utils._
-import scala.concurrent.Future
-
-import play.api.libs.concurrent.Execution.Implicits._
-import play.core.NamedThreadFactory
-
-object Application extends Controller {
-
-  private val MaxQueriesPerRequest = 20
-  private val TestDatabaseRows = 10000
-
-  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
-  private val maxConnections =
-    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
-  private val minConnections =
-    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
-
-  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
-    0L, TimeUnit.MILLISECONDS,
-    new LinkedBlockingQueue[Runnable](),
-    new NamedThreadFactory("dbEc"))
-  private val dbEc = ExecutionContext.fromExecutorService(tpe)
-
-  private val worldsTable = new WorldsTableQuery
-  private val fortunesTable = new FortunesTableQuery
-
-  // A predicate for checking our ability to service database requests is determined by ensuring that the request
-  // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
-  // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
-  // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
-  // adequate in order to throttle the acceptance of requests to the size of the pool.
-  def isDbAvailable: Boolean = (tpe.getQueue.size() < maxConnections * MaxQueriesPerRequest)
-
-  def db(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      val random = ThreadLocalRandom.current()
-
-      val _worlds = Future.sequence((for {
-        _ <- 1 to queries
-      } yield Future(worldsTable.findById(random.nextInt(TestDatabaseRows) + 1))(dbEc)
-        ).toList)
-
-      _worlds map {
-        w => Ok(Json.toJson(w))
-      }
-    }
-  }
-
-  def fortunes() = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      Future(fortunesTable.getAll())(dbEc).map { fs =>
-        val fortunes =  Fortune(0, "Additional fortune added at request time.") +: fs
-        Ok(views.html.fortune(fortunes))
-      }
-    }
-  }
-
-  def update(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      val random = ThreadLocalRandom.current()
-
-      val boundsCheckedQueries = queries match {
-        case q if q > 500 => 500
-        case q if q <   1 => 1
-        case _ => queries
-      }
-
-      val worlds = Future.sequence((for {
-        _ <- 1 to boundsCheckedQueries
-      } yield Future {
-          for {
-            world <- worldsTable.findById(random.nextInt(TestDatabaseRows) + 1) 
-          } yield {
-            val updatedWorld = world.copy(randomNumber = random.nextInt(TestDatabaseRows) + 1)
-            worldsTable.updateRandom(updatedWorld)
-            updatedWorld
-          }
-        }(dbEc)
-      ).toList)
-
-      worlds.map {
-        w => Ok(Json.toJson(w)).withHeaders("Server" -> "Netty")
-      }
-    }
-  }
-}

+ 0 - 20
frameworks/Scala/play-slick/app/utils/PredicatedAction.scala

@@ -1,20 +0,0 @@
-package utils
-
-import play.api.mvc._
-import scala.concurrent.{Promise, Future}
-
-/**
- * A predicated action is one where a condition must be satisfied in order to proceed with the request. If the
- * condition is not satisfied then a supplied status result is yielded.
- */
-class PredicatedActionBuilder {
-  def apply[A](p: => Boolean, failed: => SimpleResult)(action: Action[A]): Action[A] = new Action[A] {
-    def apply(request: Request[A]): Future[SimpleResult] = {
-      if (p) action(request) else Promise.successful(failed).future
-    }
-
-    lazy val parser = action.parser
-  }
-}
-
-object PredicatedAction extends PredicatedActionBuilder

+ 0 - 26
frameworks/Scala/play-slick/benchmark_config

@@ -1,26 +0,0 @@
-{
-  "framework": "play-slick",
-  "tests": [{
-    "default": {
-      "setup_file": "setup",
-      "db_url": "/db",
-      "query_url": "/db?queries=",
-      "fortune_url": "/fortunes",
-      "update_url": "/update?queries=",
-      "port": 9000,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "MySQL",
-      "framework": "play2",
-      "language": "Scala",
-      "orm": "Raw",
-      "platform": "Netty",
-      "webserver": "None",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "play-scala-slick",
-      "notes": "",
-      "versus": "netty"
-    }
-  }]
-}

+ 0 - 3
frameworks/Scala/play-slick/install.sh

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-fw_depends play2

+ 0 - 20
frameworks/Scala/play-slick/project/Build.scala

@@ -1,20 +0,0 @@
-import sbt._
-import Keys._
-import play.Project._
-
-object ApplicationBuild extends Build {
-
-  val appName         = "play-slick"
-  val appVersion      = "1.0-SNAPSHOT"
-
-  val appDependencies = Seq(
-    jdbc,
-    anorm,
-    "mysql" % "mysql-connector-java" % "5.1.22",
-    "com.typesafe.play" %% "play-slick" % "0.6.0.1"
-  )
-
-  val main = play.Project(appName, appVersion, appDependencies).settings(
-  )
-
-}

+ 0 - 1
frameworks/Scala/play-slick/project/build.properties

@@ -1 +0,0 @@
-sbt.version=0.13.0

+ 0 - 52
frameworks/Scala/play-slick/setup.py

@@ -1,52 +0,0 @@
-
-import subprocess
-import sys
-import setup_util
-import os
-from zipfile import ZipFile
-
-def start(args, logfile, errfile):
-  setup_util.replace_text("play-slick/conf/application.conf", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
-
-  subprocess.check_call("play clean dist", shell=True, cwd="play-slick", stderr=errfile, stdout=logfile)
-
-  if os.name == 'nt':
-    ZipFile("./play-slick/target/universal/play-slick-1.0-SNAPSHOT.zip").extractall("./play-slick/target/universal")
-    with open("./play-slick/target/universal/play-slick-1.0-SNAPSHOT/bin/play-slick.bat", "w+") as f:
-      f.write("java %1 -cp \"./lib/*;\" play.core.server.NettyServer .")
-    subprocess.Popen("play-slick.bat", shell=True, cwd="play-slick/target/universal/play-slick-1.0-SNAPSHOT/bin", stderr=errfile, stdout=logfile)
-  else:
-    subprocess.check_call("unzip play-slick-1.0-SNAPSHOT.zip", shell=True, cwd="play-slick/target/universal", stderr=errfile, stdout=logfile)
-    subprocess.check_call("chmod +x play-slick", shell=True, cwd="play-slick/target/universal/play-slick-1.0-SNAPSHOT/bin", stderr=errfile, stdout=logfile)
-    subprocess.Popen("./play-slick", shell=True, cwd="play-slick/target/universal/play-slick-1.0-SNAPSHOT/bin", stderr=errfile, stdout=logfile)
-
-  return 0
-def stop(logfile, errfile):
-  #if os.name == 'nt':
-  #  with open("./play-slick/target/universal/play-slick-1.0-SNAPSHOT/RUNNING_PID") as f:
-  #    pid = int(f.read())
-  #    os.kill(pid, 9)
-  #else:
-    #p = subprocess.Popen(['ps', 'ef'], stdout=subprocess.PIPE)
-    #out, err = p.communicate()
-    #for line in out.splitlines():
-    #  if 'NettyServer' in line:
-    #    pid = int(line.split(None, 2)[1])
-    #    os.kill(pid, 9)
-  with open("./play-slick/target/universal/play-slick-1.0-SNAPSHOT/RUNNING_PID") as f:
-    pid = int(f.read())
-    os.kill(pid, 15)
-
-  try:
-    #os.remove("play-slick/target/universal/play-slick-1.0-SNAPSHOT/RUNNING_PID")
-    os.remove("play-slick/target/universal/play-slick-1.0-SNAPSHOT/play-slick-1.0-SNAPSHOT/RUNNING_PID")
-  except OSError:
-    return 1
-
-  # Takes up so much disk space
-  if os.name == 'nt':
-    subprocess.check_call("del /f /s /q target", shell=True, cwd="play-slick", stderr=errfile, stdout=logfile)
-  else:
-    subprocess.check_call("rm -rf target", shell=True, cwd="play-slick", stderr=errfile, stdout=logfile)
-
-  return 0

+ 21 - 0
frameworks/Scala/play2-scala/benchmark_config

@@ -61,6 +61,27 @@
         "query_url": "/queries?queries=", 
         "fortune_url": "/fortunes", 
         "update_url": "/update?queries="
+      }, 
+      "scala-slick": {
+        "display_name": "play2-scala-slick", 
+        "setup_file": "setup_scala_slick", 
+        "framework": "play2", 
+        "language": "Scala", 
+        "orm": "Slick", 
+        "os": "Linux", 
+        "database": "MySQL", 
+        "approach": "Realistic", 
+        "classification": "Fullstack", 
+        "platform": "Netty", 
+        "webserver": "None", 
+        "database_os": "Linux", 
+        "notes": "", 
+        "versus": "netty", 
+        "port": "9000", 
+        "db_url": "/db", 
+        "query_url": "/queries?queries=", 
+        "fortune_url": "/fortunes", 
+        "update_url": "/update?queries="
       }
     }
   ]

+ 0 - 0
frameworks/Scala/play-slick/.gitignore → frameworks/Scala/play2-scala/play2-scala-slick/.gitignore


+ 91 - 0
frameworks/Scala/play2-scala/play2-scala-slick/app/controllers/Application.scala

@@ -0,0 +1,91 @@
+package controllers
+
+import java.util.concurrent._
+import play.api.Play.current
+import play.api.mvc._
+import play.api.libs.json.Json
+import scala.concurrent._
+import models.{Worlds, World, Fortunes, Fortune, WorldsTableQuery, FortunesTableQuery}
+import utils._
+import utils.DbOperation._
+import scala.concurrent.Future
+import scala.slick.jdbc.JdbcBackend.Session
+
+import play.api.libs.concurrent.Execution.Implicits._
+
+object Application extends Controller {
+
+  // Slick code
+
+  private val worldsTable = new WorldsTableQuery
+  private val fortunesTable = new FortunesTableQuery
+
+  def getRandomWorlds(n: Int): Future[Seq[World]] = asyncDbOp { implicit session =>
+    val random = ThreadLocalRandom.current()
+    for (_ <- 1 to n) yield {
+      val randomId = random.nextInt(TestDatabaseRows) + 1
+      worldsTable.findById(randomId)
+    }
+  }
+
+  def getFortunes(): Future[Seq[Fortune]] = asyncDbOp { implicit session =>
+    fortunesTable.getAll()
+  }
+
+  def updateWorlds(n: Int): Future[Seq[World]] = asyncDbOp { implicit session =>
+    val random = ThreadLocalRandom.current()
+    for (_ <- 1 to n) yield {
+      val randomId = random.nextInt(TestDatabaseRows) + 1
+      val world = worldsTable.findById(randomId)
+      val randomNumber = random.nextInt(10000) + 1
+      val updatedWorld = world.copy(randomNumber = randomNumber)
+      worldsTable.updateRandom(updatedWorld)
+      updatedWorld
+    }
+  }
+
+  // Common code between Anorm and Slick
+
+  protected val TestDatabaseRows = 10000
+
+  def db = Action.async {
+    getRandomWorlds(1).map { worlds =>
+      Ok(Json.toJson(worlds.head))
+    }
+  }
+
+  def queries(countString: String) = Action.async {
+    val n = parseCount(countString)
+    getRandomWorlds(n).map { worlds =>
+      Ok(Json.toJson(worlds))
+    }
+  }
+
+  def fortunes() = Action.async {
+    getFortunes().map { dbFortunes =>
+      val appendedFortunes =  Fortune(0, "Additional fortune added at request time.") :: (dbFortunes.to[List])
+      Ok(views.html.fortune(appendedFortunes))
+    }
+  }
+
+  def update(queries: String) = Action.async {
+    val n = parseCount(queries)
+    updateWorlds(n).map { worlds =>
+      Ok(Json.toJson(worlds))
+    }
+  }
+
+  private def parseCount(s: String): Int = {
+    try {
+      val parsed = java.lang.Integer.parseInt(s, 10)
+      parsed match {
+        case i if i < 1 => 1
+        case i if i > 500 => 500
+        case i => i
+      }
+    } catch {
+      case _: NumberFormatException => 1
+    }
+  }
+
+}

+ 1 - 1
frameworks/Scala/play-slick/app/models/Fortune.scala → frameworks/Scala/play2-scala/play2-scala-slick/app/models/Fortune.scala

@@ -12,7 +12,7 @@ class Fortunes(tag: Tag) extends Table[Fortune](tag, "Fortune") {
 }
 class FortunesTableQuery extends TableQuery(new Fortunes(_)){
   val byId = this.findBy(_.id)
-  val all = Compiled{this:Query[Fortunes,Fortune]}
+  val all = Compiled{this:Query[Fortunes,Fortune,Seq]}
   def getAll(): List[Fortune] = DB.withSession { implicit session =>
     all.list
   }

+ 6 - 7
frameworks/Scala/play-slick/app/models/World.scala → frameworks/Scala/play2-scala/play2-scala-slick/app/models/World.scala

@@ -14,15 +14,14 @@ class Worlds(tag: Tag) extends Table[World](tag, "World") {
 class WorldsTableQuery extends TableQuery(new Worlds(_)){
   val byId = this.findBy(_.id)
 
-  def findById(id: Int): Option[World] = DB.withSession { implicit session =>
-      byId(id).firstOption
+  def findById(id: Int)(implicit session: Session): World = {
+    byId(id).first
   }
 
-  val updateQuery = Compiled{ (id: Column[Int]) => this.where(_.id === id) }
-  def updateRandom(world: World) {
-    DB.withSession { implicit session: Session =>
-      updateQuery(world.id).update(world)
-    }
+  val updateQuery = Compiled{ (id: Column[Int]) => this.filter(_.id === id) }
+
+  def updateRandom(world: World)(implicit session: Session) {
+    updateQuery(world.id).update(world)
   }  
 }
 

+ 47 - 0
frameworks/Scala/play2-scala/play2-scala-slick/app/utils/DbOperation.scala

@@ -0,0 +1,47 @@
+package utils
+
+import java.util.concurrent._
+import play.api.db.slick.DB
+import play.api.Play.current
+import play.core.NamedThreadFactory
+import scala.concurrent._
+import scala.concurrent.Future
+import scala.slick.jdbc.JdbcBackend.Session
+
+object DbOperation {
+
+  // Common code between Anorm and Slick
+
+  private val maxDbOperations = current.configuration.underlying.getInt("max-db-ops")
+
+  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
+  private val maxConnections =
+    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
+  private val minConnections =
+    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
+
+  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
+    0L, TimeUnit.MILLISECONDS,
+    new LinkedBlockingQueue[Runnable](), // TODO: Could use ArrayBlockingQueue?
+    new NamedThreadFactory("dbEc"))
+  private val dbEc = ExecutionContext.fromExecutorService(tpe)
+
+  // Slick code
+
+  /**
+   * Run a DB operation in the DB context. Automatically
+   * provides a Session.
+   */
+  def asyncDbOp[T](op: Session => T): Future[T] = {
+    // If the thread-pool queue used by the database grows too large then our server
+    // is probably struggling, and we should start dropping requests. If we don't
+    // then we'll just slow everything down and it will fail anyway. Better to fail
+    // quickly rather than slowly. Set the max size of our queue something above the
+    // number of concurrent connections that we expect to be handling.
+    if (tpe.getQueue.size > maxDbOperations) sys.error(s"Aborted DB operation because queue is > $maxDbOperations")
+    Future {
+      DB.withSession { session => op(session) }
+    }(dbEc)
+  }
+
+}

+ 0 - 0
frameworks/Scala/play-slick/app/views/fortune.scala.html → frameworks/Scala/play2-scala/play2-scala-slick/app/views/fortune.scala.html


+ 0 - 0
frameworks/Scala/play-slick/app/views/main.scala.html → frameworks/Scala/play2-scala/play2-scala-slick/app/views/main.scala.html


+ 11 - 0
frameworks/Scala/play2-scala/play2-scala-slick/build.sbt

@@ -0,0 +1,11 @@
+name := "play2-scala-slick"
+
+version := "1.0-SNAPSHOT"
+
+scalaVersion := "2.11.2"
+
+lazy val root = (project in file(".")).enablePlugins(PlayScala)
+
+libraryDependencies ++= Seq(
+  "com.typesafe.play" %% "play-slick" % "0.8.0",
+  "mysql" % "mysql-connector-java" % "5.1.32")

+ 3 - 0
frameworks/Scala/play-slick/conf/application.conf → frameworks/Scala/play2-scala/play2-scala-slick/conf/application.conf

@@ -1,6 +1,9 @@
 # This is the main configuration file for the application.
 # ~~~~~
 
+# The max size of the queue for running db operations
+max-db-ops = 1024
+
 # Secret key
 # ~~~~~
 # The secret key is used to secure cryptographics functions.

+ 3 - 2
frameworks/Scala/play-slick/conf/routes → frameworks/Scala/play2-scala/play2-scala-slick/conf/routes

@@ -3,9 +3,10 @@
 # ~~~~
 
 # Home page
-GET     /db                             controllers.Application.db(queries: Int ?= 1)
+GET     /db                             controllers.Application.db
+GET     /queries                        controllers.Application.queries(queries ?= "1")
 GET     /fortunes                       controllers.Application.fortunes
-GET     /update                         controllers.Application.update(queries: Int ?= 1)
+GET     /update                         controllers.Application.update(queries ?= "1")
 
 # Map static resources from the /public folder to the /assets URL path
 GET     /assets/*file                   controllers.Assets.at(path="/public", file)

+ 1 - 0
frameworks/Scala/play2-scala/play2-scala-slick/project/build.properties

@@ -0,0 +1 @@
+sbt.version=0.13.5

+ 1 - 1
frameworks/Scala/play-slick/project/plugins.sbt → frameworks/Scala/play2-scala/play2-scala-slick/project/plugins.sbt

@@ -5,4 +5,4 @@ logLevel := Level.Warn
 resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
 
 // Use the Play sbt plugin for Play projects
-addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.2.0")
+addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.3")

+ 0 - 0
frameworks/Scala/play-slick/source_code → frameworks/Scala/play2-scala/play2-scala-slick/source_code


+ 6 - 0
frameworks/Scala/play2-scala/setup_scala_slick.py

@@ -0,0 +1,6 @@
+
+# This file was generated by frameworks/Java/play2-java/generate_config.py.
+# Do not edit this file directly, use the script to regenerate.
+from .setup_common import make_setup_for_dir
+
+make_setup_for_dir(globals(), 'play2-scala-slick')