Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue#4 Adding .singleOption #34

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion scalasql/core/src/DbApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,26 @@ object DbApi {
}
}
}


def runOption[Q, R](query: Q, fetchSize: Int = -1, queryTimeoutSeconds: Int = -1)(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this on DbApi. .single is implemented on Select and doesn't need any dedicated methods on DbApi, so I would expect singleOption would be similar

implicit qr: Queryable[Q, Option[R]],
fileName: sourcecode.FileName,
lineNum: sourcecode.Line
): Option[R] = {
assert(qr.isSingleRowOption(query))
val res = stream(query, fetchSize, queryTimeoutSeconds)(
qr.asInstanceOf[Queryable[Q, Seq[?]]],
fileName,
lineNum
)
val results = res.take(2).toVector
if (results.size == 0) None
else if (results.size == 1) Some(results.head.asInstanceOf[R])
else {
throw new AssertionError(s"Single row query must return 1 result, not ${results.size}")
}
}

def stream[Q, R](query: Q, fetchSize: Int = -1, queryTimeoutSeconds: Int = -1)(
implicit qr: Queryable[Q, Seq[R]],
fileName: sourcecode.FileName,
Expand Down
10 changes: 10 additions & 0 deletions scalasql/core/src/Queryable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ trait Queryable[-Q, R] {
*/
def isSingleRow(q: Q): Boolean

/**
* Whether this query expects a single row to be returned, if so we can assert on
* the number of rows and raise an error if 2+ rows are present. Returns
* None if 0 rows are present.
* @param q
* @return
*/
def isSingleRowOption(q: Q): Boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if instead of adding another method isSingleRowOption, we should change the approach towards a method named cardinality (or something like that), that returns an enum value among: singleOptional, single or many.

I think the danger with the current approach is that we end up with two somewhat mutually exclusive cases: isSingleRow and isSingleRowOption ... what if they are both set to return true?


/**
* Converts the given queryable value into a [[SqlStr]], that can then be executed
* by the underlying SQL JDBC interface
Expand Down Expand Up @@ -91,6 +100,7 @@ object Queryable {
def isGetGeneratedKeys(q: Q): Option[Queryable.Row[?, ?]] = None
def isExecuteUpdate(q: Q): Boolean = false
def isSingleRow(q: Q): Boolean = true
def isSingleRowOption(q: Q): Boolean = true
def walkLabels(): Seq[List[String]]
def walkLabels(q: Q): Seq[List[String]] = walkLabels()

Expand Down
2 changes: 2 additions & 0 deletions scalasql/query/src/GetGeneratedKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import scalasql.core.{Context, Expr, Queryable, SqlStr, WithSqlExpr}
*/
trait GetGeneratedKeys[Q, R] extends Query[Seq[R]] {
def single: Query.Single[R] = new Query.Single(this)
def singleOption: Query.SingleOption[R] = new Query.SingleOption(this)
}

object GetGeneratedKeys {
Expand All @@ -24,6 +25,7 @@ object GetGeneratedKeys {
protected def queryWalkLabels(): Seq[List[String]] = Nil
protected def queryWalkExprs(): Seq[Expr[?]] = Nil
protected override def queryIsSingleRow = false
protected override def queryIsSingleRowOption = false
protected override def queryIsExecuteUpdate = true

override private[scalasql] def renderSql(ctx: Context): SqlStr = Renderable.renderSql(base)(ctx)
Expand Down
19 changes: 19 additions & 0 deletions scalasql/query/src/Query.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ trait Query[R] extends Renderable {
protected def queryWalkLabels(): Seq[List[String]]
protected def queryWalkExprs(): Seq[Expr[?]]
protected def queryIsSingleRow: Boolean
protected def queryIsSingleRowOption: Boolean
protected def queryGetGeneratedKeys: Option[Queryable.Row[?, ?]] = None
protected def queryIsExecuteUpdate: Boolean = false

Expand All @@ -26,6 +27,7 @@ object Query {
protected def queryWalkLabels(): Seq[List[String]] = Nil
protected def queryWalkExprs(): Seq[Expr[?]] = Nil
protected override def queryIsSingleRow = true
protected override def queryIsSingleRowOption = true
protected override def queryIsExecuteUpdate = true
}

Expand All @@ -38,6 +40,7 @@ object Query {
protected def queryWalkLabels() = query.queryWalkLabels()
protected def queryWalkExprs() = query.queryWalkExprs()
protected override def queryIsSingleRow = query.queryIsSingleRow
protected override def queryIsSingleRowOption = query.queryIsSingleRowOption
protected override def queryIsExecuteUpdate = query.queryIsExecuteUpdate
}

Expand All @@ -49,6 +52,7 @@ object Query {
protected def queryWalkLabels() = qr.walkLabels(expr)
protected def queryWalkExprs() = qr.walkExprs(expr)
protected override def queryIsSingleRow = qr.isSingleRow(expr)
protected override def queryIsSingleRowOption = qr.isSingleRowOption(expr)
protected override def queryIsExecuteUpdate = qr.isExecuteUpdate(expr)
}

Expand All @@ -57,6 +61,7 @@ object Query {
def walkLabels[R](q: Query[R]) = q.queryWalkLabels()
def walkSqlExprs[R](q: Query[R]) = q.queryWalkExprs()
def isSingleRow[R](q: Query[R]) = q.queryIsSingleRow
def isSingleRowOption[R](q: Query[R]) = q.queryIsSingleRowOption
def construct[R](q: Query[R], args: Queryable.ResultSetIterator) = q.queryConstruct(args)

/**
Expand All @@ -70,6 +75,7 @@ object Query {
override def walkLabels(q: Q) = q.queryWalkLabels()
override def walkExprs(q: Q) = q.queryWalkExprs()
override def isSingleRow(q: Q) = q.queryIsSingleRow
override def isSingleRowOption(q: Q) = q.queryIsSingleRowOption

def renderSql(q: Q, ctx: Context): SqlStr = q.renderSql(ctx)

Expand All @@ -86,4 +92,17 @@ object Query {
protected override def queryConstruct(args: Queryable.ResultSetIterator): R =
query.queryConstruct(args).asInstanceOf[R]
}

/**
* A [[Query]] that wraps another [[Query]] but sets [[queryIsSingleRow]] to `true`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should state: sets [[queryIsSingleRowOption]] to true

*/
class SingleOption[R](protected val query: Query[Seq[R]]) extends Query.DelegateQuery[R]
{
protected override def queryIsSingleRowOption: Boolean = true

private[scalasql] def renderSql(ctx: Context): SqlStr = Renderable.renderSql(query)(ctx)

protected override def queryConstruct(args: Queryable.ResultSetIterator): R =
query.queryConstruct(args).asInstanceOf[R]
}
}
Loading