Skip to content

Commit

Permalink
Interpolate 'inline val' as a literal string in query
Browse files Browse the repository at this point in the history
  • Loading branch information
sake92 committed Jan 8, 2024
1 parent 56b5fda commit 25deb2d
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 38 deletions.
7 changes: 2 additions & 5 deletions squery/src/ba/sake/squery/query.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ package ba.sake.squery

import java.{sql => jsql}
import scala.util.Using

import net.sf.jsqlparser.parser.CCJSqlParserUtil
import net.sf.jsqlparser.statement.select.Select
import net.sf.jsqlparser.statement.update.Update
import net.sf.jsqlparser.statement.delete.Delete
import net.sf.jsqlparser.JSQLParserException

import com.typesafe.scalalogging.Logger

import ba.sake.squery.write.SqlArgument
import ba.sake.squery.DynamicArg

case class Query(
private[squery] val sqlString: String,
private[squery] val arguments: Seq[SqlArgument[?]]
private[squery] val arguments: Seq[DynamicArg[?]]
) {

private val logger = Logger(getClass.getName)
Expand Down
47 changes: 39 additions & 8 deletions squery/src/ba/sake/squery/sql.scala
Original file line number Diff line number Diff line change
@@ -1,27 +1,58 @@
package ba.sake.squery

import ba.sake.squery.write.SqlArgument
import scala.compiletime.*
import scala.compiletime.ops.any.*
import scala.collection.mutable.ListBuffer
import ba.sake.squery.write.*
import ba.sake.squery.Query

// arg can be a simple value
// or another query
type SqlInterpolatorArg = SqlArgument[?] | Query
// TODO make derived SqlArgument[CaseClass]
// and insert (?, ?) automatically ??
// or autogenerate it with scalafix, easier

case class LiteralString(value: String)

case class DynamicArg[T](value: T)(using val sqlWrite: SqlWrite[T])

type LiteralOrDynamicString = LiteralString | DynamicArg[String]

// strings are treated specially:
// - literal-type strings are just passed through
// - dynamic strings are interpolated with ?, of course
given string2LiteralString[T <: Singleton & String]: Conversion[T, LiteralOrDynamicString] with
transparent inline def apply(value: T): LiteralOrDynamicString =
inline constValue[IsConst[T]] match
case true => LiteralString(value)
case false => DynamicArg(value)(using SqlWrite[String])

given sqlWrite2DynamicArg[T: SqlWrite]: Conversion[T, DynamicArg[T]] with
def apply(value: T): DynamicArg[T] =
DynamicArg(value)

/*
arg can be:
- a literal string https://scala-slick.org/doc/3.2.0/sql.html#splicing-literal-values
- a simple value
- or another query
*/
type SqlInterpolatorArgOrQuery = LiteralString | DynamicArg[?] | Query

/** Implementation of `sql""` interpolator. For a query sql"SELECT .. WHERE $a > 5 AND b = 'abc' ", there have to be
* `SqlWrite` typeclass instances for types of $a and $b.
*/
extension (sc: StringContext) {

// TODO implement as a macro, so we get a statically known string literal... !?? ZOMG :OO
def sql(args: SqlInterpolatorArg*): Query =
inline def sql(args: SqlInterpolatorArgOrQuery*): Query =
val stringPartsIter = sc.parts.iterator
val argsIter = args.iterator
var sb = StringBuilder(stringPartsIter.next())
val allArgs = ListBuffer.empty[SqlArgument[?]]
val allArgs = ListBuffer.empty[DynamicArg[?]]

while stringPartsIter.hasNext do {
argsIter.next() match
case sqlArg: SqlArgument[?] =>
case literalString: LiteralString =>
sb.append(literalString.value)
case sqlArg: DynamicArg[?] =>
sb.append("?")
allArgs += sqlArg
case nestedQuery: Query =>
Expand Down
14 changes: 0 additions & 14 deletions squery/src/ba/sake/squery/write/SqlArgument.scala

This file was deleted.

1 change: 0 additions & 1 deletion squery/test/src/ba/sake/squery/PostgresSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import java.time.Instant
import java.time.temporal.ChronoUnit
import scala.collection.decorators._
import org.testcontainers.containers.PostgreSQLContainer
import ba.sake.squery.write.SqlArgument

class PostgresSuite extends munit.FunSuite {

Expand Down
42 changes: 32 additions & 10 deletions squery/test/src/ba/sake/squery/SquerySuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,34 @@ import java.util.UUID
import java.time.Instant
import java.time.temporal.ChronoUnit
import org.testcontainers.containers.PostgreSQLContainer
import ba.sake.squery.write.SqlArgument
import ba.sake.squery.DynamicArg

class SquerySuite extends munit.FunSuite {

test("Query concat") {
test("Interpolate literal/constant in query") {
inline val columns = "id, name"
val q = sql"""SELECT ${columns} FROM customers"""

val p1 = "a_customer"
val q1 = sql"""SELECT id FROM customers WHERE name = $p1"""
assertEquals(
q.sqlString,
"""SELECT id, name FROM customers"""
)
assertEquals(q.arguments, Seq())
}

test("Interpolate value in query") {
val p1 = "a_customer"
val p2 = "a_customer2"
val q2 = sql"""OR name = ${p2}"""
val q = sql"""SELECT id FROM customers WHERE name IN ($p1, $p2)"""

val q = q1 ++ q2
assertEquals(
q.sqlString,
"""SELECT id FROM customers WHERE name = ? OR name = ?"""
"""SELECT id FROM customers WHERE name IN (?, ?)"""
)
assertEquals(q.arguments, Seq(p1, p2).map(SqlArgument(_)))
assertEquals(q.arguments, Seq(p1, p2).map(DynamicArg.apply))
}

test("Query in query") {
test("Interpolate query in query") {
val likeArg = "%Bob%"
val queryWhere = sql"WHERE name ILIKE ${likeArg}"

Expand All @@ -35,7 +42,22 @@ class SquerySuite extends munit.FunSuite {
q.sqlString,
"""SELECT id FROM customers WHERE name ILIKE ? LIMIT ?"""
)
assertEquals(q.arguments, Seq(SqlArgument(likeArg), SqlArgument(limitArg)))
assertEquals(q.arguments, Seq(DynamicArg(likeArg), DynamicArg(limitArg)))
}

test("Query concat ++") {
val p1 = "a_customer"
val q1 = sql"""SELECT id FROM customers WHERE name = $p1"""

val p2 = "a_customer2"
val q2 = sql"""OR name = ${p2}"""

val q = q1 ++ q2
assertEquals(
q.sqlString,
"""SELECT id FROM customers WHERE name = ? OR name = ?"""
)
assertEquals(q.arguments, Seq(p1, p2).map(DynamicArg.apply))
}

test("DbAction") {
Expand Down

0 comments on commit 25deb2d

Please sign in to comment.