Very simple Slick query, get a single value - mysql

I'm trying to introduce slick into my code to replace some existing jdbc code.
First of all I'd like to use a scala worksheet to run a really simple query, I want to pass in an integer id, and get back a string uuid. This is the simplest method in the whole codebase.
As I understand I need to make a connection to the database, setup an action, and then run the action. I have the following code:
val db = Database.forURL("jdbc:mysql://mysql-dev.${URL}/${DB}?autoReconnect=true&characterEncoding=UTF-8",
driver = "com.mysql.jdbc.Driver", user = "${user}",password= "${pass}")
val getUUID = sql"""SELECT ${UUIDFIELD} from users u WHERE u.${IDFIELD} = ${Id}""".as[String]
val uuid:String = db.run(getUUID)
println(uuid)
I'm pretty sure I don't have the driver setup correctly in the Database.forURL call, but also the worksheet is complaining that the result of db.run is not a string. How do I get to the string UUID value?

The db.run method returns a Future[_] type. You should use Await for getting result from it.
val db = Database.forURL("jdbc:mysql://mysql-dev.${URL}/${DB}?autoReconnect=true&characterEncoding=UTF-8",
driver = "com.mysql.jdbc.Driver", user = "${user}",password= "${pass}")
val getUUID = sql"""SELECT ${UUIDFIELD} from users u WHERE u.${IDFIELD} = ${Id}""".as[String]
val uuidFuture:Future[String] = db.run(getUUID)
import scala.concurrent._
import scala.concurrent.duration._
val uuid:String = Await.result(uuidFuture, Duration.Inf)
println(uuid)

Related

How to get random row from sql database using Ktrom in Kotlin Project?

I'm trying to make a matchmaking system inside a Ktor project, I access the Users database using Ktorm, everything works fine for all functionalities (register, login, add victories, etc) but for the matchmaking I can retrive all Users that fulfill the conditions required (having the same number of victories as the challenger) but from that list of users I need one at random and I haven't been able to achieve that. I can only get the first one.
This is my code for this method always getting the same first user:
override fun getOpponentFor(challengerUser : User): User {
return db.from(UserEntity).select()
.where {
(UserEntity.name notEq challengerUser.name)
and (UserEntity.victories eq challengerUser.victories)
}
.map {
val id = it[UserEntity.id]!!.toLong()
val name = it[UserEntity.name]!!
val email = it[UserEntity.email]!!
val coin = it[UserEntity.coin]!!.toInt()
val victories = it[UserEntity.coin]!!.toInt()
User(id,name,email, coin, victories)
}
.first()
}
I've been searching and I would need something like:
override fun getOpponentFor(challengerUser : User): User {
val randExpression = OrderByExpression((0..1).random().asExpression(), OrderType.ASCENDING)
return db.from(UserEntity).select()
.where {
(UserEntity.name notEq challengerUser.name)
and (UserEntity.victories eq challengerUser.victories)
}
.orderBy(randExpression)
.map{
val id = it[UserEntity.id]!!.toLong()
val name = it[UserEntity.name]!!
val email = it[UserEntity.email]!!
val coin = it[UserEntity.coin]!!.toInt()
val victories = it[UserEntity.coin]!!.toInt()
User(id,name,email, coin, victories)
}
.first()
}
but the "asExpression()" method called in the declaration of randExpression shows a compile time error. That code I just got from the internet so any fix to that code or any new solution would be welcome.

Slick flatMap does not execute all queries as a group

I fount that Scala Slick can not execute multiple queries as plain sql.
For example:
val query = sql"""
SET #referenceTime = '12:00:00';
SELECT * FROM table WHERE dateTime <= #referenceTime;
""".as[ClassResult]
dbConfig.db.run(query)
In this string are 2 queries, and Slick return an error as :
You have an error in your SQL syntax; check the manual .... to use near 'SELECT * FROM
From this, I understand that all queries before "SELECT" (maybe the last query) are ignored. So, I found a solution using flatMap, but is not the perfect.
val timeQuery = sql"""SET #referenceTime = '12:00:00';""".as[String]
val dataQuery = sql"""SELECT * FROM table WHERE dateTime <= #referenceTime;""".as[ClassResult]
val composedAction = for {
timeRes <- timeQuery,
dataRes <- dataQuery
} yield dataRes
dbConfig.db.run(composedAction)
This run and in 99% of cases return the result (a list of items). But, sometimes the list is empty, even if I'm sure that must return some data). So, I think that composedAction doesn't wait and execute both queries as a group every time. How I can do this, because I need in second query the result of the first (is used as a parameter in second)
edit:
another solution is to wait for result of the first query and use it as parameter in second. But it is a good solution/practice? Using sync code.
val timeQuery = sql"""SELECT '12:00:00';""".as[String]
var defaultTime: String = ""
val tempResult = dbConfig.db.run(timeParameterQuery.head).map(res => defaultTime = res)
Await.result(tempResult, Duration.Inf)
val dataQuery = sql"""SELECT * FROM table WHERE dateTime <= $defaultTime;""".as[ClassResult]
dbConfig.db.run(dataQuery)
I guess using SQL variables in slick is problematic, could be multiple db contexts. You can chain the queries with the 'map' rather than awaiting the result. Try something like this (untested)
val timeQuery = sql"""SELECT '12:00:00';""".as[String]
dbConfig.db.run(timeQuery.head).flatMap{res =>
dbConfig.db.run(sql"""SELECT * FROM table WHERE dateTime <= $res;""".as[ClassResult])
}

Connecting to an AWS MySQL Database from Scala with Slick

i just created my first AWS MySQL Database and want to connect to that from my scala application using Slick.
My config file shows:
awsMySQL = {
profile = "slick.jdbc.MySQLProfile$"
dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
properties = {
url = "jdbc:mysql://<databaseName>.cn17tbad2awy.eu-central-1.rds.amazonaws.com"
user = "foo"
password = "bar"
driver = com.mysql.cj.jdbc.Driver
}
connectionPool = disabled
keepAliveConnection = true
}
I just define a query to receive all my customers, but when exeuting this code i receive a SQLException: No database selected.
val db = Database.forConfig("awsMySQL")
val CustomersDAO = TableQuery[Customers]
val q1 = for (c <- CustomersDAO) yield c.name
val a = q1.result
val f = db.run(a)
Await.result(f, Duration.Inf)
I do not really understand this exception, because from my point of view by the url specifies the database. Could you please help me.
Thanks in advance.
I think you are pointing to the host where the MySQL service is running but not to the Database itself
Try to replace url = "jdbc:mysql://<databaseName>.cn17tbad2awy.eu-central-1.rds.amazonaws.com" with something like:
url = "jdbc:mysql://<databaseName>.cn17tbad2awy.eu-central-1.rds.amazonaws.com/DBSCHEMA"

Fetch the response from sql, store it in a object and use conditions?

I have two sql statements to be executed with a validity check. My need is that I execute the 1st query and store the response in one object and check the object is empty or not and execute the second query if it is not empty.
So, I have tried something like
In rolerepository.scala=>
override val allQuery = s"""
select UserRoles.* from
(select CASE rbac.roleTypeID
ELSE rbac.name JOIN dirNetworkInfo ni
ON UserRoles.PersonID = ni.PersonID
where ni.Loginname = {loginName}
and UserRoles.roleName in ( 'Business User ','Administrator')"""
(This is just some sample of the query - it is not fully written here.)
Then I map it to an object with model class written outside
override def map2Object(implicit map: Map[String, Any]):
HierarchyEntryBillingRoleCheck = {
HierarchyEntryBillingRoleCheck(str("roleName"), oint("PersonID")) }
Then I have written the getall method to execute the query
override def getAll(implicit loginName: String):
Future[Seq[HierarchyEntryBillingRoleCheck]] = {
doQueryIgnoreRowErrors(allQuery, "loginName" -> loginName) }
Then I have written the method to check whether the response from the 1st sql is empty or not. This is were I'm stuck and not able to proceed further.
def method1()= {
val getallresponse = HierarchyEntryBillingRoleCheck
getallresponse.toString
if (getallresponse != " ")
billingMonthCheckRepository.getrepo()
}
I am getting an error (type mismatch) in last closing brace and I don't know what other logic can be used here.
Can any one of you please explain and give me some solution for this?
And i also tried to use for loop in controller but not getting how to do that.
i tried ->
def getAll(implicit queryParams: QueryParams,
billingMonthmodel:Seq[HierarchyEntryBillingRoleCheck]):
Action[AnyContent] = securityService.authenticate() { implicit request
=> withErrorRecovery { req =>
toJson {
repository.getAll(request.user.loginName)
for {
rolenamecheck <- billingMonthmodel
}yield rolenamecheck
}}}}
You don't say which db access method you are using. (I'm assuming anorm). One way of approaching this is:
Create a case class matching your table
Create a parser matching your case class
use Option (or Either) to return a row for a specific set of parameters
For example, perhaps you have:
case class UserRole (id:Int, loginName:String, roleName:String)
And then
object UserRole {
val sqlFields = "ur.id, ur.loginName, ur.roleName"
val userRoleParser = {
get[Int]("id") ~
get[String]("loginName") ~
get[String]("roleName") map {
case id ~ loginName ~ roleName => {
UserRole(id, loginName, roleName)
}
}
}
...
The parser maps the row to your case class. The next step is creating either single row methods like findById or findByLoginName and multi-row methods, perhaps allForRoleName or other generic filter methods. In your case there might (assuming a single role per loginName) be something like:
def findByLoginName(loginName:String):Option[UserRole) = DB.withConnection { implicit c =>
SQL(s"select $sqlFields from userRoles ur ...")
.on('loginName -> loginName)
.as(userRoleParser.singleOpt)
}
The .as(parser... is key. Typically, you'll need at least:
as(parser.singleOpt) which returns an Option of your case class
as(parser *) which returns a List of your case class (you'll need this if multiple roles could exist for a login
as(scalar[Long].singleOpt) which returns an Option[Long] and which is handy for returning counts or exists values
Then, to eventually return to your question a little more directly, you can call your find method, and if it returns something, continue with the second method call, perhaps like this:
val userRole = findByLoginName(loginName)
if (userRole.isDefined)
billingMonthCheckRepository.getrepo()
or, a little more idiomatically
findByLoginName(loginName).map { userRole =>
billingMonthCheckRepository.getrepo()
...
I've shown the find method returning an Option, but in reality we find it more useful to return an Either[String,(your case class)], and then the string contains the reason for failure. Either is cool.
On my version of play (2.3.x), the imports for the above are:
import play.api.db._
import play.api.Play.current
import anorm._
import anorm.SqlParser._
You're going to be doing this sort of thing a lot so worth finding a set of patterns that works for you.
WOW I don't know what's happening with the formatting here, I am really attempting to use the code formatter on the toolbar but I don't know why it won't format it, even when pressed multiple times. I invite the community to edit my code formatting because I can't figure it out. Apologies to OP.
Because I find Play's documentation to be very tough to trudge through if you're unfamiliar with it, I won't just leave a link to it only.
You have to inject an instance of your database into your controller. This will then give it to you as a global variable:
#Singleton
class LoginRegController #Inject()(**myDB: Database**, cc: ControllerComponents ) {
// do stuff
}
But, it's bad practice to actually use this connection within the controller, because the JDBC is a blocking operation, so you need to create a Model which takes the db as a parameter to a method. Don't set the constructor of the object to take the DB and store it as a field. For some reason this creates connection leaks and the connections won't release when they are done with your query. Not sure why, but that's how it is.
Create a Model object that you will use to execute your query. Instead of passing the DB through the object's constructor, pass it through the method you will create:
object DBChecker {
def attemptLogin(db:Database, password:String): String = {
}}
In your method, use the method .withConnection { conn => to access your JDBC connection. So, something like this:
object DBChecker {
def attemptLogin(db:Database, password:String):String = {
var username: String = ""
db.withConnection{ conn =>
val query:String = s"SELECT uploaded_by, date_added FROM tableName where PASSWORD = $password ;"
val stmt = conn.createStatement()
val qryResult:ResultSet = stmt.executeQuery(query)
// then iterate over your ResultSet to get the results from the query
if (qryResult.next()) {
userName = qryResult.getString("uploaded_by")
}
}
}
return username
}
// but note, please look into the use of PreparedStatement objects, doing it this way leaves you vulnerable to SQL injection.
In your Controller, as long as you import the object, you can then call that object's methods from your controller you made in Step 1.
import com.path.to.object.DBChecker
#Singleton
class LoginRegController #Inject()(myDB: Database, cc: ControllerComponents ) { def attemptLogin(pass:String) = Action {
implicit request: Request[AnyContent] => {
val result:String = DbChecker.attemptLogin(pass)
// do your work with the results here
}

How to call Stored Procedures and defined functions in MySQL with Slick 3.0

I have defined in my db something like this
CREATE FUNCTION fun_totalInvestorsFor(issuer varchar(30)) RETURNS INT
NOT DETERMINISTIC
BEGIN
RETURN (SELECT COUNT(DISTINCT LOYAL3_SHARED_HOLDER_ID)
FROM stocks_x_hldr
WHERE STOCK_TICKER_SIMBOL = issuer AND
QUANT_PURCHASES > QUANT_SALES);
END;
Now I have received an answer from Stefan Zeiger (Slick lead) redirecting me here: User defined functions in Slick
I have tried (having the following object in scope):
lazy val db = Database.forURL("jdbc:mysql://localhost:3306/mydb",
driver = "com.mysql.jdbc.Driver", user = "dev", password = "root")
val totalInvestorsFor = SimpleFunction.unary[String, Int]("fun_totalInvestorsFor")
totalInvestorsFor("APPLE") should be (23)
Result: Rep(slick.lifted.SimpleFunction$$anon$2#13fd2ccd fun_totalInvestorsFor, false) was not equal to 23
I have also tried while having an application.conf in src/main/resources like this:
tsql = {
driver = "slick.driver.MySQLDriver$"
db {
connectionPool = disabled
driver = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost/mydb"
}
}
Then in my code with #StaticDatabaseConfig("file:src/main/resources/application.conf#tsql")
tsql"select fun_totalInvestorsFor('APPLE')" should be (23)
Result: Error:(24, 9) Cannot load #StaticDatabaseConfig("file:src/main/resources/application.conf#tsql"): No configuration setting found for key 'tsql'
tsql"select fun_totalInvestorsFor('APPLE')" should be (23)
^
I am also planning to call stored procedures that return one tuple of three values, via sql"call myProc(v1).as[(Int, Int, Int)]
Any ideas?
EDIT: When making
sql""""SELECT COUNT(DISTINCT LOYAL3_SHARED_HOLDER_ID)
FROM stocks_x_hldr
WHERE STOCK_TICKER_SIMBOL = issuer AND
QUANT_PURCHASES > QUANT_SALES""".as[(Int)]
results in SqlStreamingAction[Vector[Int], Int, Effect] instead of the suggested DBIO[Int] (from what I infer) suggested by the documentation
I've been running into exactly the same problem for the past week. After some extensive research (see my post here, I'll be adding a complete description of what I've done as a solution), I decided it can't be done in Slick... not strictly speaking.
But, I'm resistant to adding pure JDBC or Anorm into our solution stack, so I did find an "acceptable" fix, IMHO.
The solution is to get the session object from Slick, and then use common JDBC to manage the stored function / stored procedure calls. At that point you can use any third party library that makes it easier... although in my case I wrote my own function to set up the call and return a result set.
val db = Database.forDataSource(DB.getDataSource)
var response: Option[GPInviteResponse] = None
db.withSession {
implicit session => {
// Set up your call here... (See my other post for a more detailed
// answer with an example:
// procedure is eg., "{?=call myfunction(?,?,?,?)}"
val cs = session.conn.prepareCall(procedure.toString)
// Set up your in and out parameters here
// eg. cs.setLong(index, value)
val result = cs.execute()
val rc = result.head.asInstanceOf[Int]
response = rc match {
// Package up the response to the caller
}
}
}
db.close()
I know that's pretty terse, but as I said, see the other thread for a more complete posting. I'm putting it together right now and will post the answer shortly.