Mysql Aliased tables in Anorm are not recognized - scala

Update
Created a runnable demo for this problem.
https://github.com/narayanjr/anorm_test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I am unable to access fields on an aliased table. I keep getting error messages saying the field is not an option and the available fields are the base field name, or the `table_name'.field_name. But not the aliased field name. This makes it impossible to JOIN the same table twice and access all the fields.
var vendor_client_parser_1 = SqlParser.long("vid") ~ SqlParser.str("vname") ~ SqlParser.long("cid") ~ SqlParser.str("cname") map
{
case vid ~ vn ~ cid ~ cn => println(vid + "," + vn + "," + cid + "," + cn + ",")
}
var vendor_client_parser_2 = SqlParser.long("v.business_id") ~ SqlParser.str("v.name") ~ SqlParser.long("c.business_id") ~ SqlParser.str("c.name") map
{
case vid ~ vn ~ cid ~ cn => println(vid + "," + vn + "," + cid + "," + cn + ",")
}
var vendor_client_parser_3 = SqlParser.long(1) ~ SqlParser.str(2) ~ SqlParser.long(3) ~ SqlParser.str(4) map
{
case vid ~ vn ~ cid ~ cn => println(vid + "," + vn + "," + cid + "," + cn + ",")
}
DB.withConnection
{
implicit c =>
var results =
SQL"""
SELECT v.business_id AS vid, v.name AS vname, c.business_id AS cid, c.name AS cname
FROM #$BUSINESS_CONNECTION_TABLE
JOIN #$BUSINESS_TABLE AS v ON (vendor_id = v.business_id)
JOIN #$BUSINESS_TABLE AS c ON (client_id = c.business_id)
LIMIT 20
""".as(vendor_client_parser.*)
}
Expected result:
1, Vendor A, 10, Vendor K
2, Vendor B, 11, Vendor L
2, Vendor B, 1, Vendor A
12, Vendor M, 3, Vendor C
Result from vendor_client_parser_1:
10, Vendor K, 10, Vendor K
11, Vendor L, 11, Vendor L
1, Vendor A, 1, Vendor A
3, Vendor C, 3, Vendor C
Result from vendor_client_parser_2:
Execution exception[[AnormException: 'v.business_id' not found, available columns: business.business_id, business_id, business.name, name, business.business_id, business_id, business.name, name]]
Result from vendor_client_parser_3: (Same as expected)
1, Vendor A, 10, Vendor K
2, Vendor B, 11, Vendor L
2, Vendor B, 1, Vendor A
12, Vendor M, 3, Vendor C
vendor_client_parser_3 works but it relies on using index instead of names. I dis like using indexes because If I mess up an index I might still get a valid response back and not notice. If I mess up a name the column wont exist and I will know something is wrong.
Is there something I am missing? Is there any way to achieve the results I need without having to rely on using the index?
Play Scala 2.4.1
Anorm 2.5.0
Update:
If I do not alias the columns and use vendor_client_parser_2 I get the same result as when the columns are alias.
Modified Query:
SQL"""
SELECT v.business_id, v.name, c.business_id, c.name
FROM #$BUSINESS_CONNECTION_TABLE
JOIN #$BUSINESS_TABLE AS v ON (vendor_id = v.business_id)
JOIN #$BUSINESS_TABLE AS c ON (client_id = c.business_id)
LIMIT 20
""".as(vendor_client_parser_2.*)
Result with vendor_client_parser_2.*:
Execution exception[[AnormException: 'v.business_id' not found, available columns: business.business_id, business_id, business.name, name, business.business_id, business_id, business.name, name]]
I also tested it with a single table aliased and it refuses to see the aliased table name
Single Table Test:
SQL"""
SELECT v.business_id, v.name, business_id, name
FROM #$BUSINESS_TABLE AS v
LIMIT 20
""".as(test_parser.*)
test_parser:
var test_parser = SqlParser.long("v.business_id") ~ SqlParser.str("v.name") ~ SqlParser.long("business_id") ~ SqlParser.str("name") map
{
case vid ~ vn ~ cid ~ cn => println(vid + "," + vn + "," + cid + "," + cn + ",")
}
Result:
[AnormException: 'v.business_id' not found, available columns: business.business_id, business_id, business.name, name, business.business_id, business_id, business.name, name]
I then tested if aliased columns are accessible by both their original and aliased names.
Test Aliased columns:
SQL"""
SELECT business_id AS vid, name AS vname
FROM #$BUSINESS_TABLE
LIMIT 20
""".as(test_parser_2.*)
test_parser_2:
var test_parser_2 = SqlParser.long("business_id") ~ SqlParser.str("name") ~ SqlParser.long("vid") ~ SqlParser.str("vname") map
{
case vid ~ vn ~ cid ~ cn => println(vid + "," + vn + "," + cid + "," + cn + ",")
}
This test did not error out and it properly pulled in values as business_id and vid. As well as name and vname.
I forced it to error out so it would give me a list of column names. And it does seem like Anorm doesn't offer the non-aliased names as suggestions but they do work in this case.
[AnormException: 'forceError' not found, available columns: business.business_id, vid, business.name, vname]
I also tried not using SqlParser.
var businesses = SQL"""
SELECT v.business_id AS vid, v.name AS vname, c.business_id AS cid, c.name AS cname
FROM #$BUSINESS_CONNECTION_TABLE
JOIN #$BUSINESS_TABLE AS v ON (vendor_id = v.business_id)
JOIN #$BUSINESS_TABLE AS c ON (client_id = c.business_id)
LIMIT 20
""".fold(List[(Long, String, Long, String)]())
{
(list, row) =>
list :+ (row[Long]("v.business_id"), row[String]("v.name"), row[Long]("c.business_id"), row[String]("c.name")) //attempt_1
//list :+ (row[Long]("vid"), row[String]("vname"), row[Long]("cid"), row[String]("cname")) //attempt_2
}
If I use attempt_1 I get this error which as you suggested shouldn't work.
Left('v.business_id' not found, available columns: business.business_id, vid, business.name, vname, business.business_id, cid, business.name, cname)))
If I use attempt_2, I get the same results as vendor_client_parser_1
10, Vendor K, 10, Vendor K
11, Vendor L, 11, Vendor L
1, Vendor A, 1, Vendor A
3, Vendor C, 3, Vendor C
If I do not alias the columns and use this same method
SQL"""
SELECT v.business_id, v.name, c.business_id, c.name
FROM #$BUSINESS_CONNECTION_TABLE
JOIN #$BUSINESS_TABLE AS v ON (vendor_id = v.business_id)
JOIN #$BUSINESS_TABLE AS c ON (client_id = c.business_id)
LIMIT 20
""".fold(List[(Long, String, Long, String)]())
{
(list, row) =>
list :+ (row[Long]("v.business_id"), row[String]("v.name"), row[Long]("c.business_id"), row[String]("c.name")) //Attempt_3
}
Using this query without aliasing the columns causes this error,
Left('v.business_id' not found, available columns: business.business_id, business_id, business.name, name, business.business_id, business_id, business.name, name)))
I then tested a simple aliased tabled using this method
SQL"""
SELECT v.business_id, v.name
FROM #$BUSINESS_TABLE AS v
LIMIT 20
""".fold(List[(Long, String)]())
{
(list, row) =>
list :+ (row[Long]("v.business_id"), row[String]("v.name")) //simple_attempt_1
}
I get the same error
Left(List(java.lang.RuntimeException: Left('v.business_id' not found, available columns: business.business_id, business_id, business.name, name)))
So as far as I can tell it isn't possible to access fields that are part of an aliased table if the same table is used twice using the field names instead of index.
Update 2:
I tried reversing the order of fields in the SQL so it was c.business_id AS cid, c.name AS cname, v.business_id AS vid, v.name AS vname and rerunning vendor_client_parser_1. It gave me the inverse results
Result from vendor_client_parser_1 with mysql fields switched:
1, Vendor A, 1, Vendor A
2, Vendor B, 2, Vendor B
2, Vendor B, 2, Vendor B
12, Vendor M, 12, Vendor M
When I force an error and it shows me possible fields I get these,
Fields in original order:
Left('forceError' not found, available columns: business.business_id, vid, business.name, vname, business.business_id, cid, business.name, cname)
Fields in switched order:
Left('forceError' not found, available columns: business.business_id, cid, business.name, cname, business.business_id, vid, business.name, vname)
This makes me think this scenario is happening.
In case several columns are found with same name in query result, for example columns named code in both Country and CountryLanguage tables, there can be ambiguity. By default a mapping like following one will use the last column:
https://www.playframework.com/documentation/2.4.1/ScalaAnorm
If you look at the suggested fields business.business_id and business.name occur twice because the table is referenced twice. It seems like Anorm is associating the last occurrence of business.business_id and business.name with both aliases.
Update
Created a runnable demo for this problem.
https://github.com/narayanjr/anorm_test

I have not tried it in 2.4, but you could possibly use a parser method to generate the column name for you, rather than a variable. My approach would be something like the following:
case class BusinessConnection(vendor_id: Int, client_id: Int)
case class Business(id: Int, name: String)
case class VendorClient(vendor: Business, client: Business)
object VendorClient {
val businessConnectionP =
get[Int]("vendor_id") ~
get[Int]("client_id") map {
case vendor_id ~ client_id =>
BusinessConnection(vendor_id, client_id)
}
def businessP(alias: String) =
getAliased[Int](alias + ".id") ~
getAliased[String](alias + ".name") map {
case id ~ name =>
Business(id, name)
}
val vendorClientP =
businessP("v") ~ businessP("c") map {
case business ~ client =>
VendorClient(business, client)
}
val sqlSelector = "v.id, v.name, c.id, c.name"
def all() = DB.withConnection { implicit c =>
SQL(s"""
select $sqlSelector
from businessConnection
join business as v on vendor_id=v.id
join business as c on client_id=c.id
""").as(vendorClientP *)
}
}

Related

Print all values from column using custom predicate

How can I print next values
'aa, bb, cc, dd, ee'
from DB column, like that:
'aa-bb, aa-cc, aa-dd, aa-ee, etc'?
NOTE: If I have, for example following value: 'aa-bb', so 'bb-aa' should be skipped out
My TSQL Example doesn't work:
select l.SURNAME + '-' + l1.SURNAME
from LECTURERS l cross join LECTURERS l1
where l.CITY = 'Kyiv' and l.SURNAME <> l1.SURNAME and LEFT(l1.SURNAME,
charindex('-', l1.SURNAME)) <> l.SURNAME
Prints me following 'aa-bb, aa-cc, aa-dd, aa-ee', but how can I except 'aa-bb' <> 'bb-aa'

Is it possible to pass flags in to regex_matches with PostgreSQL?

I'm trying to select multiple matches from a field, I have the query working as SQL but am at a loss when it comes to the jOOQ version. Here's the SQL:
SELECT
array_to_string(array(select array_to_string(regexp_matches(m.content, '#([a-zA-Z0-9]+)', 'g'), ' ')), ' ') hashtags
FROM tweets
But I can't work out how to pass in the 'g' flag to regexp_matches, and Postgres doesn't support g: style embedded flags. At the moment I'm using (in Scala):
val hashtags = DSL.field(
"array_to_string(array(select array_to_string(regexp_matches(tweets.content, '#([a-zA-Z0-9]+)', 'g'), ' ')), ' ')",
classOf[String])
but that seems kind of gross (but it works, so there's that! 😀).
Current Approach
I have an enum with fields associated with it, like this:
MentionUri(MENTIONS.URL),
Content(MENTIONS.CONTENT),
Hashtags(DSL.field(
"array(select array_to_string(regexp_matches(mentions.content, '#([a-zA-Z0-9]+)', 'g'), ' '))").as("hashtags")),
MediaLinks(DSL.field("json_agg(twitter_media)").as("media_links")),
Location(MENTIONS.LOCATION),
the 'g' flag is needed because I want to get all of the matching fragments from the target text (all of the hashtags from the mention content in this case, where mentions are Tweets, Facebook posts, &c.)
and a class with a bunch of optional search parameters, like this:
minFollowing: Option[Int] = None,
maxFollowing: Option[Int] = None,
workflowStates: Set[WorkflowState] = WorkflowState.values.toSet,
onlyVerifiedUsers: Boolean = false,
and then I build up the query based on a list of these fields
private val fields = v.fields.toSet
override def query(sql: DSLContext): Query = {
val fields = v.fields.map(_.field) ++ Seq(M.PROJECT_ID)
var query = (sql
select fields.toSet.asJava
from M
leftJoin MTM on (M.PROJECT_ID === MTM.PROJECT_ID) and (M.ID === MTM.MENTION_ID)
leftJoin TM on (MTM.URL === TM.URL)
where (M.PROJECT_ID in v.criteria.projectIds.asJava))
if (v.criteria.channels.size < numChannels)
query = query and (M.CHANNEL in v.criteria.channels.asJava)
if (v.criteria.mentionTypes.size < numMentionTypes)
query = query and (M.MENTION_TYPE in v.criteria.mentionTypes.asJava)
// ... more critera get added here, finally ...
if (v.max.isDefined)
query groupBy (M.PROJECT_ID, M.ID, M.CHANNEL, M.USERNAME) limit v.max.get
else
query groupBy (M.PROJECT_ID, M.ID, M.CHANNEL, M.USERNAME)

Possible Bug in JDBC?

I am currently facing a weird problem.
Whenever a user types something into the search bar that starts with an 's', the request crashes.
What you see next is a sample sql code generated by the search engine I programmed for this project.
SELECT Profiles.ProfileID,Profiles.Nickname,Profiles.Email,Profiles.Status,Profiles.Role,Profiles.Credits, Profiles.Language,Profiles.Created,Profiles.Modified,Profiles.Cover,Profiles.Prename, Profiles.Lastname,Profiles.BirthDate,Profiles.Country,Profiles.City,Profiles.Phone,Profiles.Website, Profiles.Description, Profiles.Affair,Scores.AvgScore, coalesce(Scores.NumScore, 0) AS NumScore, coalesce(Scores.NumScorer, 0) AS NumScorer, (
(SELECT count(*)
FROM Likes
JOIN Comments using(CommentID)
WHERE Comments.ProfileID = Profiles.ProfileID)) NumLikes, (
(SELECT count(*)
FROM Likes
JOIN Comments using(CommentID)
WHERE Comments.ProfileID = Profiles.ProfileID) /
(SELECT coalesce(nullif(count(*), 0), 1)
FROM Comments
WHERE Comments.ProfileID = Profiles.ProfileID)) AvgLikes, Movies.MovieID, Movies.Caption, Movies.Description, Movies.Language, Movies.Country, Movies.City, Movies.Kind, Movies.Integration,
(SELECT cast(least(25 + 5.000000 * round((75 * ((0.500000 * SIZE/1024.0/1024.0 * 0.001250) + (0.500000 * Duration/60.0 * 0.050000))) / 5.000000), 100) AS signed int)
FROM Streams
WHERE MovieID = Movies.MovieID
AND Tag = "main"
AND ENCODING = "mp4") AS ChargeMain,
(SELECT cast(least(25 + 10.000000 * round((75 * ((0.200000 * SIZE/1024.0/1024.0 * 0.001000) + (0.800000 * Duration/60.0 * 0.016667))) / 10.000000), 100) AS signed int)
FROM Streams
WHERE MovieID = Movies.MovieID
AND Tag = "notes"
AND ENCODING = "mp4") AS ChargeNotes,
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "main") AS MainViews,
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "notes") AS NotesViews,
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "trailer") AS TrailerViews,
(SELECT coalesce(greatest(
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "trailer"),
(SELECT coalesce(count(*), 0)
FROM Views
WHERE Views.MovieID = Movies.MovieID
AND Tag = "main")), 0)) AS MaxMainTrailerViews,
(SELECT avg(Score)
FROM Scores
WHERE Scores.MovieID = Movies.MovieID) AS Score,
(SELECT coalesce(group_concat(cast(Score AS signed int)), "")
FROM Scores
WHERE Scores.MovieID = Movies.MovieID) AS Scores, Movies.Cover, Movies.Locked, Movies.Created, Movies.Modified,
(SELECT coalesce(group_concat(name separator ','),"")
FROM Tags
JOIN TagLinks using(TagID)
WHERE TagLinks.MovieID = Movies.MovieID
ORDER BY name ASC) AS Tags,
(SELECT count(*)
FROM Purchases
WHERE MovieID = Movies.MovieID
AND ProfileID = %s
AND TYPE = "main") AS PurchasedMain,
(SELECT count(*)
FROM Purchases
WHERE MovieID = Movies.MovieID
AND ProfileID = %s
AND TYPE = "notes") AS PurchasedNotes,
(SELECT count(*)
FROM Watchlist
WHERE MovieID = Movies.MovieID
AND ProfileID = %s) AS Watchlist,
(SELECT count(*)
FROM Scores
WHERE MovieID = Movies.MovieID
AND ProfileID = %s) AS Rated,
(SELECT count(*)
FROM Comments
WHERE MovieID = Movies.MovieID
AND Deleted IS NULL) AS Comments,
(SELECT sum(Duration)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Tag IN ("main",
"notes")
AND Streams.ENCODING = "mp4") AS Runtime,
(SELECT cast(count(*) AS signed int)
FROM Movies
JOIN Profiles ON Profiles.ProfileID = Movies.ProfileID
WHERE ((Movies.Locked = 0
AND
(SELECT count(*)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Status <> "ready") = 0
AND Profiles.Status = "active")
OR (%s = 1)
OR (Movies.ProfileID = %s))
AS Movies,
(SELECT cast(ceil(count(*) / %s) AS signed int)
FROM Movies
JOIN Profiles using(ProfileID)
WHERE ((Movies.Locked = 0
AND
(SELECT count(*)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Status <> "ready") = 0
AND Profiles.Status = "active")
OR (%s = 1)
OR (Movies.ProfileID = %s))
AS Pages
FROM Movies
JOIN Profiles using(ProfileID)
LEFT JOIN
(SELECT Movies.ProfileID AS ProfileID,
avg(Scores.Score) AS AvgScore,
count(*) AS NumScore,
count(DISTINCT Scores.ProfileID) AS NumScorer
FROM Scores
JOIN Movies using(MovieID)
GROUP BY Movies.ProfileID) AS Scores using(ProfileID)
WHERE ((Movies.Locked = 0
AND
(SELECT count(*)
FROM Streams
WHERE Streams.MovieID = Movies.MovieID
AND Streams.Status <> "ready") = 0
AND Profiles.Status = "active")
OR (%s = 1)
OR (Movies.ProfileID = %s))
ORDER BY Score DESC LIMIT %s,
%s
After countless hours of investigating and comparing possible user inputs with the generated sql code I finally nailed the problem down to some really strange behaviour of the JDBC driver which I consider a serious bug - yet I am not sure:
I spent another few hours trying to reproduce the problem with as less sql code as possible and ended up with the following:
SQL("""select * from Movies where "s" like "%s" and MovieID = {a} """)
.on('a -> 1).as(scalar[Long]*)
[SQLException: Parameter index out of range (1 > number of parameters, which is 0).]
SQL("""select * from Movies where "s" like "%samuel" and MovieID = {a} """)
.on('a -> 1).as(scalar[Long]*)
[SQLException: Parameter index out of range (1 > number of parameters, which is 0).]
SQL("""select * from Movies where "s" like "%flower" and MovieID = {a} """)
.on('a -> 1).as(scalar[Long]*)
[OK]
SQL("""select * from Movies where "s" like "%samuel" and MovieID = 1 """)
.on('a -> 1).as(scalar[Long]*)
[OK]
SQL("""select * from Movies where "s" like "%s" and MovieID = "{a}" """)
.on('a -> 1).as(scalar[Long]*)
[OK]
SQL("""select * from Movies where MovieID = {a} and "s" like "%s" """)
.on('a -> 1).as(scalar[Long]*)
[OK]
I believe to see a pattern here:
Under the exact condition that there is a %s sequence (quoted or unquoted) anywhere in a sql code, followed by a non quoted named parameter with arbitrary name and arbitrary distance
to the %s sequence, jdbc (or anorm) crashes. The crash seems to occur in JDBC, however its also possible that Anorm submits invalid values to JDBC.
Do you guys have any suggestions?
I think I found an enduring solution for the problem meanwhile. Since my sql generator needs to stay very flexible I somehow need a way to pass along sql fragments with their corresponding parameters without evaluating them right away. Instead the generator must be able to assemble and compose various sql fragments into bigger fragments at any time - just as he does now - but now with the acompanying, not yet evaluated parameters. I came up with this prototype:
DB.withConnection("betterdating") { implicit connection =>
case class SqlFragment(Fragment: String, Args: NamedParameter*)
val aa = SqlFragment("select MovieID from Movies")
val bb = SqlFragment("join Profiles using(ProfileID)")
val cc = SqlFragment("where Caption like \"%{a}\" and MovieID = {b}", 'a -> "s", 'b -> 5)
// combine all fragments
val v1 = SQL(Seq(aa, bb, cc).map(_.Fragment).mkString(" "))
.on((aa.Args ++ bb.Args ++ cc.Args): _*)
// better solution
val v2 = Seq(aa, bb, cc).unzip(frag => (frag.Fragment, frag.Args)) match {
case (frags, args) => SQL(frags.mkString(" ")).on(args.flatten: _*)
}
// works
println(v1.as(scalar[Long].singleOpt))
println(v2.as(scalar[Long].singleOpt))
}
It seems to work great! :-)
I then rewrote the last part of the freetext filter as follow:
// finally transform the expression
// list a single sql fragment
expressions.zipWithIndex.map { case (expr, index) =>
s"""
(concat(Movies.Caption, " ", Movies.Description, " ", Movies.Kind, " ", Profiles.Nickname, " ",
(select coalesce(group_concat(Tags.Name), "") from Tags join TagLinks using (TagID)
where TagLinks.MovieID = Movies.MovieID)) like "%{expr$index}%"))
""" -> (s"expr$index" -> expr)
}.unzip match { case (frags, args) => SqlFragment(frags.mkString(" and "), args.flatten: _*)
What do you think?
This is how it is being implemented right now:
/**
* This private helper method transforms a content filter string into an sql expression
* for searching within movies, owners and kinds and tags.
* #author Samuel Lörtscher
*/
private def contentFilterToSql(value: String) = {
// trim and clean and the parametric value from any possible anomalies
// (those include strange spacing and non closed quotes)
val cleaned = value.trim match {
case trimmed if trimmed.count(_ == '"') % 2 != 0 =>
if (trimmed.last == '"') trimmed.dropRight(1).trim
else trimmed + '"'
case trimmed =>
trimmed
};
// transform the cleaned value into a list of expressions
// (words between quotes are considered being one expression)
// empty expressions between quotes are being removed
// expressions will contain no quotes as they are being stripped during evaluation -
// thus counter measures for sql injection should be obsolete
// (we put an empty space at the end because it makes the lexer algorithm much
// more efficient as it will not need to check for end of file in every iteration)
val expressions = (cleaned + " ").foldLeft((List[String](), "", false)) { case ((list, expr, quoted), char) =>
// perform the lexer operation for the current character
if (char == ' ' && !quoted) (expr :: list, "", false)
else if (char == '"') (expr :: list, "", !quoted)
else (list, expr + char, quoted)
}._1.filter(_.nonEmpty).map(_.trim)
// finally transform the expression
// list into a variable length sql condition statement
expressions.map { expr =>
s"""
(concat(Movies.Caption, " ", Movies.Description, " ", Movies.Kind, " ", Profiles.Nickname, " ",
(select coalesce(group_concat(Tags.Name), "")
from Tags join TagLinks using (TagID) where TagLinks.MovieID = Movies.MovieID)) like "%$expr%")
"""
}.mkString(" and ")
}
Since the number of search expressions is variable, I cannot use Anorm arguments here. :-/
I found a simple solution now, but I am not exactly happy being forced to apply such crappy hacks.
Since putting a %s character sequence seems to trigger the bug, I was looking for possibilities to submit the same semantical outcome without directly passing this character sequence. I finally ended up replacing like "%$expr%" by like concat("%", "$expr%"). Since concat is being evaluated by the MySql Server engine BEFORE "like", he will put the original pattern back together before processing it by "like" - and without the sequence %s ever being transmitted through the anorm, jdbc data processors.
// finally transform the expression
// list into a variable length sql condition statement
// (freaking concat("%", "$expr%")) is required due to a freaking bug in either anorm or JDBC
// which results into a crash when %s is anyway submitted)
expressions.map { expr =>
s"""
(concat(Movies.Caption, " ", Movies.Description, " ", Movies.Kind, " ", Profiles.Nickname, " ",
(select coalesce(group_concat(Tags.Name), "")
from Tags join TagLinks using (TagID) where TagLinks.MovieID = Movies.MovieID)) like concat("%", "$expr%"))
"""
}.mkString(" and ")

F# Navigate object graph to return specific Nodes

I'm trying to build a list of DataTables based on DataRelations in a DataSet, where the tables returned are only included by their relationships with each other, knowing each end of the chain in advance. Such that my DataSet has 7 Tables. The relationships look like this:
Table1 -> Table2 -> Table3 -> Table4 -> Table5
-> Table6 -> Table7
So given Table1 and Table7, I want to return Tables 1, 2, 3, 6, 7
My code so far traverses all the relations and returns all Tables so in the example it returns Table4 and Table5 as well. I've passed in the first, last as arguments, and know that I'm not yet using the last one yet, I am still trying to think how to go about it, and is where I need the help.
type DataItem =
| T of DataTable
| R of DataRelation list
let GetRelatedTables (first, last) =
let rec flat_rec dt acc =
match dt with
| T(dt) ->
let rels = [ for r in dt.ParentRelations do yield r ]
dt :: flat_rec(R(rels)) acc
| R(h::t) ->
flat_rec(R(t)) acc # flat_rec(T(h.ParentTable)) acc
| R([]) -> []
flat_rec first []
I think something like this would do it (although I haven't tested it). It returns DataTable list option because, in theory, a path between two tables might not exist.
let findPathBetweenTables (table1 : DataTable) (table2 : DataTable) =
let visited = System.Collections.Generic.HashSet() //check for circular references
let rec search path =
let table = List.head path
if not (visited.Add(table)) then None
elif table = table2 then Some(List.rev path)
else
table.ChildRelations
|> Seq.cast<DataRelation>
|> Seq.tryPick (fun rel -> search (rel.ChildTable::path))
search [table1]

How to make aggregations with slick

I want to force slick to create queries like
select max(price) from coffees where ...
But slick's documentation doesn't help
val q = Coffees.map(_.price) //this is query Query[Coffees.type, ...]
val q1 = q.min // this is Column[Option[Double]]
val q2 = q.max
val q3 = q.sum
val q4 = q.avg
Because those q1-q4 aren't queries, I can't get the results but can use them inside other queries.
This statement
for {
coffee <- Coffees
} yield coffee.price.max
generates right query but is deprecated (generates warning: " method max in class ColumnExtensionMethods is deprecated: Use Query.max instead").
How to generate such query without warnings?
Another issue is to aggregate with group by:
"select name, max(price) from coffees group by name"
Tried to solve it with
for {
coffee <- Coffees
} yield (coffee.name, coffee.price.max)).groupBy(x => x._1)
which generates
select x2.x3, x2.x3, x2.x4 from (select x5."COF_NAME" as x3, max(x5."PRICE") as x4 from "coffees" x5) x2 group by x2.x3
which causes obvious db error
column "x5.COF_NAME" must appear in the GROUP BY clause or be used in an aggregate function
How to generate such query?
As far as I can tell is the first one simply
Query(Coffees.map(_.price).max).first
And the second one
val maxQuery = Coffees
.groupBy { _.name }
.map { case (name, c) =>
name -> c.map(_.price).max
}
maxQuery.list
or
val maxQuery = for {
(name, c) <- Coffees groupBy (_.name)
} yield name -> c.map(_.price).max
maxQuery.list