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

Encoders and decoders refactoring #614

Merged
merged 12 commits into from Nov 10, 2016
Merged

Encoders and decoders refactoring #614

merged 12 commits into from Nov 10, 2016

Conversation

mxl
Copy link
Contributor

@mxl mxl commented Nov 1, 2016

Fixes #611

Problem

In #588 encoders and decoders type were changed to AsyncEncoder and AsyncDecoder so if I have custom type case class EncodingTestType(value: String) and table with field of that type and I trying to insert values into this table inside singleton object then compilation of this code fails. For a detailed example see test case in this pull request.

Solution

Change back types to Encoder and Decoder.

Checklist

  • Unit test all changes
  • Update README.md if applicable
  • Add [WIP] to the pull request title if it's work in progress
  • Squash commits that aren't meaningful changes
  • Run sbt scalariformFormat test:scalariformFormat to make sure that the source files are formatted

@getquill/maintainers

@mdedetrich
Copy link
Collaborator

mdedetrich commented Nov 1, 2016

Isn't this something that is better solved by addressing the core problem rather than reverting #588 ? If the intention is to revert #588 then we should just revert the commit or discuss the issues there rather than creating a new pull request that is just reverting an older one.

I feel this is better solved by detecting why the AsyncDecoder/AsyncEncoder aren't being picked up. Also, JdbcEncoder existed before #588 and it worked fine in this case (iirc) which leaves me to suspect that its an issue elsewhere.

@mxl
Copy link
Contributor Author

mxl commented Nov 1, 2016

@mdedetrich I did not revert changes from your pull request and there was no such intent. I just specified correct types for encoders and decoders back to Encoder and Decoder as it should be (seeio.getquill.context.sql.SqlContext).
In io.getquill.context.async.Encoders try to change line

implicit val stringEncoder: Encoder[String] = encoder[String](SqlTypes.VARCHAR)

to

implicit val stringEncoder = encoder[String](SqlTypes.VARCHAR)

and you'll see that added test is failing.
I do not know why this is happening but I hope that this is correct fix to the issue and it does not ruin your work in #588, isn't it? Please comment if I done something wrong.

@mxl
Copy link
Contributor Author

mxl commented Nov 1, 2016

@mdedetrich If you'll look at io.getquill.context.jdbc.JdbcEncoders then you'll notice that all encoders vals have type Encoder[...] specified:

implicit val stringEncoder: Encoder[String] = encoder(_.setString, Types.VARCHAR)

so I did just the same for io.getquill.context.async.Encoders.

@mdedetrich
Copy link
Collaborator

mdedetrich commented Nov 1, 2016

I do not know why this is happening but I hope that this is correct fix to the issue and it does not ruin your work in #588, isn't it? Please comment if I done something wrong.

Nothing wrong, but the intention of the pull request is that its possible to get any SQL primitive type out of an Encoder/Decoder (which differs against different driver implementations, hence why there is a JDBCEncoder vs an AsyncEncoder).

I need to check if this actually effects me with the plugin I am making, it may not be an issue but Quill does do some weird subtyping stuff in this area

I am just actually more surprised as to why this is coming up

@mxl
Copy link
Contributor Author

mxl commented Nov 1, 2016

@mdedetrich You can get SQL primitive type by pattern matching as before because for example type of value assigned to stringEncoder is still AsyncEncoder[String].

@mxl
Copy link
Contributor Author

mxl commented Nov 2, 2016

@mdedetrich Have you checked your plugin? I'm asking because I have two projects that I can not compile because of this issue and it's hard to roll them back to 0.10.1-SNAPSHOT because they would not compile because of other issues :(

@mdedetrich
Copy link
Collaborator

@mxl I will have time to look at it tonight, is that okay?

@mxl
Copy link
Contributor Author

mxl commented Nov 2, 2016

@mdedetrich Ok.

@mdedetrich
Copy link
Collaborator

mdedetrich commented Nov 2, 2016

@mxl Ok so here is the issue. Assuming that we change

  implicit val stringDecoder: AsyncDecoder[String] = decoder[String](PartialFunction.empty, SqlTypes.VARCHAR)

to

  implicit val stringDecoder: Decoder[String] = decoder[String](PartialFunction.empty, SqlTypes.VARCHAR)

The intention of the AsyncDecoder in the first place is that we can implicitly summon an AsyncDecoder[String]. With your change, this obviously doesn't work, i.e.

  val decoder = implicitly[AsyncDecoder[String]]

Fails. Now we can just summon the Decoder, i.e.

  val decoder = implicitly[Decoder[String]]

The issue here is that we now can't access the sqlType, which is the point of the AsyncDecoder in the first place, i.e. we wan't to get the sqlType: SqlTypes.SqlTypes for the any type T (in this case String which happens to be a SqlTypes.VARCHAR)

Of course, we can do something like this

val decoder = implicitly[Decoder[String]].asInstanceOf[AsyncDecoder]

decoder.sqlType

However that is a bit hacky. I think I am willing to live with this, but I still have a very strong suspicion that something else may be amiss here which is the actual root of the problem

@mxl
Copy link
Contributor Author

mxl commented Nov 3, 2016

@fwbrasil We discussed this issue with @mdedetrich and we need your help here.
The thing is that changing type of properties in io.getquill.context.async.Encoders to AsyncEncoder leads to the issue that I'm trying to fix with this pull request. But in some cases as @mdedetrich described in above comment it can be useful to get implicit value of AsyncEncoder that has already beed defined for the particular type. Suppose I want to extend io.getquill.context.async.Encoders and define there:

implicit def dbTraversableEncoder[T](implicit encoder: AsyncEncoder[T]): AsyncEncoder[Traversable[T]] =
    ...

and because of some driver implementation details I need exactly AsyncEncoder[T] that contains these details (sqlType for example) and not Encoder[T].

Actually now I agree that we should solve the core problem.

I tried following.

In io.getquill.dsl.QueryDslMacro on line 24 I changed silent = true to silent = false and added -Xlog-implicits to scalacOptions in build.sbt.

Then in io.getquill.context.async.Encoders I changed

implicit val stringEncoder: Encoder[String] = encoder[String](SqlTypes.VARCHAR)

to

 implicit val stringEncoder = encoder[String](SqlTypes.VARCHAR)

Running tests results in:

[info] mappedEncoder is not a valid implicit value for c.Encoder[PostgresAsyncEncodingSpec.this.EncodingTestEntity] because:
[info] hasMatchingSymbol reported error: could not find implicit value for parameter mapped: c.MappedEncoding[PostgresAsyncEncodingSpec.this.EncodingTestEntity,O]
[info] mappedEncoder is not a valid implicit value for c.Encoder[PostgresAsyncEncodingSpec.this.EncodingTestEntity] because:
[info] hasMatchingSymbol reported error: could not find implicit value for parameter mapped: c.MappedEncoding[PostgresAsyncEncodingSpec.this.EncodingTestEntity,O]
[info] mappedEncoder is not a valid implicit value for c.Encoder[String] because:
[info] hasMatchingSymbol reported error: diverging implicit expansion for type c.Encoder[io.getquill.context.sql.EncodingTestType]
[info] starting with macro method materializeEncoder in trait LowPriorityImplicits
[info] mappedEncoder is not a valid implicit value for c.Encoder[io.getquill.context.sql.EncodingTestType] because:
[info] hasMatchingSymbol reported error: Can't find Encoder for type 'String'
[info] mappedEncoder is not a valid implicit value for c.Encoder[String] because:
[info] hasMatchingSymbol reported error: Can't find Encoder for type 'io.getquill.context.sql.EncodingTestType'
[info] /app/quill-async-postgres/src/test/scala/io/getquill/context/async/postgres/PostgresAsyncEncodingSpec.scala:118: materializeInsertMeta is not a valid implicit value for io.getquill.dsl.MetaDsl#InsertMeta[PostgresAsyncEncodingSpec.this.EncodingTestEntity] because:
[info] hasMatchingSymbol reported error: Can't expand nested value 'String', please make it an Embedded case class or provide an implicit Encoder for it.
[info] result <- c.run(liftQuery(insertValues).foreach(e => query[EncodingTestEntity].insert(e)))
[info] ^
[error] /app/quill-async-postgres/src/test/scala/io/getquill/context/async/postgres/PostgresAsyncEncodingSpec.scala:118: exception during macro expansion:
[error] scala.reflect.macros.TypecheckException: implicit search has failed. to find out the reason, turn on -Xlog-implicits
[error] at scala.reflect.macros.TypecheckException$.apply(Typers.scala:126)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$inferImplicitValue$2.apply(Typers.scala:41)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$inferImplicitValue$2.apply(Typers.scala:41)
[error] at scala.tools.nsc.typechecker.Implicits$class.inferImplicit(Implicits.scala:102)
[error] at scala.tools.nsc.Global$$anon$1.inferImplicit(Global.scala:462)
[error] at scala.reflect.macros.contexts.Typers$class.inferImplicitValue(Typers.scala:41)
[error] at scala.reflect.macros.contexts.Context.inferImplicitValue(Context.scala:6)
[error] at scala.reflect.macros.contexts.Context.inferImplicitValue(Context.scala:6)
[error] at io.getquill.dsl.QueryDslMacro.meta(QueryDslMacro.scala:24)
[error] at io.getquill.dsl.QueryDslMacro.expandAction(QueryDslMacro.scala:20)
[error] at io.getquill.dsl.QueryDslMacro.expandInsert(QueryDslMacro.scala:14)
[error] result <- c.run(liftQuery(insertValues).foreach(e => query[EncodingTestEntity].insert(e)))

I searched for Can't expand nested value message and found io.getquill.dsl.MetaDslMacro line 179.

And finally I wrote simple case that illustrates the issue.

@fwbrasil
Copy link
Collaborator

fwbrasil commented Nov 3, 2016

@mxl Thanks for the stackoverflow link! I was trying to avoid a refactoring of the encoder definitions but it seems necessary. My proposal is:

EncodingDsl.scala

type Index = Int
type Encoder[T] <: (Index, T, PrepareRow) => PrepareRow
type Decoder[T] <: (Index, ResultRow) => T

AsyncContext and other contexts

case class AsyncEncoder[T](sqlType: SqlTypes.SqlTypes)(implicit encoder: Encoder[T]) extends ((Index, T, PrepareRow) => PrepareRow) {
  override def apply(index: Index, value: T, row: PrepareRow) = ...
...
}

type Encoder[T] = AsyncEncoder[T]

// the same for `Decoder`

It seems that it should work: https://scalafiddle.io/sf/2N1qodh/11.

This also would allow us to remove the ugly hack that requires the contexts to override mappedDecoderImpl/mappedEncoderImpl

@mxl @mdedetrich what do you think?

@mxl
Copy link
Contributor Author

mxl commented Nov 3, 2016

@fwbrasil 👍

@mdedetrich
Copy link
Collaborator

All good from me 👍

@fwbrasil
Copy link
Collaborator

fwbrasil commented Nov 4, 2016

@mxl or @mdedetrich would you be interested in working on it?

@mxl
Copy link
Contributor Author

mxl commented Nov 4, 2016

@fwbrasil Let me try to finish it in this pull request.

@mxl mxl changed the title Encoders and decoders types for quill-async-* Encoders and decoders refactoring Nov 9, 2016
@mxl
Copy link
Contributor Author

mxl commented Nov 9, 2016

@fwbrasil Done.

# Conflicts:
#	quill-core/src/main/scala/io/getquill/dsl/MetaDslMacro.scala
@mxl
Copy link
Contributor Author

mxl commented Nov 9, 2016

@fwbrasil Could you review this PR ASAP? #611 is a showstopper for my team. Thanks!


trait LowPriorityImplicits {
this: EncodingDsl =>

implicit def materializeEncoder[T]: Encoder[T] = macro EncodingDslMacro.materializeEncoder[T]
implicit def materializeDecoder[T]: Decoder[T] = macro EncodingDslMacro.materializeDecoder[T]
implicit def materializeEncoder[T <: AnyVal]: Encoder[T] = macro EncodingDslMacro.materializeEncoder[T]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you clarify why you needed to add the <: AnyVal constraint? The materializers also work for AnyRefs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without <: AnyVal constraint it conflicts with implicit def mappedEncoder when resolving implicits. Try to remove constraint and run tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you sure that it works with AnyRefs? It looks like it fails if type is not AnyVal.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, makes sense 👍


implicit def mappedEncoder[I, O](implicit mapped: MappedEncoding[I, O], encoder: Encoder[O]): Encoder[I] =
mappedEncoderImpl[I, O]
def mappedBaseEncoder[I, O](mapped: MappedEncoding[I, O], encoder: BaseEncoder[O]): BaseEncoder[I] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

could this be protected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

def apply(index: Int, value: I, row: PrepareRow) =
encoder(index, mapped.f(value), row)
}
def mappedBaseDecoder[I, O](mapped: MappedEncoding[I, O], decoder: BaseDecoder[I]): BaseDecoder[O] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

could this be protected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants