scalikejdbcでDBMS独自構文を書く

例えばMySQLon duplicate key updateなど

まず、sql"..."で書けば何でもいけるのはいいとして

def upsertUser1(id: String, email: String)(implicit session: DBSession) =
  sql"""
    insert into `user` (`id`, `email`) values ($id, $email)
    on duplicate key update `email` = values(`email`)
  """.update().apply()

QueryDSLでinsert文を書いてsqls"..."appendするもよし

def upsertUser2(id: String, email: String)(implicit session: DBSession) =
  applyUpdate {
    val c = User.column
    insert.into(User).namedValues(
      c.id -> id,
      c.email -> email
    ).append(
      sqls"on duplicate key update ${c.email} = values(${c.email})"
    )
  }

しかしtypoするとメンドクサイし、自分はIDEの補完に頼って生きていきたい。

そういうことでInsertSQLBuilderと、ついでにSQLSyntaxオブジェクトに対してimplicit conversionを定義します。

object MySQLSyntaxSupport {

  implicit class RichInsertSQLBuilder(val self: InsertSQLBuilder) extends AnyVal {
    def onDuplicateKeyUpdate(columnsAndValues: (SQLSyntax, Any)*): InsertSQLBuilder = {
      val cvs = columnsAndValues map {
        case (c, v) => sqls"$c = $v"
      }
      self.append(sqls"on duplicate key update ${sqls.csv(cvs:_*)}")
    }
  }

  implicit class RichSQLSyntax(val self: sqls.type) extends AnyVal {
    def values(column: SQLSyntax): SQLSyntax = sqls"values($column)"
  }
}

これでこのように書けます。

import MySQLSyntaxSupport._

def upsertUser3(id: String, email: String)(implicit session: DBSession) =
  applyUpdate {
    val c = User.column
    insert.into(User).namedValues(
      c.id -> id,
      c.email -> email
    ).onDuplicateKeyUpdate(
      c.email -> sqls.values(c.email)
    )
  }

まとめ

implicit conversionによってinsertにon duplicate key updateのサポートを追加しました。

同様にselect/update/deleteやwhereなどの後にも文脈に沿ったメソッドを追加することができます。

version

scalaVersion := "2.10.3"

libraryDependencies ++= Seq(
  "org.scalikejdbc" %% "scalikejdbc"               % "1.7.4",
  "org.scalikejdbc" %% "scalikejdbc-interpolation" % "1.7.4",
  "mysql"            % "mysql-connector-java"      % "5.1.28"
)