slick icon indicating copy to clipboard operation
slick copied to clipboard

Compiled Queries are a huge pain

Open schmitch opened this issue 9 years ago • 21 comments

Hello, currently I'm using slick and I try to compile all queries, however I only do this, since the queries are really slow if I won't compiled them, they take a HUGE hit into performance.

However Compiling a query is sometimes really hard and should be improved. Instead of writing just a simple function like that:

db.run(tables.filter(_.id === id).result)

I need to write the following:

db.run(compiledGet(id).result)
def getQuery(id: Rep[Long]) = tables.filter(_.id === id)
val compiledGet = Compiled(getQuery _)

This is really overly verbose. I think that should definitely improved.

Without any improvements it's way easier to just use sql, sqlu and tsql to write any query since they won't need to be compiled and run already really fast.

schmitch avatar Jun 05 '15 10:06 schmitch

what do you suggest as a less verbose way to express it?

cvogt avatar Jun 05 '15 18:06 cvogt

Currently I have no idea how that could be better, however I think maybe it's better to have always Compiled Queries, except the user explicitly wan't non compiled, that would be an option. I mean slick already had a huge gain by introducing sql, sqlu, tsql and Future responses. Now if it would be less painfull to have fast queries, it would be the greatest "ORM-like" Library.

I mean without compiled queries people get performance hits by more than 5-10ms, which is huge really huge. The tables.filter query is 5 ms slower than the corresponding sql"SELECT * FROM tables WHERE id = $id"; Even when you need to manually map the response to the correct case class. Which takes more lines to write.

schmitch avatar Jun 07 '15 08:06 schmitch

db.run(compiledGet(id).result)`
def getQuery(id: Rep[Long]) = tables.filter(_.id === id)
val compiledGet = Compiled(getQuery _)

You don't have to write it that way. You can just write

val compiledGet = Compiled { id: Rep[Long] => tables.filter(_.id === id) }

db.run(compiledGet(id).result)

That's just the way scala works. Compiled takes a function literal, it can be anonymous if you like.

nafg avatar Jun 07 '15 23:06 nafg

@schmitch Compiling by default would take away explicit control from the user when compilation happens and where the compilation result is stored. Right now, you are in control. You decide where you place the val, if you rely on Compiled's default lazy compilation or if you trigger it upfront. We consider that an advantage over compilation as a caching side-effect as ORMs may do it. I doubt the syntax can be any shorter than it is right now given this design choice. Closing this now, but feel free to reopen, if you have a good suggestion how.

cvogt avatar Jun 08 '15 03:06 cvogt

Not sure, can you show me how to do it the verbose way?

On Mon, Jun 8, 2015, 4:50 AM Schmitt Christian [email protected] wrote:

@nafg https://github.com/nafg and how would one compile a simple SELECT

  • FROM table; query? If I just try to write a Compiled { tables } it won't work.

— Reply to this email directly or view it on GitHub https://github.com/slick/slick/issues/1155#issuecomment-109914287.

nafg avatar Jun 08 '15 18:06 nafg

@cvogt how could I compile a query that takes no parameters??

schmitch avatar Jun 10 '15 21:06 schmitch

@szeiger?

cvogt avatar Jun 10 '15 21:06 cvogt

It should "just work" like any other query. Maybe a variance bug? I've fixed those for other corner cases. What's the compiler error?

On June 10, 2015 11:34:46 PM GMT+02:00, Jan Christopher Vogt [email protected] wrote:

@szeiger?


Reply to this email directly or view it on GitHub: https://github.com/slick/slick/issues/1155#issuecomment-110921535

Sent from my Android phone with K-9 Mail. Please excuse my brevity.

szeiger avatar Jun 11 '15 10:06 szeiger

Query:

    val compilde = Compiled {
      articles.sortBy(_.id.desc)
    }
    dbConfig.db.run(compilde.result)

Error:

type mismatch;
 found   : compilde.type (with underlying type slick.lifted.CompiledStreamingExecutable[slick.lifted.Query[de.envisia.article.daos.ArticleTable,de.envisia.article.daos.ArticleTable#TableElementType,Seq],Seq[de.envisia.article.daos.ArticleTable#TableElementType],de.envisia.article.daos.ArticleTable#TableElementType])
 required: ?{def result: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method streamableCompiledQueryActionExtensionMethods in trait API of type [RU, EU](c: slick.lifted.StreamableCompiled[_, RU, EU])de.envisia.all.PostgresDriver.StreamingQueryActionExtensionMethods[RU,EU]
 and method runnableStreamableCompiledQueryActionExtensionMethods in trait API of type [R, RU, EU, C[_]](c: slick.lifted.RunnableCompiled[de.envisia.all.PostgresDriver.api.Query[R,EU,C],RU])de.envisia.all.PostgresDriver.StreamingQueryActionExtensionMethods[RU,EU]
 are possible conversion functions from compilde.type to ?{def result: ?}

Or if i put the result directly at the Query:

    val compilde = Compiled {
      articles.sortBy(_.id.desc).result
    }
    dbConfig.db.run(compilde)

I Get:

Computation of type de.envisia.all.PostgresDriver.StreamingDriverAction[Seq[de.envisia.article.daos.ArticleTable#TableElementType],de.envisia.article.daos.ArticleTable#TableElementType,slick.dbio.Effect.Read] cannot be compiled (as type C)

schmitch avatar Jun 11 '15 11:06 schmitch

Also a Huge impact is a bigger Query, where the where part will be dynamically generated. There is no way to have such a query compiled, ok there is a way if you make individual queries, based on the Where part, but that would take way longer (and will contain duplicated code) than just writing a string interpolated query with a splicing literal, however that has another drawback, such as that all the parts in the splicing literals aren't sql injection safe.

That's the part where slick definitely needs some improvement.

schmitch avatar Jun 12 '15 07:06 schmitch

@schmitch Slick's plain SQL interpolation is injection safe. Caching dynamically composed queries can indeed be tricky. You can use the conditions used to determine what query to build as cache keys and e.g. use a mutable Map from key to compiled query as the cache.

cvogt avatar Jun 12 '15 15:06 cvogt

@cvogt i know that the plain SQL interpolation is injection safe, however that is not safe:

val whereList = (s"username = '$username'" :: s"custome_id = $id" List())
val where = whereList.mkstring(" AND ")

val query = sql"SELECT * FROM table #$where;"

I mean that wouldn't make that much sense, however it would make more sense if some builts the list dynamically while username is a Option[String] and it should only appended when the option is non empty. However username is not sql safe it will definitly print an error while giving a query as $username

and yes something like the Mutable Map is something that i do currently, however the query is still really good even on the non compiled parts, however this query has two joins and if we need to have more data we would need to use Elasticsearch or another NoSQL solution anyway.

However would still be nice if we could chain sql"" especially when sql"" would have a mkstring function. This is really nice to build queries. And codegen makes it really easy to have both, the Functional Query and the Plain Queries.

schmitch avatar Jun 12 '15 15:06 schmitch

composing plain SQL strings sounds like a cool feature. can you add a separate issue for that? Or even better, a PR that implements it. This is an open source library :).

cvogt avatar Jun 12 '15 16:06 cvogt

hm i will definitly make an issue, however currently my time is really really limited. But i will take a look into it.

schmitch avatar Jun 12 '15 16:06 schmitch

I've created a new Issue at #1161 however there is still a lot of work to do before even implementing a single line of code since this feature is more than just "compose sql strings"

schmitch avatar Jun 13 '15 10:06 schmitch

I have some idea on a better support of compiled queries:

@compiledQuery def get(id: Rep[Long]) = tables.filter(_.id === id).result

would be compiled as:

private val compiledGet = tables.filter(_.id === id).result

def get(id: Rep[Long]) = compiledGet(id)

For creating some service class, it would be useful to have a simple way to define get(id: Long) instead of get(id: Rep[Long]), maybe through a different annotation.

v6ak avatar Jul 24 '15 20:07 v6ak

You can pass a Long where a Rep[Long] is expected (if you have the implicits in scope)

On Fri, Jul 24, 2015, 4:26 PM Vít Šesták [email protected] wrote:

I have some idea on a better support of compiled queries:

@compiledQuery def get(id: Rep[Long]) = tables.filter(_.id === id).result

would be compiled as:

private val compiledGet = tables.filter(_.id === id).result

def get(id: Rep[Long]) = compiledGet(id)

For creating some service class, it would be useful to have a simple way to define get(id: Long) instead of get(id: Rep[Long]), maybe through a different annotation.

— Reply to this email directly or view it on GitHub https://github.com/slick/slick/issues/1155#issuecomment-124710800.

nafg avatar Jul 27 '15 03:07 nafg

That's true. However, this is not OK for some level of encapsulation. One might want to receive just Long and return just Future[ResultType]

v6ak avatar Jul 30 '15 16:07 v6ak

I haven't found any example on where to store the compiled query. At the moment i am using:

class NotificationDAOJDBC @Inject()(dbConfigProvider: DatabaseConfigProvider) {

  import dbConfig.profile.api._

  /*--------- QUERIES ---------*/
  private def queryCount() = Identification.table.length

  /*--------- COMPILED ---------*/
  val compiledCount = Compiled(queryCount())

  def count(): Future[Int] =
      db.run(compiledCount.result)
}

I think the compiled val gets every time evaulated which is not good. Any tips where to put the cached queries?

perotom avatar Jul 18 '17 18:07 perotom

You could use some scala magic and use this:

val findById: UUID => Future[Option[Entity]] = {
      val query = Compiled((id: Rep[UUID]) => tableQuery.filter(_.id === id))
      id: UUID => db.run(query(id).result.headOption)
}

Starofall avatar Jul 10 '18 11:07 Starofall

Hi I am having kind of the same issue performance issue in compiled query and I am not getting an answer what should I do. I have posed the question in stackoverflow. Please anyone guide me on how can I reduce the time.

https://stackoverflow.com/questions/67380865/big-time-diff-in-slick-plain-sql-query-and-plain-sql-query-with-dynamic-keys

Thanks

mahesh2492 avatar May 06 '21 12:05 mahesh2492