Skip to content

Commit

Permalink
Merge pull request #242 from gemini-hlsw/like
Browse files Browse the repository at this point in the history
switch Like to take an optional term
  • Loading branch information
milessabin authored Aug 8, 2022
2 parents 3a06d25 + f934778 commit 5b67161
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 2 deletions.
4 changes: 4 additions & 0 deletions modules/doobie/src/test/scala/DoobieSuites.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ final class JsonbSpec extends DoobieDatabaseSuite with SqlJsonbSpec {
lazy val mapping = new DoobieTestMapping(xa) with SqlJsonbMapping[IO]
}

final class LikeSpec extends DoobieDatabaseSuite with SqlLikeSpec {
lazy val mapping = new DoobieTestMapping(xa) with SqlLikeMapping[IO]
}

final class MovieSpec extends DoobieDatabaseSuite with SqlMovieSpec {
lazy val mapping =
new DoobieTestMapping(xa) with SqlMovieMapping[IO] {
Expand Down
4 changes: 4 additions & 0 deletions modules/skunk/src/test/scala/SkunkSuites.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ final class JsonbSpec extends SkunkDatabaseSuite with SqlJsonbSpec {
lazy val mapping = new SkunkTestMapping(pool) with SqlJsonbMapping[IO]
}

final class LikeSpec extends SkunkDatabaseSuite with SqlLikeSpec {
lazy val mapping = new SkunkTestMapping(pool) with SqlLikeMapping[IO]
}

final class MovieSpec extends SkunkDatabaseSuite with SqlMovieSpec {
lazy val mapping =
new SkunkTestMapping(pool) with SqlMovieMapping[IO] {
Expand Down
42 changes: 42 additions & 0 deletions modules/sql/src/main/scala-2/Like.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2016-2020 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package edu.gemini.grackle
package sql

import scala.util.matching.Regex

case class Like private[sql] (x: Term[_], pattern: String, caseInsensitive: Boolean) extends Predicate {
lazy val r = Like.likeToRegex(pattern, caseInsensitive)
def apply(c: Cursor): Result[Boolean] =
x(c).map(_ match {
case s: String => r.matches(s)
case Some(s: String) => r.matches(s)
case None => false
case other => sys.error(s"Expected value of type String or Option[String], found $other")
})
def children = List(x)
}

object Like extends Like0 {
private[sql] def apply(x: Term[_], pattern: String, caseInsensitive: Boolean): Like =
new Like(x, pattern, caseInsensitive)

private def likeToRegex(pattern: String, caseInsensitive: Boolean): Regex = {
val csr = ("^"+pattern.replace("%", ".*").replace("_", ".")+"$")
(if (caseInsensitive) s"(?i:$csr)" else csr).r
}
}

trait Like0 {
trait PossiblyOptionString[T]
object PossiblyOptionString extends PossiblyOptionString0 {
implicit val sInst: PossiblyOptionString[String] = new PossiblyOptionString[String] {}
}
trait PossiblyOptionString0 {
implicit val osInst: PossiblyOptionString[Option[String]] = new PossiblyOptionString[Option[String]] {}
}

def apply[T](x: Term[T], pattern: String, caseInsensitive: Boolean)(implicit @annotation.nowarn ev: PossiblyOptionString[T]): Predicate =
new Like(x, pattern, caseInsensitive)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ package sql

import scala.util.matching.Regex

case class Like(x: Term[String], pattern: String, caseInsensitive: Boolean) extends Predicate {
case class Like(x: Term[String]|Term[Option[String]], pattern: String, caseInsensitive: Boolean) extends Predicate {
lazy val r = Like.likeToRegex(pattern, caseInsensitive)
def apply(c: Cursor): Result[Boolean] = x(c).map(r.matches(_))
def apply(c: Cursor): Result[Boolean] =
x(c).map(_ match {
case s: String => r.matches(s)
case Some(s: String) => r.matches(s)
case None => false
})
def children = List(x)
}

Expand Down
10 changes: 10 additions & 0 deletions modules/sql/src/test/resources/db/like.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE likes (
id INTEGER PRIMARY KEY,
notnullable TEXT NOT NULL,
nullable TEXT
);

COPY likes (id, notnullable, nullable) FROM STDIN WITH DELIMITER '|';
1|foo|\N
2|bar|baz
\.
100 changes: 100 additions & 0 deletions modules/sql/src/test/scala/SqlLikeMapping.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2016-2020 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package edu.gemini.grackle.sql.test

import cats.implicits._

import edu.gemini.grackle._
import edu.gemini.grackle.syntax._
import Query._, Path._, Predicate._, Value._
import QueryCompiler._
import sql.Like

trait SqlLikeMapping[F[_]] extends SqlTestMapping[F] {

object likes extends TableDef("likes") {
val id = col("id", int4)
val notNullableText = col("notnullable", text)
val nullableText = col("nullable", nullable(text))
}

val schema =
schema"""
type Query {
likes: [Like!]!
likeNotNullableNotNullable(pattern: String!): [Like!]!
likeNotNullableNullable(pattern: String): [Like!]!
likeNullableNotNullable(pattern: String!): [Like!]!
likeNullableNullable(pattern: String): [Like!]!
}
type Like {
id: Int!
notNullable: String!
nullable: String
}
"""

val QueryType = schema.ref("Query")
val LikeType = schema.ref("Like")

val typeMappings =
List(
ObjectMapping(
tpe = QueryType,
fieldMappings =
List(
SqlRoot("likes"),
)
),
ObjectMapping(
tpe = LikeType,
fieldMappings =
List(
SqlField("id", likes.id, key = true),
SqlField("notNullable", likes.notNullableText),
SqlField("nullable", likes.nullableText)
)
)
)

object NonNullablePattern {
def unapply(value: Value): Option[String] =
value match {
case AbsentValue => None
case StringValue(p) => Some(p)
case other => sys.error(s"Expected pattern, found $other")
}
}

object NullablePattern {
def unapply(value: Value): Option[Option[String]] =
value match {
case AbsentValue => None
case NullValue => Some(None)
case StringValue(p) => Some(Some(p))
case other => sys.error(s"Expected pattern, found $other")
}
}

def mkPredicate(t: Term[Option[String]], pattern: Option[String]): Predicate =
pattern match {
case None => IsNull(t, true)
case Some(p) => Like(t, p, false)
}

override val selectElaborator: SelectElaborator = new SelectElaborator(Map(
QueryType -> {
case Select(f@"likeNotNullableNotNullable", List(Binding("pattern", NonNullablePattern(pattern))), child) =>
Rename(f, Select("likes", Nil, Filter(Like(UniquePath(List("notNullable")), pattern, false), child))).rightIor
case Select(f@"likeNotNullableNullable", List(Binding("pattern", NullablePattern(pattern))), child) =>
Rename(f, Select("likes", Nil, Filter(mkPredicate(UniquePath(List("notNullable")), pattern), child))).rightIor
case Select(f@"likeNullableNotNullable", List(Binding("pattern", NonNullablePattern(pattern))), child) =>
Rename(f, Select("likes", Nil, Filter(Like(UniquePath(List("nullable")), pattern, false), child))).rightIor
case Select(f@"likeNullableNullable", List(Binding("pattern", NullablePattern(pattern))), child) =>
Rename(f, Select("likes", Nil, Filter(mkPredicate(UniquePath(List("nullable")), pattern), child))).rightIor

case other => other.rightIor
}
))
}
178 changes: 178 additions & 0 deletions modules/sql/src/test/scala/SqlLikeSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright (c) 2016-2020 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package edu.gemini.grackle.sql.test

import cats.effect.IO
import io.circe.Json
import org.scalatest.funsuite.AnyFunSuite
import cats.effect.unsafe.implicits.global

import edu.gemini.grackle._
import syntax._

import grackle.test.GraphQLResponseTests.assertWeaklyEqual

trait SqlLikeSpec extends AnyFunSuite {
def mapping: QueryExecutor[IO, Json]

test("No filter") {
val query = """
query {
likes {
id
notNullable
nullable
}
}
"""

val expected = json"""
{
"data" : {
"likes" : [
{
"id" : 1,
"notNullable" : "foo",
"nullable" : null
},
{
"id" : 2,
"notNullable" : "bar",
"nullable" : "baz"
}
]
}
}
"""

val res = mapping.compileAndRun(query).unsafeRunSync()
//println(res)

assertWeaklyEqual(res, expected)
}

test("Not nullable, not null") {
val query = """
query {
likeNotNullableNotNullable(pattern: "f%") {
id
notNullable
nullable
}
}
"""

val expected = json"""
{
"data" : {
"likeNotNullableNotNullable" : [
{
"id" : 1,
"notNullable" : "foo",
"nullable" : null
}
]
}
}
"""

val res = mapping.compileAndRun(query).unsafeRunSync()
//println(res)

assertWeaklyEqual(res, expected)
}

test("Not nullable, null") {
val query = """
query {
likeNotNullableNullable(pattern: null) {
id
notNullable
nullable
}
}
"""

val expected = json"""
{
"data" : {
"likeNotNullableNullable" : [
]
}
}
"""

val res = mapping.compileAndRun(query).unsafeRunSync()
//println(res)

assertWeaklyEqual(res, expected)
}

test("Nullable, not null") {
val query = """
query {
likeNullableNotNullable(pattern: "b%") {
id
notNullable
nullable
}
}
"""

val expected = json"""
{
"data" : {
"likeNullableNotNullable" : [
{
"id" : 2,
"notNullable" : "bar",
"nullable" : "baz"
}
]
}
}
"""

val res = mapping.compileAndRun(query).unsafeRunSync()
//println(res)

assertWeaklyEqual(res, expected)
}

test("Nullable, null") {
val query = """
query {
likeNullableNullable(pattern: null) {
id
notNullable
nullable
}
}
"""

val expected = json"""
{
"data" : {
"likeNullableNullable" : [
{
"id" : 1,
"notNullable" : "foo",
"nullable" : null
}
]
}
}
"""

val res = mapping.compileAndRun(query).unsafeRunSync()
//println(res)

assertWeaklyEqual(res, expected)
}
}

0 comments on commit 5b67161

Please sign in to comment.