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

Meta constructor for struct types #49

Open
tpolecat opened this issue Jan 16, 2015 · 7 comments
Open

Meta constructor for struct types #49

tpolecat opened this issue Jan 16, 2015 · 7 comments
Milestone

Comments

@tpolecat
Copy link
Member

I think the path from here to there is:

  • On Meta we need toObject and fromObject that perform a cast at the lowest level and then apply any accumulated invariant mappings. Propagate this to Atom.
  • On Composite we then implement toObjectArray and fromObjectArray inductively, and from there trivially we can implement Meta.fromComposite.
@tpolecat
Copy link
Member Author

Examples given in #94

@tpolecat tpolecat modified the milestones: 0.3.0, 0.2.2 Mar 10, 2015
@tpolecat
Copy link
Member Author

Bah, ok this doesn't work with nulls because we can't distinguish a null Int from 0. So the derivation needs to be distinct from Composite. For now maybe punt and just have a raw struct mapping that you have to nxmap.

@tpolecat
Copy link
Member Author

Well, actually, nuts to this. The Postgres driver doesn't support structs so you have to go via JSON or something. Pushing to backlog.

@tpolecat tpolecat modified the milestones: Backlog, 0.2.2 Apr 23, 2015
@wedens
Copy link
Contributor

wedens commented Jan 15, 2018

Just for the future reference and in case someone is looking for at least half-arsed solution:

  def pgStruct[A >: Null <: AnyRef: TypeTag](
    typeName: String,
    encode: A => String,
    decode: String => A
  )(implicit A: ClassTag[A]): AdvancedMeta[A] = {
    val toPGobject: A => PGobject = a => {
      val o = new PGobject
      o.setType(typeName)
      o.setValue(encode(a))
      o
    }
    Meta.advanced[A](
      NonEmptyList.of(JdbcType.Struct),
      NonEmptyList.of(typeName),
      (rs, n) => rs.getObject(n) match {
        case null => null
        case a: PGobject => decode(a.getValue)
        case a => throw InvalidObjectMapping(A.runtimeClass, a.getClass)
      },
      (ps, n, a) => ps.setObject(n, toPGobject(a)),
      (rs, n, a) => rs.updateObject(n, toPGobject(a))
    )
  }

encode and decode assumes string in a format (value1,value2,...) (I can't find any specification on this format)

@tpolecat
Copy link
Member Author

Yeah the PGObject format seems to be undocumented. I assume it's the same string encoding that's used by the wire protocol, which is the literal encoding you use in SQL statements. I have looked at the driver's implementation and I can't figure it out. It's very complicated and side-effecty. It would be a good project to try to make sense of it.

@Malax
Copy link

Malax commented Jan 26, 2018

I think this is the documentation for syntax of the string: https://www.postgresql.org/docs/current/static/rowtypes.html#ROWTYPES-IO-SYNTAX. I hacked something that can escape/unescape this, maybe it's worth iterating on it to work out the edge cases.

Worth noting that Postgres changes the escaping on its own (tested with 10). If you escape it using backslashes, it'll come back quoted.

object CompositeTextRepresentation {
  def build(s: List[String]): String = {
    s.map(_.replace(",", "\\,").replace("\"", "\\\"")).mkString("(", ",", ")")
  }

  def parse(s: String): List[String] = {
    val inner = s.substring(1, s.length - 1)

    var inQuotes = false
    var inEscapedQuote = false

    var builder = StringBuilder.newBuilder
    var results = List.empty[String]

    for (c <- inner) {
      if (c != '"' && c != ',') {
        builder.append(c)
      }

      if (c == '"') {
        if (!inQuotes) {
          inQuotes = true
        } else if (inQuotes && !inEscapedQuote) {
          inEscapedQuote = true
        } else {
          builder.append(c)
          inEscapedQuote = false
        }
      }

      if (c == ',') {
        if (inQuotes && !inEscapedQuote) {
          builder.append(c)
        } else {
          inQuotes = false
          inEscapedQuote = false
          results = builder.toString() :: results
          builder = StringBuilder.newBuilder
        }
      }
    }

    (builder.toString() :: results).reverse
  }
}

This still requires a way to get from individual types to a single string tho.

@wedens
Copy link
Contributor

wedens commented Jan 26, 2018

This still requires a way to get from individual types to a single string tho.

Yeah, it asks for a typeclass. Similarly to this PR #625

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

No branches or pull requests

3 participants