slick-pg icon indicating copy to clipboard operation
slick-pg copied to clipboard

Ecaping array elements is too eager

Open nafg opened this issue 4 years ago • 5 comments

  def arrayAsText[A: ClassTag](fromString: String => Option[A])(toString: A => String): JdbcType[Seq[A]] =
    new SimpleArrayJdbcType[String]("text")
      .mapTo(s => fromString(s).getOrElse(sys.error(s"Could not convert '$s' to a ${classTag[A]}")), toString)

does not work if toString of an A includes an apostrophe, because it gets saved with a double apostrophe.

I assume that without the mapTo line as well, nothing would crash but your strings would get their apostrophes doubled.

Similarly, this broke recently:

  def arrayAsText[A: ClassTag](fromString: String => Option[A])(toString: A => String): JdbcType[Seq[A]] =
    new AdvancedArrayJdbcType[A](
      sqlBaseType = "text",
      fromString = SimpleArrayUtils.fromString(fromString)(_).map(_.flatten).orNull,
      mkString = SimpleArrayUtils.mkString[A](toString)(_)
    )

Changing SimpleArrayUtils.mkString to SimpleArrayUtils.mkStringUnsafe works.

nafg avatar Mar 11 '21 20:03 nafg

@nafg I didn't reproduce the double apostrophe. Can you provide full reproducible codes?

tminglei avatar Apr 21 '21 14:04 tminglei

I just tried with 0.19.6 and it's still broken.

It's not hard to reproduce. There's nothing complicated here.

In fact, if you can show a working example that includes saving and reloading a TEXT[] containing an apostrophe character that doesn't involve low-level APIs (preferably mapped to a custom type that has a string representation), then I can't claim anything is broken.

That said, I will try to share more code. It would be hard to start making a minimal executable self-contained program, but here is the relevant code, that should give you a clearer picture of what I have.

def arrayAsText[A: ClassTag](fromString: String => Option[A])(toString: A => String): JdbcType[Seq[A]] =
    new SimpleArrayJdbcType[String]("text")
      .mapTo(s => fromString(s).getOrElse(sys.error(s"Could not convert '$s' to a ${classTag[A]}")), toString)
abstract class CustomFieldKind(val id: String, val name: String) {
  def subfieldDefs: List[SubfieldDefBase]
  def allowInNature(@unused nature: Nature): Boolean = true

  override def toString = s"CustomFieldKind($id, $name) { subfieldDefs = $subfieldDefs }"
}
object CustomFieldKind {
  class Simple(id: String, title: String)(defs: SubfieldDefBase*) extends CustomFieldKind(id, title) {
    override def subfieldDefs = defs.toList
  }

  private val ClientAddressFieldKind = new Simple("clientAddress", "Client's address")(
    SubfieldDef(SubfieldKind.ClientAddress, "clientAddress", "Client's address")
  )
  def forName(string: String): Option[CustomFieldKind] = all.find(_.name == string)
  private implicit val fieldKindListMapping: JdbcType[Seq[CustomFieldKind]] =
    SlickColumnMappings.arrayAsText(CustomFieldKind.forName)(_.name)
  def allowedKinds = column[Seq[CustomFieldKind]]("allowed_kinds")

If I then save an array containing a ClientAddressFieldKind into allowedKinds and then try to load it, I get the following error:

java.lang.RuntimeException: Could not convert 'Client''s address' to a chesednow.customfields.shared.CustomFieldKind            
     at scala.sys.package$.error(package.scala:27)                                                                              
     at chesednow.models.common.SlickColumnMappings$.$anonfun$arrayAsText$2(SlickColumnMappings.scala:50)                       
     at scala.Option.getOrElse(Option.scala:201)                                                                                
     at chesednow.models.common.SlickColumnMappings$.$anonfun$arrayAsText$1(SlickColumnMappings.scala:50)                       
     at com.github.tminglei.slickpg.array.PgArrayJdbcTypes$SimpleArrayJdbcType.$anonfun$mapTo$1(PgArrayJdbcTypes.scala:66)      
     at scala.collection.immutable.ArraySeq.$anonfun$map$1(ArraySeq.scala:71)                                                   
     at scala.collection.immutable.ArraySeq.$anonfun$map$1$adapted(ArraySeq.scala:71)                                           
     at scala.collection.immutable.ArraySeq$.tabulate(ArraySeq.scala:286)                                                       
     at scala.collection.immutable.ArraySeq$.tabulate(ArraySeq.scala:265)                                                       
     at scala.collection.ClassTagIterableFactory$AnyIterableDelegate.tabulate(Factory.scala:679)                                

Is it clearer now?

nafg avatar May 07 '21 02:05 nafg

I should mention that querying the database directly shows that indeed, it saved it as Client''s address instead of as Client's address.

nafg avatar May 07 '21 02:05 nafg

Hi, was there any update on this?

nafg avatar Aug 20 '23 22:08 nafg

@nafg I can't reproduce it with updated tests at a6836d4.

tminglei avatar Sep 02 '23 03:09 tminglei