diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9011206e..dc70c577 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: distribution: 'temurin' java-version: 17 - - run: ./mill -i "__.test" && ./mill -i generateTutorial + generateReference && git diff --exit-code + - run: ./mill -ij1 "__.test" && ./mill -i generateTutorial + generateReference && git diff --exit-code check-scalafix: runs-on: ubuntu-latest @@ -89,7 +89,7 @@ jobs: distribution: 'temurin' java-version: 11 - name: Publish to Maven Central - run: ./mill --import "ivy:com.lihaoyi::mill-contrib-sonatypecentral:" mill.contrib.sonatypecentral.SonatypeCentralPublishModule/publishAll --publishArtifacts __.publishArtifacts + run: ./mill -i mill.scalalib.SonatypeCentralPublishModule/ - name: Create GitHub Release id: create_gh_release diff --git a/.mill-version b/.mill-version deleted file mode 100644 index 54dbed47..00000000 --- a/.mill-version +++ /dev/null @@ -1 +0,0 @@ -0.12.10 diff --git a/build.mill b/build.mill index 592bbc31..1986cd87 100644 --- a/build.mill +++ b/build.mill @@ -1,10 +1,12 @@ +//| mill-version: 1.0.0 +//| repositories: +//| - https://oss.sonatype.org/content/repositories/snapshots +//| mvnDeps: +//| - com.github.lolgab::mill-mima::0.2.0 +//| - com.goyeau::mill-scalafix::0.6.0 package build -import $packages._ -import $file.docs.generateDocs -import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.4.0` -import $ivy.`com.github.lolgab::mill-mima::0.1.1` -import $ivy.`com.goyeau::mill-scalafix::0.5.1` -import de.tobiasroeser.mill.vcs.version.VcsVersion +import build.docs +import mill.util.VcsVersion import com.goyeau.mill.scalafix.ScalafixModule import mill._, scalalib._, publish._ @@ -30,7 +32,7 @@ trait CommonBase extends ScalaModule with PublishModule with ScalafixModule { co ) ) - def scalacOptions = T { + def scalacOptions = Task { Seq("-Wunused:privates,locals,explicits,implicits,params") ++ Option.when(scalaVersion().startsWith("2."))("-Xsource:3") } @@ -42,19 +44,19 @@ trait CommonBase extends ScalaModule with PublishModule with ScalafixModule { co trait CommonTest extends ScalaTests with ScalafixModule { def semanticDbVersion: T[String] = common.semanticDbVersion def scalacOptions = common.scalacOptions - def ivyDeps = Agg( - ivy"com.github.vertical-blank:sql-formatter:2.0.4", - ivy"com.lihaoyi::mainargs:0.4.0", - ivy"com.lihaoyi::os-lib:0.9.1", - ivy"com.lihaoyi::upickle:3.1.3", - ivy"com.lihaoyi::utest:0.8.2", - ivy"com.h2database:h2:2.2.224", - ivy"org.xerial:sqlite-jdbc:3.43.0.0", - ivy"org.testcontainers:postgresql:1.19.1", - ivy"org.postgresql:postgresql:42.6.0", - ivy"org.testcontainers:mysql:1.19.1", - ivy"mysql:mysql-connector-java:8.0.33", - ivy"com.zaxxer:HikariCP:5.1.0" + def mvnDeps = Seq( + mvn"com.github.vertical-blank:sql-formatter:2.0.4", + mvn"com.lihaoyi::mainargs:0.4.0", + mvn"com.lihaoyi::os-lib:0.9.1", + mvn"com.lihaoyi::upickle:4.2.1", + mvn"com.lihaoyi::utest:0.8.2", + mvn"com.h2database:h2:2.2.224", + mvn"org.xerial:sqlite-jdbc:3.43.0.0", + mvn"org.testcontainers:postgresql:1.19.1", + mvn"org.postgresql:postgresql:42.6.0", + mvn"org.testcontainers:mysql:1.19.1", + mvn"mysql:mysql-connector-java:8.0.33", + mvn"com.zaxxer:HikariCP:5.1.0" ) def recordedTestsFile: String @@ -65,7 +67,7 @@ trait CommonBase extends ScalaModule with PublishModule with ScalafixModule { co def forkArgs = Seq("-Duser.timezone=Asia/Singapore") def forkEnv = Map( - "MILL_WORKSPACE_ROOT" -> T.workspace.toString(), + "MILL_WORKSPACE_ROOT" -> mill.api.BuildCtx.workspaceRoot.toString(), "SCALASQL_RECORDED_TESTS_NAME" -> recordedTestsFile, "SCALASQL_RECORDED_SUITE_DESCRIPTIONS_NAME" -> recordedSuiteDescriptionsFile ) @@ -75,10 +77,10 @@ trait Common extends CommonBase with CrossScalaModule object `scalasql-namedtuples` extends CommonBase { def scalaVersion: T[String] = scala3NamedTuples - def millSourcePath: os.Path = scalasql(scala3).millSourcePath / "namedtuples" + def moduleDir: os.Path = scalasql(scala3).moduleDir / "namedtuples" def moduleDeps: Seq[PublishModule] = Seq(scalasql(scala3)) - // override def scalacOptions: Target[Seq[String]] = T { + // override def scalacOptions: Target[Seq[String]] = Task { // super.scalacOptions() :+ "-Xprint:inlining" // } @@ -93,8 +95,8 @@ object `scalasql-namedtuples` extends CommonBase { object scalasql extends Cross[ScalaSql](scalaVersions) trait ScalaSql extends Common { common => def moduleDeps = Seq(query, operations) - def ivyDeps = Agg.empty[Dep] ++ Option.when(scalaVersion().startsWith("2."))( - ivy"org.scala-lang:scala-reflect:${scalaVersion()}" + def mvnDeps = Seq.empty[Dep] ++ Option.when(scalaVersion().startsWith("2."))( + mvn"org.scala-lang:scala-reflect:${scalaVersion()}" ) override def consoleScalacOptions: T[Seq[String]] = Seq("-Xprint:typer") @@ -108,15 +110,15 @@ trait ScalaSql extends Common { common => code.map(_.split("\n").map(" " + _).mkString("\n")).mkString("\n") object core extends Common with CrossValue { - def ivyDeps = Agg( - ivy"com.lihaoyi::geny:1.0.0", - ivy"com.lihaoyi::sourcecode:0.3.1", - ivy"com.lihaoyi::pprint:0.8.1" + def mvnDeps = Seq( + mvn"com.lihaoyi::geny:1.0.0", + mvn"com.lihaoyi::sourcecode:0.3.1", + mvn"com.lihaoyi::pprint:0.8.1" ) ++ Option.when(scalaVersion().startsWith("2."))( - ivy"org.scala-lang:scala-reflect:${scalaVersion()}" + mvn"org.scala-lang:scala-reflect:${scalaVersion()}" ) - def generatedSources = T { + def generatedSources = Task { def commaSep0(i: Int, f: Int => String) = Range.inclusive(1, i).map(f).mkString(", ") val queryableRowDefs = for (i <- Range.inclusive(2, 22)) yield { @@ -135,7 +137,7 @@ trait ScalaSql extends Common { common => } os.write( - T.dest / "Generated.scala", + Task.dest / "Generated.scala", s"""package scalasql.core.generated |import scalasql.core.Queryable |trait QueryableRow{ @@ -143,7 +145,7 @@ trait ScalaSql extends Common { common => |} |""".stripMargin ) - Seq(PathRef(T.dest / "Generated.scala")) + Seq(PathRef(Task.dest / "Generated.scala")) } } @@ -155,7 +157,7 @@ trait ScalaSql extends Common { common => object query extends Common with CrossValue { def moduleDeps = Seq(core) - def generatedSources = T { + def generatedSources = Task { def commaSep0(i: Int, f: Int => String) = Range.inclusive(1, i).map(f).mkString(", ") def defs(isImpl: Boolean) = { @@ -215,7 +217,7 @@ trait ScalaSql extends Common { common => } os.write( - T.dest / "Generated.scala", + Task.dest / "Generated.scala", s"""package scalasql.generated |import scalasql.core.{Queryable, Expr} |import scalasql.query.Column @@ -240,21 +242,21 @@ trait ScalaSql extends Common { common => |} |""".stripMargin ) - Seq(PathRef(T.dest / "Generated.scala")) + Seq(PathRef(Task.dest / "Generated.scala")) } } } val generatedCodeHeader = "[//]: # (GENERATED SOURCES, DO NOT EDIT DIRECTLY)" -def generateTutorial() = T.command { - generateDocs.generateTutorial( - T.workspace / "scalasql" / "test" / "src" / "WorldSqlTests.scala", - T.workspace / "docs" / "tutorial.md" +def generateTutorial() = Task.Command { + docs.generateTutorial( + mill.api.BuildCtx.workspaceRoot / "scalasql" / "test" / "src" / "WorldSqlTests.scala", + mill.api.BuildCtx.workspaceRoot / "docs" / "tutorial.md" ) } -def generateReference() = T.command { - generateDocs.generateReference( - T.workspace / "docs" / "reference.md", +def generateReference() = Task.Command { + docs.generateReference( + mill.api.BuildCtx.workspaceRoot / "docs" / "reference.md", (sources, config) => mill.scalalib.scalafmt.ScalafmtWorkerModule .worker() diff --git a/docs/developer.md b/docs/developer.md index d707661d..159e495b 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -27,7 +27,7 @@ To facilitate efficient testing, you can choose from several commands based on y ### Full Test Suite with Documentation Generation * Re-generating docs: ```bash - ./mill -i "__.test" + generateTutorial + generateReference + ./mill -ij1 "__.test" + generateTutorial + generateReference ``` * Note that ScalaSql's reference docs are extracted from the test suite, and thus we need to make sure to run the test suite before re-generating them. diff --git a/docs/generateDocs.mill b/docs/generateDocs.mill index 4f1b771a..cd7113e5 100644 --- a/docs/generateDocs.mill +++ b/docs/generateDocs.mill @@ -1,4 +1,5 @@ package build.docs +import mill.api.BuildCtx import collection.mutable val generatedCodeHeader = "[//]: # (GENERATED SOURCES, DO NOT EDIT DIRECTLY)" def generateTutorial(sourcePath: os.Path, destPath: os.Path) = { @@ -29,7 +30,7 @@ def generateTutorial(sourcePath: os.Path, destPath: os.Path) = { case (s"// +INCLUDE SNIPPET [$key] $rest", _) => // reuse the iterator each time, // basically assume snippets are requested in order. - val sublines: scala.collection.BufferedIterator[String] = snippets.getOrElseUpdate(rest, os.read.lines(mill.api.WorkspaceRoot.workspaceRoot / os.SubPath(rest)).iterator.buffered) + val sublines: scala.collection.BufferedIterator[String] = snippets.getOrElseUpdate(rest, os.read.lines(BuildCtx.workspaceRoot / os.SubPath(rest)).iterator.buffered) val start = s"// +SNIPPET [$key]" val end = s"// -SNIPPET [$key]" while (sublines.hasNext && !sublines.head.contains(start)) { @@ -45,7 +46,7 @@ def generateTutorial(sourcePath: os.Path, destPath: os.Path) = { outputLines.append("") } case (s"// +INCLUDE $rest", _) => - os.read.lines(mill.api.WorkspaceRoot.workspaceRoot / os.SubPath(rest)).foreach(outputLines.append) + os.read.lines(BuildCtx.workspaceRoot / os.SubPath(rest)).foreach(outputLines.append) case (s"//$rest", false) => outputLines.append(rest.stripPrefix(" ")) @@ -70,16 +71,16 @@ def generateTutorial(sourcePath: os.Path, destPath: os.Path) = { def generateReference(dest: os.Path, scalafmtCallback: (Seq[os.Path], os.Path) => Unit) = { def dropExprPrefix(s: String) = s.split('.').drop(2).mkString(".") def dropNTExprPrefix(s: String) = s.split('.').drop(3).mkString(".") - val records = upickle.default.read[Seq[Record]](os.read.stream(mill.api.WorkspaceRoot.workspaceRoot / "out" / "recordedTests.json")) - val ntRecords = upickle.default.read[Seq[Record]](os.read.stream(mill.api.WorkspaceRoot.workspaceRoot / "out" / "recordedTestsNT.json")) - val suiteDescriptions = upickle.default.read[Map[String, String]](os.read.stream(mill.api.WorkspaceRoot.workspaceRoot / "out" / "recordedSuiteDescriptions.json")) + val records = upickle.default.read[Seq[Record]](os.read.stream(BuildCtx.workspaceRoot / "out" / "recordedTests.json")) + val ntRecords = upickle.default.read[Seq[Record]](os.read.stream(BuildCtx.workspaceRoot / "out" / "recordedTestsNT.json")) + val suiteDescriptions = upickle.default.read[Map[String, String]](os.read.stream(BuildCtx.workspaceRoot / "out" / "recordedSuiteDescriptions.json")) .map{case (k, v) => (dropExprPrefix(k), v)} val rawScalaStrs = (records ++ ntRecords) .flatMap(r => Seq(r.queryCodeString) ++ r.resultCodeString) val formattedScalaStrs = { val tmps = rawScalaStrs.map(os.temp(_, suffix = ".scala")) - scalafmtCallback(tmps, mill.api.WorkspaceRoot.workspaceRoot / ".scalafmt.conf") + scalafmtCallback(tmps, BuildCtx.workspaceRoot / ".scalafmt.conf") tmps.map(os.read(_).trim) } diff --git a/docs/reference.md b/docs/reference.md index 51f024b8..bd22d693 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -20,154 +20,75 @@ ScalaSql should do the right thing for you. >`Table`, with a higher kinded type parameter on a case class. If you are using >`SimpleTable`, then the same code snippets should work by dropping `[Sc]`. -## DbApi -Basic usage of `db.*` operations such as `db.run` -### DbApi.renderSql - -You can use `.renderSql` on the `DbApi` or `DbClient` to see the SQL -that is generated without actually running it - -```scala -dbClient.renderSql(Buyer.select) ==> - "SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0" -``` - - - - - - -### DbApi.run - -Most common usage of `dbClient.transaction`/`db.run` -to run a simple query within a transaction - -```scala -dbClient.transaction { db => - db.run(Buyer.select) ==> List( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) - ) -} -``` - - - - - - -### DbApi.runSql - -`db.runSql` can be used to run `sql"..."` strings, while providing a -specified type that the query results will be deserialized as the specified -type. `db.runSql` supports the all the same data types as `db.run`: -primitives, date and time types, tuples, `Foo[Sc]` `case class`s, and -any combination of these. +## DataTypes +Basic operations on all the data types that ScalaSql supports mapping between Database types and Scala types +### DataTypes.constant -The `sql"..."` string interpolator automatically converts interpolated values -into prepared statement variables, avoidin SQL injection vulnerabilities. You -can also interpolate other `sql"..."` strings, or finally use `SqlStr.raw` for -the rare cases where you want to interpolate a trusted `java.lang.String` into -the `sql"..."` query without escaping. +This example demonstrates a range of different data types being written +and read back via ScalaSQL ```scala -dbClient.transaction { db => - val filterId = 2 - val output = db.runSql[String]( - sql"SELECT name FROM buyer WHERE id = $filterId" - ) - - assert(output == Seq("叉烧包")) - - val output2 = db.runSql[(String, LocalDate)]( - sql"SELECT name, date_of_birth FROM buyer WHERE id = $filterId" - ) - assert( - output2 == - Seq(("叉烧包", LocalDate.parse("1923-11-12"))) - ) +object MyEnum extends Enumeration { + val foo, bar, baz = Value - val output3 = db.runSql[(String, LocalDate, Buyer[Sc])]( - sql"SELECT name, date_of_birth, * FROM buyer WHERE id = $filterId" - ) - assert( - output3 == - Seq( - ( - "叉烧包", - LocalDate.parse("1923-11-12"), - Buyer[Sc]( - id = 2, - name = "叉烧包", - dateOfBirth = LocalDate.parse("1923-11-12") - ) - ) - ) - ) + implicit def make: String => Value = withName } -``` - - - - - - -### DbApi.updateSql +case class DataTypes[T[_]]( + myTinyInt: T[Byte], + mySmallInt: T[Short], + myInt: T[Int], + myBigInt: T[Long], + myDouble: T[Double], + myBoolean: T[Boolean], + myLocalDate: T[LocalDate], + myLocalTime: T[LocalTime], + myLocalDateTime: T[LocalDateTime], + myUtilDate: T[Date], + myInstant: T[Instant], + myVarBinary: T[geny.Bytes], + myUUID: T[java.util.UUID], + myEnum: T[MyEnum.Value] +) -Similar to `db.runQuery`, `db.runUpdate` allows you to pass in a `SqlStr`, but runs -an update rather than a query and expects to receive a single number back from the -database indicating the number of rows inserted or updated +object DataTypes extends Table[DataTypes] -```scala -dbClient.transaction { db => - val newName = "Moo Moo Cow" - val newDateOfBirth = LocalDate.parse("2000-01-01") - val count = db - .updateSql( - sql"INSERT INTO buyer (name, date_of_birth) VALUES($newName, $newDateOfBirth)" - ) - assert(count == 1) +val value = DataTypes[Sc]( + myTinyInt = 123.toByte, + mySmallInt = 12345.toShort, + myInt = 12345678, + myBigInt = 12345678901L, + myDouble = 3.14, + myBoolean = true, + myLocalDate = LocalDate.parse("2023-12-20"), + myLocalTime = LocalTime.parse("10:15:30"), + myLocalDateTime = LocalDateTime.parse("2011-12-03T10:15:30"), + myUtilDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2011-12-03T10:15:30.000"), + myInstant = Instant.parse("2011-12-03T10:15:30Z"), + myVarBinary = new geny.Bytes(Array[Byte](1, 2, 3, 4, 5, 6, 7, 8)), + myUUID = new java.util.UUID(1234567890L, 9876543210L), + myEnum = MyEnum.bar +) - db.run(Buyer.select) ==> List( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01")) +db.run( + DataTypes.insert.columns( + _.myTinyInt := value.myTinyInt, + _.mySmallInt := value.mySmallInt, + _.myInt := value.myInt, + _.myBigInt := value.myBigInt, + _.myDouble := value.myDouble, + _.myBoolean := value.myBoolean, + _.myLocalDate := value.myLocalDate, + _.myLocalTime := value.myLocalTime, + _.myLocalDateTime := value.myLocalDateTime, + _.myUtilDate := value.myUtilDate, + _.myInstant := value.myInstant, + _.myVarBinary := value.myVarBinary, + _.myUUID := value.myUUID, + _.myEnum := value.myEnum ) -} -``` - - - - - - -### DbApi.updateGetGeneratedKeysSql - -Allows you to fetch the primary keys that were auto-generated for an INSERT -defined as a `SqlStr`. -Note: not supported by Sqlite https://github.com/xerial/sqlite-jdbc/issues/980 - -```scala -dbClient.transaction { db => - val newName = "Moo Moo Cow" - val newDateOfBirth = LocalDate.parse("2000-01-01") - val generatedIds = db - .updateGetGeneratedKeysSql[Int]( - sql"INSERT INTO buyer (name, date_of_birth) VALUES ($newName, $newDateOfBirth), ($newName, $newDateOfBirth)" - ) - - assert(generatedIds == Seq(4, 5)) +) ==> 1 - db.run(Buyer.select) ==> List( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01")), - Buyer[Sc](5, "Moo Moo Cow", LocalDate.parse("2000-01-01")) - ) -} +db.run(DataTypes.select) ==> Seq(value) ``` @@ -175,43 +96,39 @@ dbClient.transaction { db => -### DbApi.runRaw +### DataTypes.nonRoundTrip -`runRawQuery` is similar to `runQuery` but allows you to pass in the SQL strings -"raw", along with `?` placeholders and interpolated variables passed separately. +In general, databases do not store timezones and offsets together with their timestamps: +"TIMESTAMP WITH TIMEZONE" is a lie and it actually stores UTC and renders to whatever +timezone the client queries it from. Thus values of type `OffsetDateTime` can preserve +their instant, but cannot be round-tripped preserving the offset. ```scala -dbClient.transaction { db => - val output = db.runRaw[String]("SELECT name FROM buyer WHERE id = ?", Seq(2)) - assert(output == Seq("叉烧包")) -} -``` - - - - +case class NonRoundTripTypes[T[_]]( + myZonedDateTime: T[ZonedDateTime], + myOffsetDateTime: T[OffsetDateTime] +) +object NonRoundTripTypes extends Table[NonRoundTripTypes] -### DbApi.updateRaw +val value = NonRoundTripTypes[Sc]( + myZonedDateTime = ZonedDateTime.parse("2011-12-03T10:15:30+01:00[Europe/Paris]"), + myOffsetDateTime = OffsetDateTime.parse("2011-12-03T10:15:30+00:00") +) -`runRawUpdate` is similar to `runRawQuery`, but for update queries that -return a single number +def normalize(v: NonRoundTripTypes[Sc]) = v.copy[Sc]( + myZonedDateTime = v.myZonedDateTime.withZoneSameInstant(ZoneId.systemDefault), + myOffsetDateTime = v.myOffsetDateTime.withOffsetSameInstant(OffsetDateTime.now.getOffset) +) -```scala -dbClient.transaction { db => - val count = db.updateRaw( - "INSERT INTO buyer (name, date_of_birth) VALUES(?, ?)", - Seq("Moo Moo Cow", LocalDate.parse("2000-01-01")) +db.run( + NonRoundTripTypes.insert.columns( + _.myOffsetDateTime := value.myOffsetDateTime, + _.myZonedDateTime := value.myZonedDateTime ) - assert(count == 1) +) ==> 1 - db.run(Buyer.select) ==> List( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01")) - ) -} +db.run(NonRoundTripTypes.select).map(normalize) ==> Seq(normalize(value)) ``` @@ -219,12160 +136,1144 @@ dbClient.transaction { db => -### DbApi.updateGetGeneratedKeysRaw +### DataTypes.enclosing -Allows you to fetch the primary keys that were auto-generated for an INSERT -defined using a raw `java.lang.String` and variables. -Note: not supported by Sqlite https://github.com/xerial/sqlite-jdbc/issues/980 +You can nest `case class`es in other `case class`es to DRY up common sets of +table columns. These nested `case class`es have their columns flattened out +into the enclosing `case class`'s columns, such that at the SQL level it is +all flattened out without nesting. ```scala -dbClient.transaction { db => - val generatedKeys = db.updateGetGeneratedKeysRaw[Int]( - "INSERT INTO buyer (name, date_of_birth) VALUES (?, ?), (?, ?)", - Seq( - "Moo Moo Cow", - LocalDate.parse("2000-01-01"), - "Moo Moo Cow", - LocalDate.parse("2000-01-01") - ) +// case class Nested[T[_]]( +// fooId: T[Int], +// myBoolean: T[Boolean], +// ) +// object Nested extends Table[Nested] +// +// case class Enclosing[T[_]]( +// barId: T[Int], +// myString: T[String], +// foo: Nested[T] +// ) +// object Enclosing extends Table[Enclosing] +val value1 = Enclosing[Sc]( + barId = 1337, + myString = "hello", + foo = Nested[Sc]( + fooId = 271828, + myBoolean = true ) - assert(generatedKeys == Seq(4, 5)) - - db.run(Buyer.select) ==> List( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01")), - Buyer[Sc](5, "Moo Moo Cow", LocalDate.parse("2000-01-01")) +) +val value2 = Enclosing[Sc]( + barId = 31337, + myString = "world", + foo = Nested[Sc]( + fooId = 1618, + myBoolean = false ) -} -``` - - - - - - -### DbApi.stream - -`db.stream` can be run on queries that return `Seq[T]`s, and makes them -return `geny.Generator[T]`s instead. This allows you to deserialize and -process the returned database rows incrementally without buffering the -entire `Seq[T]` in memory. Not that the underlying JDBC driver and the -underlying database may each perform their own buffering depending on -their implementation - -```scala -dbClient.transaction { db => - val output = collection.mutable.Buffer.empty[String] - - db.stream(Buyer.select).generate { buyer => - output.append(buyer.name) - if (buyer.id >= 2) Generator.End else Generator.Continue - } - - output ==> List("James Bond", "叉烧包") -} -``` - - - - - - -### DbApi.streamSql - -`.streamSql` provides a lower level interface to `.stream`, allowing you to pass -in a `SqlStr` of the form `sql"..."`, while allowing you to process the returned rows -in a streaming fashion without. - -```scala -dbClient.transaction { db => - val excluded = "James Bond" - val output = db - .streamSql[Buyer[Sc]](sql"SELECT * FROM buyer where name != $excluded") - .takeWhile(_.id <= 2) - .map(_.name) - .toList - - output ==> List("叉烧包") -} -``` - - - - - - -### DbApi.streamRaw - -`.streamRaw` provides a lowest level interface to `.stream`, allowing you to pass -in a `java.lang.String` and a `Seq[Any]` representing the interpolated prepared -statement variables - -```scala -dbClient.transaction { db => - val excluded = "James Bond" - val output = db - .streamRaw[Buyer[Sc]]("SELECT * FROM buyer WHERE buyer.name <> ?", Seq(excluded)) - .takeWhile(_.id <= 2) - .map(_.name) - .toList - - output ==> List("叉烧包") -} -``` - - - - - - -## Transaction -Usage of transactions, rollbacks, and savepoints -### Transaction.simple.commit - -Common workflow to create a transaction and run a `delete` inside of it. The effect -of the `delete` is visible both inside the transaction and outside after the -transaction completes successfully and commits - -```scala -dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_ => true)) ==> 7 - - db.run(Purchase.select.size) ==> 0 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 0 -``` - - - - +) +val insertColumns = Enclosing.insert.columns( + _.barId := value1.barId, + _.myString := value1.myString, + _.foo.fooId := value1.foo.fooId, + _.foo.myBoolean := value1.foo.myBoolean +) +db.renderSql(insertColumns) ==> + "INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)" -### Transaction.simple.isolation +db.run(insertColumns) ==> 1 -You can use `.updateRaw` to perform `SET TRANSACTION ISOLATION LEVEL` commands, -allowing you to configure the isolation and performance characteristics of -concurrent transactions in your database +val insertValues = Enclosing.insert.values(value2) +db.renderSql(insertValues) ==> + "INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)" -```scala -dbClient.transaction { implicit db => - db.updateRaw("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE") +db.run(insertValues) ==> 1 - db.run(Purchase.select.size) ==> 7 +db.renderSql(Enclosing.select) ==> """ + SELECT + enclosing0.bar_id AS bar_id, + enclosing0.my_string AS my_string, + enclosing0.foo_id AS foo_id, + enclosing0.my_boolean AS my_boolean + FROM enclosing enclosing0 + """ - db.run(Purchase.delete(_ => true)) ==> 7 - - db.run(Purchase.select.size) ==> 0 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 0 -``` - - - - - - -### Transaction.simple.rollback - -Example of explicitly rolling back a transaction using the `db.rollback()` method. -After rollback, the effects of the `delete` query are undone, and subsequent `select` -queries can see the previously-deleted entries both inside and outside the transaction - -```scala -dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_ => true)) ==> 7 - - db.run(Purchase.select.size) ==> 0 - - db.rollback() - - db.run(Purchase.select.size) ==> 7 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 7 -``` - - - - - - -### Transaction.simple.throw - -Transactions are also rolled back if they terminate with an uncaught exception - -```scala -try { - dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_ => true)) ==> 7 - - db.run(Purchase.select.size) ==> 0 - - throw new FooException - } -} catch { - case e: FooException => /*donothing*/ -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 7 -``` - - - - - - -### Transaction.savepoint.commit - -Savepoints are like "sub" transactions: they let you declare a savepoint -and roll back any changes to the savepoint later. If a savepoint block -completes successfully, the savepoint changes are committed ("released") -and remain visible later in the transaction and outside of it - -```scala -dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_.id <= 3)) ==> 3 - db.run(Purchase.select.size) ==> 4 - - db.savepoint { _ => - db.run(Purchase.delete(_ => true)) ==> 4 - db.run(Purchase.select.size) ==> 0 - } - - db.run(Purchase.select.size) ==> 0 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 0 -``` - - - - - - -### Transaction.savepoint.rollback - -Like transactions, savepoints support the `.rollback()` method, to undo any -changes since the start of the savepoint block. - -```scala -dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_.id <= 3)) ==> 3 - db.run(Purchase.select.size) ==> 4 - - db.savepoint { sp => - db.run(Purchase.delete(_ => true)) ==> 4 - db.run(Purchase.select.size) ==> 0 - sp.rollback() - db.run(Purchase.select.size) ==> 4 - } - - db.run(Purchase.select.size) ==> 4 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 4 -``` - - - - - - -### Transaction.savepoint.throw - -Savepoints also roll back their enclosed changes automatically if they -terminate with an uncaught exception - -```scala -dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_.id <= 3)) ==> 3 - db.run(Purchase.select.size) ==> 4 - - try { - db.savepoint { _ => - db.run(Purchase.delete(_ => true)) ==> 4 - db.run(Purchase.select.size) ==> 0 - throw new FooException - } - } catch { - case e: FooException => /*donothing*/ - } - - db.run(Purchase.select.size) ==> 4 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 4 -``` - - - - - - -### Transaction.doubleSavepoint.commit - -Only one transaction can be present at a time, but savepoints can be arbitrarily nested. -Uncaught exceptions or explicit `.rollback()` calls would roll back changes done during -the inner savepoint/transaction blocks, while leaving changes applied during outer -savepoint/transaction blocks in-place - -```scala -dbClient.transaction { implicit db => - db.run(Purchase.select.size) ==> 7 - - db.run(Purchase.delete(_.id <= 2)) ==> 2 - db.run(Purchase.select.size) ==> 5 - - db.savepoint { _ => - db.run(Purchase.delete(_.id <= 4)) ==> 2 - db.run(Purchase.select.size) ==> 3 - - db.savepoint { _ => - db.run(Purchase.delete(_.id <= 6)) ==> 2 - db.run(Purchase.select.size) ==> 1 - } - - db.run(Purchase.select.size) ==> 1 - } - - db.run(Purchase.select.size) ==> 1 -} - -dbClient.transaction(_.run(Purchase.select.size)) ==> 1 -``` - - - - - - -## Select -Basic `SELECT` operations: map, filter, join, etc. -### Select.constant - -The most simple thing you can query in the database is an `Expr`. These do not need -to be related to any database tables, and translate into raw `SELECT` calls without -`FROM`. - -```scala -Expr(1) + Expr(2) -``` - - -* - ```sql - SELECT (? + ?) AS res - ``` - - - -* - ```scala - 3 - ``` - - - -### Select.table - -You can list the contents of a table via the query `Table.select`. It returns a -`Seq[CaseClass[Sc]]` with the entire contents of the table. Note that listing -entire tables can be prohibitively expensive on real-world databases, and you -should generally use `filter`s as shown below - -```scala -Buyer.select -``` - - -* - ```sql - SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq( - Buyer[Sc](id = 1, name = "James Bond", dateOfBirth = LocalDate.parse("2001-02-03")), - Buyer[Sc](id = 2, name = "叉烧包", dateOfBirth = LocalDate.parse("1923-11-12")), - Buyer[Sc](id = 3, name = "Li Haoyi", dateOfBirth = LocalDate.parse("1965-08-09")) - ) - ``` - - - -### Select.filter.single - -ScalaSql's `.filter` translates to SQL `WHERE`, in this case we -are searching for rows with a particular `buyerId` - -```scala -ShippingInfo.select.filter(_.buyerId `=` 2) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - WHERE (shipping_info0.buyer_id = ?) - ``` - - - -* - ```scala - Seq( - ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), - ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### Select.filter.multiple - -You can stack multiple `.filter`s on a query. - -```scala -ShippingInfo.select - .filter(_.buyerId `=` 2) - .filter(_.shippingDate `=` LocalDate.parse("2012-05-06")) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - WHERE (shipping_info0.buyer_id = ?) AND (shipping_info0.shipping_date = ?) - ``` - - - -* - ```scala - Seq(ShippingInfo[Sc](id = 3, buyerId = 2, shippingDate = LocalDate.parse("2012-05-06"))) - ``` - - - -### Select.filter.dotSingle.pass - -Queries that you expect to return a single row can be annotated with `.single`. -This changes the return type of the `.select` from `Seq[T]` to just `T`, and throws -an exception if zero or multiple rows were returned - -```scala -ShippingInfo.select - .filter(_.buyerId `=` 2) - .filter(_.shippingDate `=` LocalDate.parse("2012-05-06")) - .single -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - WHERE (shipping_info0.buyer_id = ?) AND (shipping_info0.shipping_date = ?) - ``` - - - -* - ```scala - ShippingInfo[Sc](id = 3, buyerId = 2, shippingDate = LocalDate.parse("2012-05-06")) - ``` - - - -### Select.filter.combined - -You can perform multiple checks in a single filter using `&&` - -```scala -ShippingInfo.select - .filter(p => p.buyerId `=` 2 && p.shippingDate `=` LocalDate.parse("2012-05-06")) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - WHERE ((shipping_info0.buyer_id = ?) AND (shipping_info0.shipping_date = ?)) - ``` - - - -* - ```scala - Seq(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) - ``` - - - -### Select.filterIf.filter not added - - - -```scala -ShippingInfo.select.filterIf(false)(_.buyerId `=` 2) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - ``` - - - -* - ```scala - Seq( - ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), - ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05")), - ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### Select.filterIf.filter added - - - -```scala -ShippingInfo.select.filterIf(true)(_.buyerId `=` 2) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - WHERE (shipping_info0.buyer_id = ?) - ``` - - - -* - ```scala - Seq( - ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), - ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### Select.filterOpt.filter not added - - - -```scala -ShippingInfo.select.filterOpt[Int](None)((table, value) => table.buyerId `=` value) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - ``` - - - -* - ```scala - Seq( - ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), - ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05")), - ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### Select.filterOpt.filter added - - - -```scala -ShippingInfo.select.filterOpt(Some(2))((table, value) => table.buyerId `=` value) -``` - - -* - ```sql - SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - WHERE (shipping_info0.buyer_id = ?) - ``` - - - -* - ```scala - Seq( - ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), - ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### Select.map.single - -`.map` allows you to select exactly what you want to return from -a query, rather than returning the entire row. Here, we return -only the `name`s of the `Buyer`s - -```scala -Buyer.select.map(_.name) -``` - - -* - ```sql - SELECT buyer0.name AS res FROM buyer buyer0 - ``` - - - -* - ```scala - Seq("James Bond", "叉烧包", "Li Haoyi") - ``` - - - -### Select.map.filterMap - -The common use case of `SELECT FROM WHERE` can be achieved via `.select.filter.map` in ScalaSql - -```scala -Product.select.filter(_.price < 100).map(_.name) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 WHERE (product0.price < ?) - ``` - - - -* - ```scala - Seq("Face Mask", "Socks", "Cookie") - ``` - - - -### Select.map.tuple2 - -You can return multiple values from your `.map` by returning a tuple in your query, -which translates into a `Seq[Tuple]` being returned when the query is run - -```scala -Buyer.select.map(c => (c.name, c.id)) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, buyer0.id AS res_1 FROM buyer buyer0 - ``` - - - -* - ```scala - Seq(("James Bond", 1), ("叉烧包", 2), ("Li Haoyi", 3)) - ``` - - - -### Select.map.tuple3 - - - -```scala -Buyer.select.map(c => (c.name, c.id, c.dateOfBirth)) -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - buyer0.id AS res_1, - buyer0.date_of_birth AS res_2 - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq( - ("James Bond", 1, LocalDate.parse("2001-02-03")), - ("叉烧包", 2, LocalDate.parse("1923-11-12")), - ("Li Haoyi", 3, LocalDate.parse("1965-08-09")) - ) - ``` - - - -### Select.map.interpolateInMap - -You can perform operations inside the `.map` to change what you return - -```scala -Product.select.map(_.price * 2) -``` - - -* - ```sql - SELECT (product0.price * ?) AS res FROM product product0 - ``` - - - -* - ```scala - Seq(17.76, 600, 6.28, 246.9, 2000.0, 0.2) - ``` - - - -### Select.map.heterogenousTuple - -`.map` can return any combination of tuples, `case class`es, and primitives, -arbitrarily nested. here we return a tuple of `(Int, Buyer[Sc])` - -```scala -Buyer.select.map(c => (c.id, c)) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0, - buyer0.id AS res_1_id, - buyer0.name AS res_1_name, - buyer0.date_of_birth AS res_1_date_of_birth - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq( - (1, Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03"))), - (2, Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12"))), - (3, Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))) - ) - ``` - - - -### Select.toExpr - -`SELECT` queries that return a single row and column can be used as SQL expressions -in standard SQL databases. In ScalaSql, this is done by the `.toExpr` method, -which turns a `Select[T]` into an `Expr[T]`. Note that if the `Select` returns more -than one row or column, the database may select a row arbitrarily or will throw -an exception at runtime (depend on implenmentation) - -```scala -Product.select.map(p => - ( - p.name, - Purchase.select - .filter(_.productId === p.id) - .sortBy(_.total) - .desc - .take(1) - .map(_.total) - .toExpr - ) -) -``` - - -* - ```sql - SELECT - product0.name AS res_0, - (SELECT purchase1.total AS res - FROM purchase purchase1 - WHERE (purchase1.product_id = product0.id) - ORDER BY res DESC - LIMIT ?) AS res_1 - FROM product product0 - ``` - - - -* - ```scala - Seq( - ("Face Mask", 888.0), - ("Guitar", 900.0), - ("Socks", 15.7), - ("Skate Board", 493.8), - ("Camera", 10000.0), - ("Cookie", 1.3) - ) - ``` - - - -### Select.subquery - -ScalaSql generally combines operations like `.map` and `.filter` to minimize the -number of subqueries to keep the generated SQL readable. If you explicitly want -a subquery for some reason (e.g. to influence the database query planner), you can -use the `.subquery` to force a query to be translated into a standalone subquery - -```scala -Buyer.select.subquery.map(_.name) -``` - - -* - ```sql - SELECT subquery0.name AS res - FROM (SELECT buyer0.name AS name FROM buyer buyer0) subquery0 - ``` - - - -* - ```scala - Seq("James Bond", "叉烧包", "Li Haoyi") - ``` - - - -### Select.aggregate.single - -You can use methods like `.sumBy` or `.sum` on your queries to generate -SQL `SUM(...)` aggregates - -```scala -Purchase.select.sumBy(_.total) -``` - - -* - ```sql - SELECT SUM(purchase0.total) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 12343.2 - ``` - - - -### Select.aggregate.multiple - -If you want to perform multiple aggregates at once, you can use the `.aggregate` method -which takes a function allowing you to call multiple aggregates inside of it - -```scala -Purchase.select.aggregate(q => (q.sumBy(_.total), q.maxBy(_.total))) -``` - - -* - ```sql - SELECT SUM(purchase0.total) AS res_0, MAX(purchase0.total) AS res_1 FROM purchase purchase0 - ``` - - - -* - ```scala - (12343.2, 10000.0) - ``` - - - -### Select.groupBy.simple - -ScalaSql's `.groupBy` method translates into a SQL `GROUP BY`. Unlike the normal -`.groupBy` provided by `scala.Seq`, ScalaSql's `.groupBy` requires you to pass -an aggregate as a second parameter, mirroring the SQL requirement that any -column not part of the `GROUP BY` clause has to be in an aggregate. - -```scala -Purchase.select.groupBy(_.productId)(_.sumBy(_.total)) -``` - - -* - ```sql - SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 - FROM purchase purchase0 - GROUP BY purchase0.product_id - ``` - - - -* - ```scala - Seq((1, 932.4), (2, 900.0), (3, 15.7), (4, 493.8), (5, 10000.0), (6, 1.30)) - ``` - - - -### Select.groupBy.having - -`.filter` calls following a `.groupBy` are automatically translated to SQL `HAVING` clauses - -```scala -Purchase.select.groupBy(_.productId)(_.sumBy(_.total)).filter(_._2 > 100).filter(_._1 > 1) -``` - - -* - ```sql - SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 - FROM purchase purchase0 - GROUP BY purchase0.product_id - HAVING (SUM(purchase0.total) > ?) AND (purchase0.product_id > ?) - ``` - - - -* - ```scala - Seq((2, 900.0), (4, 493.8), (5, 10000.0)) - ``` - - - -### Select.groupBy.filterHaving - - - -```scala -Purchase.select - .filter(_.count > 5) - .groupBy(_.productId)(_.sumBy(_.total)) - .filter(_._2 > 100) -``` - - -* - ```sql - SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 - FROM purchase purchase0 - WHERE (purchase0.count > ?) - GROUP BY purchase0.product_id - HAVING (SUM(purchase0.total) > ?) - ``` - - - -* - ```scala - Seq((1, 888.0), (5, 10000.0)) - ``` - - - -### Select.groupBy.multipleKeys - - - -```scala -Purchase.select.groupBy(x => (x.shippingInfoId, x.productId))(_.sumBy(_.total)) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0_0, - purchase0.product_id AS res_0_1, - SUM(purchase0.total) AS res_1 - FROM - purchase purchase0 - GROUP BY - purchase0.shipping_info_id, - purchase0.product_id - ``` - - - -* - ```scala - Seq( - ((1, 1), 888.0), - ((1, 2), 900.0), - ((1, 3), 15.7), - ((2, 4), 493.8), - ((2, 5), 10000.0), - ((3, 1), 44.4), - ((3, 6), 1.3) - ) - ``` - - - -### Select.groupBy.multipleKeysHaving - - - -```scala -Purchase.select - .groupBy(x => (x.shippingInfoId, x.productId))(_.sumBy(_.total)) - .filter(_._2 > 10) - .filter(_._2 < 100) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0_0, - purchase0.product_id AS res_0_1, - SUM(purchase0.total) AS res_1 - FROM - purchase purchase0 - GROUP BY - purchase0.shipping_info_id, - purchase0.product_id - HAVING - (SUM(purchase0.total) > ?) AND (SUM(purchase0.total) < ?) - ``` - - - -* - ```scala - Seq(((1, 3), 15.7), ((3, 1), 44.4)) - ``` - - - -### Select.distinct.nondistinct - -Normal queries can allow duplicates in the returned row values, as seen below. -You can use the `.distinct` operator (translates to SQl's `SELECT DISTINCT`) -to eliminate those duplicates - -```scala -Purchase.select.map(_.shippingInfoId) -``` - - -* - ```sql - SELECT purchase0.shipping_info_id AS res FROM purchase purchase0 - ``` - - - -* - ```scala - Seq(1, 1, 1, 2, 2, 3, 3) - ``` - - - -### Select.distinct.distinct - - - -```scala -Purchase.select.map(_.shippingInfoId).distinct -``` - - -* - ```sql - SELECT DISTINCT purchase0.shipping_info_id AS res FROM purchase purchase0 - ``` - - - -* - ```scala - Seq(1, 2, 3) - ``` - - - -### Select.distinct.subquery - -Columns inside nested subqueries cannot be elided when `SELECT DISTINCT` is used - -```scala -ShippingInfo.select.distinct.subquery.map(_.buyerId) -``` - - -* - ```sql - SELECT subquery0.buyer_id AS res - FROM (SELECT DISTINCT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0) subquery0 - ``` - - - -* - ```scala - Seq(1, 2, 2) - ``` - - - -### Select.contains - -ScalaSql's `.contains` method translates into SQL's `IN` syntax, e.g. here checking if a -subquery contains a column as part of a `WHERE` clause - -```scala -Buyer.select.filter(b => ShippingInfo.select.map(_.buyerId).contains(b.id)) -``` - - -* - ```sql - SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - WHERE (buyer0.id IN (SELECT shipping_info1.buyer_id AS res FROM shipping_info shipping_info1)) - ``` - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -### Select.containsMultiple - -ScalaSql's `.contains` can take a compound Scala value, which translates into - SQL's `IN` syntax on a tuple with multiple columns. e.g. this query uses that ability - to find the `Buyer` which has a shipment on a specific date, as an alternative - to doing a `JOIN`. - -```scala -Buyer.select.filter(b => - ShippingInfo.select - .map(s => (s.buyerId, s.shippingDate)) - .contains((b.id, LocalDate.parse("2010-02-03"))) -) -``` - - -* - ```sql - SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - WHERE ((buyer0.id, ?) IN (SELECT - shipping_info1.buyer_id AS res_0, - shipping_info1.shipping_date AS res_1 - FROM shipping_info shipping_info1)) - ``` - - - -* - ```scala - Seq( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -### Select.nonEmpty - -ScalaSql's `.nonEmpty` and `.isEmpty` translates to SQL's `EXISTS` and `NOT EXISTS` syntax - -```scala -Buyer.select - .map(b => (b.name, ShippingInfo.select.filter(_.buyerId `=` b.id).map(_.id).nonEmpty)) -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - (EXISTS (SELECT - shipping_info1.id AS res - FROM shipping_info shipping_info1 - WHERE (shipping_info1.buyer_id = buyer0.id))) AS res_1 - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq(("James Bond", true), ("叉烧包", true), ("Li Haoyi", false)) - ``` - - - -### Select.isEmpty - - - -```scala -Buyer.select - .map(b => (b.name, ShippingInfo.select.filter(_.buyerId `=` b.id).map(_.id).isEmpty)) -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - (NOT EXISTS (SELECT - shipping_info1.id AS res - FROM shipping_info shipping_info1 - WHERE (shipping_info1.buyer_id = buyer0.id))) AS res_1 - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq(("James Bond", false), ("叉烧包", false), ("Li Haoyi", true)) - ``` - - - -### Select.nestedTuples - -Queries can output arbitrarily nested tuples of `Expr[T]` and `case class` -instances of `Foo[Expr]`, which will be de-serialized into nested tuples -of `T` and `Foo[Sc]`s. The `AS` aliases assigned to each column will contain -the path of indices and field names used to populate the final returned values - -```scala -Buyer.select - .join(ShippingInfo)(_.id === _.buyerId) - .sortBy(_._1.id) - .map { case (b, s) => (b.id, (b, (s.id, s))) } -``` - - -* - ```sql - SELECT - buyer0.id AS res_0, - buyer0.id AS res_1_0_id, - buyer0.name AS res_1_0_name, - buyer0.date_of_birth AS res_1_0_date_of_birth, - shipping_info1.id AS res_1_1_0, - shipping_info1.id AS res_1_1_1_id, - shipping_info1.buyer_id AS res_1_1_1_buyer_id, - shipping_info1.shipping_date AS res_1_1_1_shipping_date - FROM buyer buyer0 - JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ORDER BY res_1_0_id - ``` - - - -* - ```scala - Seq[(Int, (Buyer[Sc], (Int, ShippingInfo[Sc])))]( - ( - 1, - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - (2, ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) - ) - ), - ( - 2, - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - (1, ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) - ) - ), - ( - 2, - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - (3, ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) - ) - ) - ) - ``` - - - -### Select.case.when - -ScalaSql's `caseWhen` method translates into SQL's `CASE`/`WHEN`/`ELSE`/`END` syntax, -allowing you to perform basic conditionals as part of your SQL query - -```scala -Product.select.map(p => - db.caseWhen( - (p.price > 200) -> (p.name + " EXPENSIVE"), - (p.price > 5) -> (p.name + " NORMAL"), - (p.price <= 5) -> (p.name + " CHEAP") - ) -) -``` - - -* - ```sql - SELECT - CASE - WHEN (product0.price > ?) THEN (product0.name || ?) - WHEN (product0.price > ?) THEN (product0.name || ?) - WHEN (product0.price <= ?) THEN (product0.name || ?) - END AS res - FROM product product0 - ``` - - - -* - ```scala - Seq( - "Face Mask NORMAL", - "Guitar EXPENSIVE", - "Socks CHEAP", - "Skate Board NORMAL", - "Camera EXPENSIVE", - "Cookie CHEAP" - ) - ``` - - - -### Select.case.else - - - -```scala -Product.select.map(p => - db.caseWhen( - (p.price > 200) -> (p.name + " EXPENSIVE"), - (p.price > 5) -> (p.name + " NORMAL") - ).`else` { p.name + " UNKNOWN" } -) -``` - - -* - ```sql - SELECT - CASE - WHEN (product0.price > ?) THEN (product0.name || ?) - WHEN (product0.price > ?) THEN (product0.name || ?) - ELSE (product0.name || ?) - END AS res - FROM product product0 - ``` - - - -* - ```scala - Seq( - "Face Mask NORMAL", - "Guitar EXPENSIVE", - "Socks UNKNOWN", - "Skate Board NORMAL", - "Camera EXPENSIVE", - "Cookie UNKNOWN" - ) - ``` - - - -## Join -inner `JOIN`s, `JOIN ON`s, self-joins, `LEFT`/`RIGHT`/`OUTER` `JOIN`s -### Join.joinFilter - -ScalaSql's `.join` or `.join` methods correspond to SQL `JOIN` and `JOIN ... ON ...`. -These perform an inner join between two tables, with an optional `ON` predicate. You can -also `.filter` and `.map` the results of the join, making use of the columns joined from -the two tables - -```scala -Buyer.select.join(ShippingInfo)(_.id `=` _.buyerId).filter(_._1.name `=` "叉烧包") -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - shipping_info1.id AS res_1_id, - shipping_info1.buyer_id AS res_1_buyer_id, - shipping_info1.shipping_date AS res_1_shipping_date - FROM buyer buyer0 - JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - WHERE (buyer0.name = ?) - ``` - - - -* - ```scala - Seq( - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) - ) - ) - ``` - - - -### Join.joinFilterMap - - - -```scala -Buyer.select - .join(ShippingInfo)(_.id `=` _.buyerId) - .filter(_._1.name `=` "James Bond") - .map(_._2.shippingDate) -``` - - -* - ```sql - SELECT shipping_info1.shipping_date AS res - FROM buyer buyer0 - JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - WHERE (buyer0.name = ?) - ``` - - - -* - ```scala - Seq(LocalDate.parse("2012-04-05")) - ``` - - - -### Join.selfJoin - -ScalaSql supports a "self join", where a table is joined with itself. This -is done by simply having the same table be on the left-hand-side and right-hand-side -of your `.join` or `.join` method. The two example self-joins below are trivial, -but illustrate how to do it in case you want to do a self-join in a more realistic setting. - -```scala -Buyer.select.join(Buyer)(_.id `=` _.id) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - buyer1.id AS res_1_id, - buyer1.name AS res_1_name, - buyer1.date_of_birth AS res_1_date_of_birth - FROM buyer buyer0 - JOIN buyer buyer1 ON (buyer0.id = buyer1.id) - ``` - - - -* - ```scala - Seq( - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ), - ( - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) - ) - ) - ``` - - - -### Join.selfJoin2 - - - -```scala -Buyer.select.join(Buyer)(_.id <> _.id) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - buyer1.id AS res_1_id, - buyer1.name AS res_1_name, - buyer1.date_of_birth AS res_1_date_of_birth - FROM buyer buyer0 - JOIN buyer buyer1 ON (buyer0.id <> buyer1.id) - ``` - - - -* - ```scala - Seq( - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ), - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) - ), - ( - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) - ), - ( - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ) - ``` - - - -### Join.mapForGroupBy - -Using non-trivial queries in the `for`-comprehension may result in subqueries -being generated - -```scala -for ((name, dateOfBirth) <- Buyer.select.groupBy(_.name)(_.minBy(_.dateOfBirth))) - yield (name, dateOfBirth) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, MIN(buyer0.date_of_birth) AS res_1 - FROM buyer buyer0 - GROUP BY buyer0.name - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2001-02-03")), - ("Li Haoyi", LocalDate.parse("1965-08-09")), - ("叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -### Join.leftJoin - -ScalaSql supports `LEFT JOIN`s, `RIGHT JOIN`s and `OUTER JOIN`s via the -`.leftJoin`/`.rightJoin`/`.outerJoin` methods - -```scala -Buyer.select.leftJoin(ShippingInfo)(_.id `=` _.buyerId) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - shipping_info1.id AS res_1_id, - shipping_info1.buyer_id AS res_1_buyer_id, - shipping_info1.shipping_date AS res_1_shipping_date - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq( - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) - ), - (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), None) - ) - ``` - - - -### Join.leftJoinMap - -`.leftJoin`s return a `JoinNullable[Q]` for the right hand entry. This is similar -to `Option[Q]` in Scala, supports a similar set of operations (e.g. `.map`), -and becomes an `Option[Q]` after the query is executed - -```scala -Buyer.select - .leftJoin(ShippingInfo)(_.id `=` _.buyerId) - .map { case (b, si) => (b.name, si.map(_.shippingDate)) } -``` - - -* - ```sql - SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq( - ("James Bond", Some(LocalDate.parse("2012-04-05"))), - ("Li Haoyi", None), - ("叉烧包", Some(LocalDate.parse("2010-02-03"))), - ("叉烧包", Some(LocalDate.parse("2012-05-06"))) - ) - ``` - - - -### Join.leftJoinMap2 - - - -```scala -Buyer.select - .leftJoin(ShippingInfo)(_.id `=` _.buyerId) - .map { case (b, si) => (b.name, si.map(s => (s.id, s.shippingDate))) } -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - shipping_info1.id AS res_1_0, - shipping_info1.shipping_date AS res_1_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq( - ("James Bond", Some((2, LocalDate.parse("2012-04-05")))), - ("Li Haoyi", None), - ("叉烧包", Some((1, LocalDate.parse("2010-02-03")))), - ("叉烧包", Some((3, LocalDate.parse("2012-05-06")))) - ) - ``` - - - -### Join.leftJoinExpr - -`JoinNullable[Expr[T]]`s can be implicitly used as `Expr[Option[T]]`s. This allows -them to participate in any database query logic than any other `Expr[Option[T]]`s -can participate in, such as being used as sort key or in computing return values -(below). - -```scala -Buyer.select - .leftJoin(ShippingInfo)(_.id `=` _.buyerId) - .map { case (b, si) => (b.name, si.map(_.shippingDate)) } - .sortBy(_._2) - .nullsFirst -``` - - -* - ```sql - SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ORDER BY res_1 NULLS FIRST - ``` - - - -* - ```scala - Seq[(String, Option[LocalDate])]( - ("Li Haoyi", None), - ("叉烧包", Some(LocalDate.parse("2010-02-03"))), - ("James Bond", Some(LocalDate.parse("2012-04-05"))), - ("叉烧包", Some(LocalDate.parse("2012-05-06"))) - ) - ``` - - - -### Join.leftJoinIsEmpty - -You can use the `.isEmpty` method on `JoinNullable[T]` to check whether a joined table -is `NULL`, by specifying a specific non-nullable column to test against. - -```scala -Buyer.select - .leftJoin(ShippingInfo)(_.id `=` _.buyerId) - .map { case (b, si) => (b.name, si.nonEmpty(_.id)) } - .distinct - .sortBy(_._1) -``` - - -* - ```sql - SELECT DISTINCT buyer0.name AS res_0, (shipping_info1.id IS NOT NULL) AS res_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ORDER BY res_0 - ``` - - - -* - ```scala - Seq( - ("James Bond", true), - ("Li Haoyi", false), - ("叉烧包", true) - ) - ``` - - - -### Join.leftJoinExpr2 - - - -```scala -Buyer.select - .leftJoin(ShippingInfo)(_.id `=` _.buyerId) - .map { case (b, si) => (b.name, si.map(_.shippingDate) > b.dateOfBirth) } -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - (shipping_info1.shipping_date > buyer0.date_of_birth) AS res_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq( - ("James Bond", true), - ("Li Haoyi", false), - ("叉烧包", true), - ("叉烧包", true) - ) - ``` - - - -### Join.leftJoinExprExplicit - -The conversion from `JoinNullable[T]` to `Expr[Option[T]]` can also be performed -explicitly via `JoinNullable.toExpr(...)` - -```scala -Buyer.select - .leftJoin(ShippingInfo)(_.id `=` _.buyerId) - .map { case (b, si) => - (b.name, JoinNullable.toExpr(si.map(_.shippingDate)) > b.dateOfBirth) - } -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - (shipping_info1.shipping_date > buyer0.date_of_birth) AS res_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq( - ("James Bond", true), - ("Li Haoyi", false), - ("叉烧包", true), - ("叉烧包", true) - ) - ``` - - - -### Join.rightJoin - - - -```scala -ShippingInfo.select.rightJoin(Buyer)(_.buyerId `=` _.id) -``` - - -* - ```sql - SELECT - shipping_info0.id AS res_0_id, - shipping_info0.buyer_id AS res_0_buyer_id, - shipping_info0.shipping_date AS res_0_shipping_date, - buyer1.id AS res_1_id, - buyer1.name AS res_1_name, - buyer1.date_of_birth AS res_1_date_of_birth - FROM shipping_info shipping_info0 - RIGHT JOIN buyer buyer1 ON (shipping_info0.buyer_id = buyer1.id) - ``` - - - -* - ```scala - Seq( - ( - Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))), - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) - ), - ( - Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ), - ( - Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) - ), - (None, Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))) - ) - ``` - - - -### Join.outerJoin - - - -```scala -ShippingInfo.select.outerJoin(Buyer)(_.buyerId `=` _.id) -``` - - -* - ```sql - SELECT - shipping_info0.id AS res_0_id, - shipping_info0.buyer_id AS res_0_buyer_id, - shipping_info0.shipping_date AS res_0_shipping_date, - buyer1.id AS res_1_id, - buyer1.name AS res_1_name, - buyer1.date_of_birth AS res_1_date_of_birth - FROM shipping_info shipping_info0 - FULL OUTER JOIN buyer buyer1 ON (shipping_info0.buyer_id = buyer1.id) - ``` - - - -* - ```scala - Seq( - ( - Option(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))), - Option(Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03"))) - ), - ( - Option(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))), - Option(Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12"))) - ), - ( - Option(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))), - Option(Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12"))) - ), - (Option.empty, Option(Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")))) - ) - ``` - - - -### Join.crossJoin - -`.crossJoin` can be used to generate a SQL `CROSS JOIN`, which allows you -to perform a `JOIN` with an `ON` clause in a consistent way across databases - -```scala -Buyer.select - .crossJoin(ShippingInfo) - .filter { case (b, s) => b.id `=` s.buyerId } - .map { case (b, s) => (b.name, s.shippingDate) } -``` - - -* - ```sql - SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 - FROM buyer buyer0 - CROSS JOIN shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -## FlatJoin -inner `JOIN`s, `JOIN ON`s, self-joins, `LEFT`/`RIGHT`/`OUTER` `JOIN`s -### FlatJoin.join - -"flat" joins using `for`-comprehensions are allowed. These allow you to -"flatten out" the nested tuples you get from normal `.join` clauses, -letting you write natural looking queries without deeply nested tuples. - -```scala -for { - b <- Buyer.select - si <- ShippingInfo.join(_.buyerId `=` b.id) -} yield (b.name, si.shippingDate) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 - FROM buyer buyer0 - JOIN shipping_info shipping_info1 ON (shipping_info1.buyer_id = buyer0.id) - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -### FlatJoin.join3 - -"flat" joins using `for`-comprehensions can have multiple `.join` clauses that -translate to SQL `JOIN ON`s, as well as `if` clauses that translate to SQL -`WHERE` clauses. This example uses multiple flat `.join`s together with `if` -clauses to query the products purchased by the user `"Li Haoyi"` that have -a price more than `1.0` dollars - -```scala -for { - b <- Buyer.select - if b.name === "Li Haoyi" - si <- ShippingInfo.join(_.id `=` b.id) - pu <- Purchase.join(_.shippingInfoId `=` si.id) - pr <- Product.join(_.id `=` pu.productId) - if pr.price > 1.0 -} yield (b.name, pr.name, pr.price) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, product3.name AS res_1, product3.price AS res_2 - FROM buyer buyer0 - JOIN shipping_info shipping_info1 ON (shipping_info1.id = buyer0.id) - JOIN purchase purchase2 ON (purchase2.shipping_info_id = shipping_info1.id) - JOIN product product3 ON (product3.id = purchase2.product_id) - WHERE (buyer0.name = ?) AND (product3.price > ?) - ``` - - - -* - ```scala - Seq( - ("Li Haoyi", "Face Mask", 8.88) - ) - ``` - - - -### FlatJoin.leftJoin - -Flat joins can also support `.leftJoin`s, where the table being joined -is given to you as a `JoinNullable[T]` - -```scala -for { - b <- Buyer.select - si <- ShippingInfo.leftJoin(_.buyerId `=` b.id) -} yield (b.name, si.map(_.shippingDate)) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 - FROM buyer buyer0 - LEFT JOIN shipping_info shipping_info1 ON (shipping_info1.buyer_id = buyer0.id) - ``` - - - -* - ```scala - Seq( - ("James Bond", Some(LocalDate.parse("2012-04-05"))), - ("Li Haoyi", None), - ("叉烧包", Some(LocalDate.parse("2010-02-03"))), - ("叉烧包", Some(LocalDate.parse("2012-05-06"))) - ) - ``` - - - -### FlatJoin.flatMap - -You can also perform inner joins via `flatMap`, either by directly -calling `.flatMap` or via `for`-comprehensions as below. This can help -reduce the boilerplate when dealing with lots of joins. - -```scala -Buyer.select - .flatMap(b => ShippingInfo.crossJoin().map((b, _))) - .filter { case (b, s) => b.id `=` s.buyerId && b.name `=` "James Bond" } - .map(_._2.shippingDate) -``` - - -* - ```sql - SELECT shipping_info1.shipping_date AS res - FROM buyer buyer0 - CROSS JOIN shipping_info shipping_info1 - WHERE ((buyer0.id = shipping_info1.buyer_id) AND (buyer0.name = ?)) - ``` - - - -* - ```scala - Seq(LocalDate.parse("2012-04-05")) - ``` - - - -### FlatJoin.flatMapFor - -You can also perform inner joins via `flatMap - -```scala -for { - b <- Buyer.select - s <- ShippingInfo.crossJoin() - if b.id `=` s.buyerId && b.name `=` "James Bond" -} yield s.shippingDate -``` - - -* - ```sql - SELECT shipping_info1.shipping_date AS res - FROM buyer buyer0 - CROSS JOIN shipping_info shipping_info1 - WHERE ((buyer0.id = shipping_info1.buyer_id) AND (buyer0.name = ?)) - ``` - - - -* - ```scala - Seq(LocalDate.parse("2012-04-05")) - ``` - - - -### FlatJoin.flatMapForFilter - - - -```scala -for { - b <- Buyer.select.filter(_.name `=` "James Bond") - s <- ShippingInfo.crossJoin().filter(b.id `=` _.buyerId) -} yield s.shippingDate -``` - - -* - ```sql - SELECT shipping_info1.shipping_date AS res - FROM buyer buyer0 - CROSS JOIN shipping_info shipping_info1 - WHERE (buyer0.name = ?) AND (buyer0.id = shipping_info1.buyer_id) - ``` - - - -* - ```scala - Seq(LocalDate.parse("2012-04-05")) - ``` - - - -### FlatJoin.flatMapForJoin - -Using queries with `join`s in a `for`-comprehension is supported, with the -generated `JOIN`s being added to the `FROM` clause generated by the `.flatMap`. - -```scala -for { - (b, si) <- Buyer.select.join(ShippingInfo)(_.id `=` _.buyerId) - (pu, pr) <- Purchase.select.join(Product)(_.productId `=` _.id).crossJoin() - if si.id `=` pu.shippingInfoId -} yield (b.name, pr.name) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, subquery2.res_1_name AS res_1 - FROM buyer buyer0 - JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) - CROSS JOIN (SELECT - purchase2.shipping_info_id AS res_0_shipping_info_id, - product3.name AS res_1_name - FROM purchase purchase2 - JOIN product product3 ON (purchase2.product_id = product3.id)) subquery2 - WHERE (shipping_info1.id = subquery2.res_0_shipping_info_id) - ``` - - - -* - ```scala - Seq( - ("James Bond", "Camera"), - ("James Bond", "Skate Board"), - ("叉烧包", "Cookie"), - ("叉烧包", "Face Mask"), - ("叉烧包", "Face Mask"), - ("叉烧包", "Guitar"), - ("叉烧包", "Socks") - ) - ``` - - - -### FlatJoin.flatMapForGroupBy - -Using non-trivial queries in the `for`-comprehension may result in subqueries -being generated - -```scala -for { - (name, dateOfBirth) <- Buyer.select.groupBy(_.name)(_.minBy(_.dateOfBirth)) - shippingInfo <- ShippingInfo.crossJoin() -} yield (name, dateOfBirth, shippingInfo.id, shippingInfo.shippingDate) -``` - - -* - ```sql - SELECT - subquery0.res_0 AS res_0, - subquery0.res_1 AS res_1, - shipping_info1.id AS res_2, - shipping_info1.shipping_date AS res_3 - FROM (SELECT buyer0.name AS res_0, MIN(buyer0.date_of_birth) AS res_1 - FROM buyer buyer0 - GROUP BY buyer0.name) subquery0 - CROSS JOIN shipping_info shipping_info1 - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2001-02-03"), 1, LocalDate.parse("2010-02-03")), - ("James Bond", LocalDate.parse("2001-02-03"), 2, LocalDate.parse("2012-04-05")), - ("James Bond", LocalDate.parse("2001-02-03"), 3, LocalDate.parse("2012-05-06")), - ("Li Haoyi", LocalDate.parse("1965-08-09"), 1, LocalDate.parse("2010-02-03")), - ("Li Haoyi", LocalDate.parse("1965-08-09"), 2, LocalDate.parse("2012-04-05")), - ("Li Haoyi", LocalDate.parse("1965-08-09"), 3, LocalDate.parse("2012-05-06")), - ("叉烧包", LocalDate.parse("1923-11-12"), 1, LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("1923-11-12"), 2, LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("1923-11-12"), 3, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### FlatJoin.flatMapForGroupBy2 - -Using non-trivial queries in the `for`-comprehension may result in subqueries -being generated - -```scala -for { - (name, dateOfBirth) <- Buyer.select.groupBy(_.name)(_.minBy(_.dateOfBirth)) - (shippingInfoId, shippingDate) <- ShippingInfo.select - .groupBy(_.id)(_.minBy(_.shippingDate)) - .crossJoin() -} yield (name, dateOfBirth, shippingInfoId, shippingDate) -``` - - -* - ```sql - SELECT - subquery0.res_0 AS res_0, - subquery0.res_1 AS res_1, - subquery1.res_0 AS res_2, - subquery1.res_1 AS res_3 - FROM (SELECT - buyer0.name AS res_0, - MIN(buyer0.date_of_birth) AS res_1 - FROM buyer buyer0 - GROUP BY buyer0.name) subquery0 - CROSS JOIN (SELECT - shipping_info1.id AS res_0, - MIN(shipping_info1.shipping_date) AS res_1 - FROM shipping_info shipping_info1 - GROUP BY shipping_info1.id) subquery1 - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2001-02-03"), 1, LocalDate.parse("2010-02-03")), - ("James Bond", LocalDate.parse("2001-02-03"), 2, LocalDate.parse("2012-04-05")), - ("James Bond", LocalDate.parse("2001-02-03"), 3, LocalDate.parse("2012-05-06")), - ("Li Haoyi", LocalDate.parse("1965-08-09"), 1, LocalDate.parse("2010-02-03")), - ("Li Haoyi", LocalDate.parse("1965-08-09"), 2, LocalDate.parse("2012-04-05")), - ("Li Haoyi", LocalDate.parse("1965-08-09"), 3, LocalDate.parse("2012-05-06")), - ("叉烧包", LocalDate.parse("1923-11-12"), 1, LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("1923-11-12"), 2, LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("1923-11-12"), 3, LocalDate.parse("2012-05-06")) - ) - ``` - - - -### FlatJoin.flatMapForCompound - -Using non-trivial queries in the `for`-comprehension may result in subqueries -being generated - -```scala -for { - b <- Buyer.select.sortBy(_.id).asc.take(1) - si <- ShippingInfo.select.sortBy(_.id).asc.take(1).crossJoin() -} yield (b.name, si.shippingDate) -``` - - -* - ```sql - SELECT - subquery0.name AS res_0, - subquery1.shipping_date AS res_1 - FROM - (SELECT buyer0.id AS id, buyer0.name AS name - FROM buyer buyer0 - ORDER BY id ASC - LIMIT ?) subquery0 - CROSS JOIN (SELECT - shipping_info1.id AS id, - shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - ORDER BY id ASC - LIMIT ?) subquery1 - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2010-02-03")) - ) - ``` - - - -## Insert -Basic `INSERT` operations -### Insert.single.values - -`Table.insert.values` with a single value inserts a single row into the given table - -```scala -Buyer.insert.values( - Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")) -) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### Insert.single.skipped - -You can pass in one or more columns to `.skipColumns` to avoid inserting them. -This is useful for columns where you want to rely on the value being -auto-generated by the database. - -```scala -Buyer.insert - .values( - Buyer[Sc](-1, "test buyer", LocalDate.parse("2023-09-09")) - ) - .skipColumns(_.id) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### Insert.single.columns - -`Table.insert.columns` inserts a single row into the given table, with the specified - columns assigned to the given values, and any non-specified columns left `NULL` - or assigned to their default values - -```scala -Buyer.insert.columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 4 -) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### Insert.single.partial - - - -```scala -Buyer.insert - .columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09")) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### Insert.batch.values - -You can insert multiple rows at once by passing them to `Buyer.insert.values` - -```scala -Buyer.insert.values( - Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), - Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), - Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) -) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) - VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) - ``` - - - -* - ```scala - 3 - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5,6 comes from auto increment - Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), - Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), - Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) - ) - ``` - - - -### Insert.batch.partial - - - -```scala -Buyer.insert.batched(_.name, _.dateOfBirth)( - ("test buyer A", LocalDate.parse("2001-04-07")), - ("test buyer B", LocalDate.parse("2002-05-08")), - ("test buyer C", LocalDate.parse("2003-06-09")) -) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) - VALUES (?, ?), (?, ?), (?, ?) - ``` - - - -* - ```scala - 3 - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5,6 comes from auto increment - Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), - Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), - Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) - ) - ``` - - - -### Insert.select.caseclass - -`Table.insert.select` inserts rows into the given table based on the given `Table.select` -clause, and translates directly into SQL's `INSERT INTO ... SELECT` syntax. - -```scala -Buyer.insert.select( - identity, - Buyer.select - .filter(_.name <> "Li Haoyi") - .map(b => b.copy(id = b.id + Buyer.select.maxBy(_.id))) -) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) - SELECT - (buyer0.id + (SELECT MAX(buyer1.id) AS res FROM buyer buyer1)) AS id, - buyer0.name AS name, - buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - WHERE (buyer0.name <> ?) - ``` - - - -* - ```scala - 2 - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -### Insert.select.simple - - - -```scala -Buyer.insert.select( - x => (x.name, x.dateOfBirth), - Buyer.select.map(x => (x.name, x.dateOfBirth)).filter(_._1 <> "Li Haoyi") -) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) - SELECT buyer0.name AS res_0, buyer0.date_of_birth AS res_1 - FROM buyer buyer0 - WHERE (buyer0.name <> ?) - ``` - - - -* - ```scala - 2 - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5 comes from auto increment, 6 is filtered out in the select - Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -## Update -Basic `UPDATE` queries -### Update.update - -`Table.update` takes a predicate specifying the rows to update, and a -`.set` clause that allows you to specify the values assigned to columns -on those rows - -```scala -Buyer - .update(_.name `=` "James Bond") - .set(_.dateOfBirth := LocalDate.parse("2019-04-07")) -``` - - -* - ```sql - UPDATE buyer SET date_of_birth = ? WHERE (buyer.name = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth).single -``` - - - - -* - ```scala - LocalDate.parse("2019-04-07") - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "Li Haoyi").map(_.dateOfBirth).single -``` - - - - -* - ```scala - LocalDate.parse("1965-08-09" /* not updated */ ) - ``` - - - -### Update.bulk - -The predicate to `Table.update` is mandatory, to avoid anyone forgetting to -provide one and accidentally bulk-updating all rows in their table. If you -really do want to update all rows in the table, you can provide the predicate `_ => true` - -```scala -Buyer.update(_ => true).set(_.dateOfBirth := LocalDate.parse("2019-04-07")) -``` - - -* - ```sql - UPDATE buyer SET date_of_birth = ? - ``` - - - -* - ```scala - 3 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth).single -``` - - - - -* - ```scala - LocalDate.parse("2019-04-07") - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "Li Haoyi").map(_.dateOfBirth).single -``` - - - - -* - ```scala - LocalDate.parse("2019-04-07") - ``` - - - -### Update.multiple - -This example shows how to update multiple columns in a single `Table.update` call - -```scala -Buyer - .update(_.name `=` "James Bond") - .set(_.dateOfBirth := LocalDate.parse("2019-04-07"), _.name := "John Dee") -``` - - -* - ```sql - UPDATE buyer SET date_of_birth = ?, name = ? WHERE (buyer.name = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq[LocalDate]( /* not found due to rename */ ) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "John Dee").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq(LocalDate.parse("2019-04-07")) - ``` - - - -### Update.dynamic - -The values assigned to columns in `Table.update` can also be computed `Expr[T]`s, -not just literal Scala constants. This example shows how to to update the name of -the row for `James Bond` with it's existing name in uppercase - -```scala -Buyer.update(_.name `=` "James Bond").set(c => c.name := c.name.toUpperCase) -``` - - -* - ```sql - UPDATE buyer SET name = UPPER(buyer.name) WHERE (buyer.name = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq[LocalDate]( /* not found due to rename */ ) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "JAMES BOND").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq(LocalDate.parse("2001-02-03")) - ``` - - - -## Delete -Basic `DELETE` operations -### Delete.single - -`Table.delete` takes a mandatory predicate specifying what rows you want to delete. -The most common case is to specify the ID of the row you want to delete - -```scala -Purchase.delete(_.id `=` 2) -``` - - -* - ```sql - DELETE FROM purchase WHERE (purchase.id = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Purchase.select -``` - - - - -* - ```scala - Seq( - Purchase[Sc](id = 1, shippingInfoId = 1, productId = 1, count = 100, total = 888.0), - // id==2 got deleted - Purchase[Sc](id = 3, shippingInfoId = 1, productId = 3, count = 5, total = 15.7), - Purchase[Sc](id = 4, shippingInfoId = 2, productId = 4, count = 4, total = 493.8), - Purchase[Sc](id = 5, shippingInfoId = 2, productId = 5, count = 10, total = 10000.0), - Purchase[Sc](id = 6, shippingInfoId = 3, productId = 1, count = 5, total = 44.4), - Purchase[Sc](id = 7, shippingInfoId = 3, productId = 6, count = 13, total = 1.3) - ) - ``` - - - -### Delete.multiple - -Although specifying a single ID to delete is the most common case, you can pass -in arbitrary predicates, e.g. in this example deleting all rows _except_ for the -one with a particular ID - -```scala -Purchase.delete(_.id <> 2) -``` - - -* - ```sql - DELETE FROM purchase WHERE (purchase.id <> ?) - ``` - - - -* - ```scala - 6 - ``` - - - ----- - - - -```scala -Purchase.select -``` - - - - -* - ```scala - Seq(Purchase[Sc](id = 2, shippingInfoId = 1, productId = 2, count = 3, total = 900.0)) - ``` - - - -### Delete.all - -If you actually want to delete all rows in the table, you can explicitly -pass in the predicate `_ => true` - -```scala -Purchase.delete(_ => true) -``` - - -* - ```sql - DELETE FROM purchase WHERE ? - ``` - - - -* - ```scala - 7 - ``` - - - ----- - - - -```scala -Purchase.select -``` - - - - -* - ```scala - Seq[Purchase[Sc]]( - // all Deleted - ) - ``` - - - -## CompoundSelect -Compound `SELECT` operations: sort, take, drop, union, unionAll, etc. -### CompoundSelect.sort.simple - -ScalaSql's `.sortBy` method translates into SQL `ORDER BY` - -```scala -Product.select.sortBy(_.price).map(_.name) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price - ``` - - - -* - ```scala - Seq("Cookie", "Socks", "Face Mask", "Skate Board", "Guitar", "Camera") - ``` - - - -### CompoundSelect.sort.twice - -If you want to sort by multiple columns, you can call `.sortBy` multiple times, -each with its own call to `.asc` or `.desc`. Note that the rightmost call to `.sortBy` -takes precedence, following the Scala collections `.sortBy` semantics, and so the -right-most `.sortBy` in ScalaSql becomes the _left_-most entry in the SQL `ORDER BY` clause - -```scala -Purchase.select.sortBy(_.productId).asc.sortBy(_.shippingInfoId).desc -``` - - -* - ```sql - SELECT - purchase0.id AS id, - purchase0.shipping_info_id AS shipping_info_id, - purchase0.product_id AS product_id, - purchase0.count AS count, - purchase0.total AS total - FROM purchase purchase0 - ORDER BY shipping_info_id DESC, product_id ASC - ``` - - - -* - ```scala - Seq( - Purchase[Sc](6, 3, 1, 5, 44.4), - Purchase[Sc](7, 3, 6, 13, 1.3), - Purchase[Sc](4, 2, 4, 4, 493.8), - Purchase[Sc](5, 2, 5, 10, 10000.0), - Purchase[Sc](1, 1, 1, 100, 888.0), - Purchase[Sc](2, 1, 2, 3, 900.0), - Purchase[Sc](3, 1, 3, 5, 15.7) - ) - ``` - - - -### CompoundSelect.sort.sortLimit - -ScalaSql also supports various combinations of `.take` and `.drop`, translating to SQL -`LIMIT` or `OFFSET` - -```scala -Product.select.sortBy(_.price).map(_.name).take(2) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? - ``` - - - -* - ```scala - Seq("Cookie", "Socks") - ``` - - - -### CompoundSelect.sort.sortOffset - - - -```scala -Product.select.sortBy(_.price).map(_.name).drop(2) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price OFFSET ? - ``` - - - -* - ```scala - Seq("Face Mask", "Skate Board", "Guitar", "Camera") - ``` - - - -### CompoundSelect.sort.sortLimitTwiceHigher - -Note that `.drop` and `.take` follow Scala collections' semantics, so calling e.g. `.take` -multiple times takes the value of the smallest `.take`, while calling `.drop` multiple -times accumulates the total amount dropped - -```scala -Product.select.sortBy(_.price).map(_.name).take(2).take(3) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? - ``` - - - -* - ```scala - Seq("Cookie", "Socks") - ``` - - - -### CompoundSelect.sort.sortLimitTwiceLower - - - -```scala -Product.select.sortBy(_.price).map(_.name).take(2).take(1) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? - ``` - - - -* - ```scala - Seq("Cookie") - ``` - - - -### CompoundSelect.sort.sortLimitOffset - - - -```scala -Product.select.sortBy(_.price).map(_.name).drop(2).take(2) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? OFFSET ? - ``` - - - -* - ```scala - Seq("Face Mask", "Skate Board") - ``` - - - -### CompoundSelect.sort.sortLimitOffsetTwice - - - -```scala -Product.select.sortBy(_.price).map(_.name).drop(2).drop(2).take(1) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? OFFSET ? - ``` - - - -* - ```scala - Seq("Guitar") - ``` - - - -### CompoundSelect.sort.sortOffsetLimit - - - -```scala -Product.select.sortBy(_.price).map(_.name).drop(2).take(2) -``` - - -* - ```sql - SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? OFFSET ? - ``` - - - -* - ```scala - Seq("Face Mask", "Skate Board") - ``` - - - -### CompoundSelect.distinct - -ScalaSql's `.distinct` translates to SQL's `SELECT DISTINCT` - -```scala -Purchase.select.sortBy(_.total).desc.take(3).map(_.shippingInfoId).distinct -``` - - -* - ```sql - SELECT DISTINCT subquery0.res AS res - FROM (SELECT purchase0.shipping_info_id AS res - FROM purchase purchase0 - ORDER BY purchase0.total DESC - LIMIT ?) subquery0 - ``` - - - -* - ```scala - Seq(1, 2) - ``` - - - -### CompoundSelect.flatMap - -Many operations in SQL cannot be done in certain orders, unless you move part of the logic into -a subquery. ScalaSql does this automatically for you, e.g. doing a `flatMap`, `.sumBy`, or -`.aggregate` after a `.sortBy`/`.take`, the LHS `.sortBy`/`.take` is automatically extracted -into a subquery - -```scala -Purchase.select.sortBy(_.total).desc.take(3).flatMap { p => - Product.crossJoin().filter(_.id === p.productId).map(_.name) -} -``` - - -* - ```sql - SELECT product1.name AS res - FROM (SELECT purchase0.product_id AS product_id, purchase0.total AS total - FROM purchase purchase0 - ORDER BY total DESC - LIMIT ?) subquery0 - CROSS JOIN product product1 - WHERE (product1.id = subquery0.product_id) - ``` - - - -* - ```scala - Seq("Camera", "Face Mask", "Guitar") - ``` - - - -### CompoundSelect.sumBy - - - -```scala -Purchase.select.sortBy(_.total).desc.take(3).sumBy(_.total) -``` - - -* - ```sql - SELECT SUM(subquery0.total) AS res - FROM (SELECT purchase0.total AS total - FROM purchase purchase0 - ORDER BY total DESC - LIMIT ?) subquery0 - ``` - - - -* - ```scala - 11788.0 - ``` - - - -### CompoundSelect.aggregate - - - -```scala -Purchase.select - .sortBy(_.total) - .desc - .take(3) - .aggregate(p => (p.sumBy(_.total), p.avgBy(_.total))) -``` - - -* - ```sql - SELECT SUM(subquery0.total) AS res_0, AVG(subquery0.total) AS res_1 - FROM (SELECT purchase0.total AS total - FROM purchase purchase0 - ORDER BY total DESC - LIMIT ?) subquery0 - ``` - - - -* - ```scala - (11788.0, 3929.0) - ``` - - - -### CompoundSelect.union - -ScalaSql's `.union`/`.unionAll`/`.intersect`/`.except` translate into SQL's -`UNION`/`UNION ALL`/`INTERSECT`/`EXCEPT`. - -```scala -Product.select - .map(_.name.toLowerCase) - .union(Product.select.map(_.kebabCaseName.toLowerCase)) -``` - - -* - ```sql - SELECT LOWER(product0.name) AS res - FROM product product0 - UNION - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ``` - - - -* - ```scala - Seq( - "camera", - "cookie", - "face mask", - "face-mask", - "guitar", - "skate board", - "skate-board", - "socks" - ) - ``` - - - -### CompoundSelect.unionAll - - - -```scala -Product.select - .map(_.name.toLowerCase) - .unionAll(Product.select.map(_.kebabCaseName.toLowerCase)) -``` - - -* - ```sql - SELECT LOWER(product0.name) AS res - FROM product product0 - UNION ALL - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ``` - - - -* - ```scala - Seq( - "face mask", - "guitar", - "socks", - "skate board", - "camera", - "cookie", - "face-mask", - "guitar", - "socks", - "skate-board", - "camera", - "cookie" - ) - ``` - - - -### CompoundSelect.intersect - - - -```scala -Product.select - .map(_.name.toLowerCase) - .intersect(Product.select.map(_.kebabCaseName.toLowerCase)) -``` - - -* - ```sql - SELECT LOWER(product0.name) AS res - FROM product product0 - INTERSECT - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ``` - - - -* - ```scala - Seq("camera", "cookie", "guitar", "socks") - ``` - - - -### CompoundSelect.except - - - -```scala -Product.select - .map(_.name.toLowerCase) - .except(Product.select.map(_.kebabCaseName.toLowerCase)) -``` - - -* - ```sql - SELECT LOWER(product0.name) AS res - FROM product product0 - EXCEPT - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ``` - - - -* - ```scala - Seq("face mask", "skate board") - ``` - - - -### CompoundSelect.unionAllUnionSort - -Performing a `.sortBy` after `.union` or `.unionAll` applies the sort -to both sides of the `union`/`unionAll`, behaving identically to Scala or SQL - -```scala -Product.select - .map(_.name.toLowerCase) - .unionAll(Buyer.select.map(_.name.toLowerCase)) - .union(Product.select.map(_.kebabCaseName.toLowerCase)) - .sortBy(identity) -``` - - -* - ```sql - SELECT LOWER(product0.name) AS res - FROM product product0 - UNION ALL - SELECT LOWER(buyer0.name) AS res - FROM buyer buyer0 - UNION - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ORDER BY res - ``` - - - -* - ```scala - Seq( - "camera", - "cookie", - "face mask", - "face-mask", - "guitar", - "james bond", - "li haoyi", - "skate board", - "skate-board", - "socks", - "叉烧包" - ) - ``` - - - -### CompoundSelect.unionAllUnionSortLimit - - - -```scala -Product.select - .map(_.name.toLowerCase) - .unionAll(Buyer.select.map(_.name.toLowerCase)) - .union(Product.select.map(_.kebabCaseName.toLowerCase)) - .sortBy(identity) - .drop(4) - .take(4) -``` - - -* - ```sql - SELECT LOWER(product0.name) AS res - FROM product product0 - UNION ALL - SELECT LOWER(buyer0.name) AS res - FROM buyer buyer0 - UNION - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ORDER BY res - LIMIT ? - OFFSET ? - ``` - - - -* - ```scala - Seq("guitar", "james bond", "li haoyi", "skate board") - ``` - - - -## UpdateJoin -`UPDATE` queries that use `JOIN`s -### UpdateJoin.join - -ScalaSql supports performing `UPDATE`s with `FROM`/`JOIN` clauses using the -`.update.join` methods - -```scala -Buyer - .update(_.name `=` "James Bond") - .join(ShippingInfo)(_.id `=` _.buyerId) - .set(c => c._1.dateOfBirth := c._2.shippingDate) -``` - - -* - ```sql - UPDATE buyer - SET date_of_birth = shipping_info0.shipping_date - FROM shipping_info shipping_info0 - WHERE (buyer.id = shipping_info0.buyer_id) AND (buyer.name = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq(LocalDate.parse("2012-04-05")) - ``` - - - -### UpdateJoin.multijoin - -Multiple joins are supported, e.g. the below example where we join the `Buyer` table -three times against `ShippingInfo`/`Purchase`/`Product` to determine what to update - -```scala -Buyer - .update(_.name `=` "James Bond") - .join(ShippingInfo)(_.id `=` _.buyerId) - .join(Purchase)(_._2.id `=` _.shippingInfoId) - .join(Product)(_._3.productId `=` _.id) - .filter(t => t._4.name.toLowerCase `=` t._4.kebabCaseName.toLowerCase) - .set(c => c._1.name := c._4.name) -``` - - -* - ```sql - UPDATE buyer - SET name = product2.name - FROM shipping_info shipping_info0 - JOIN purchase purchase1 ON (shipping_info0.id = purchase1.shipping_info_id) - JOIN product product2 ON (purchase1.product_id = product2.id) - WHERE (buyer.id = shipping_info0.buyer_id) - AND (buyer.name = ?) - AND (LOWER(product2.name) = LOWER(product2.kebab_case_name)) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.id `=` 1).map(_.name) -``` - - - - -* - ```scala - Seq("Camera") - ``` - - - -### UpdateJoin.joinSubquery - -In addition to `JOIN`ing against another table, you can also perform `JOIN`s against -subqueries by passing in a `.select` query to `.join` - -```scala -Buyer - .update(_.name `=` "James Bond") - .join(ShippingInfo.select.sortBy(_.id).asc.take(2))(_.id `=` _.buyerId) - .set(c => c._1.dateOfBirth := c._2.shippingDate) -``` - - -* - ```sql - UPDATE buyer SET date_of_birth = subquery0.shipping_date - FROM (SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id, - shipping_info0.shipping_date AS shipping_date - FROM shipping_info shipping_info0 - ORDER BY id ASC - LIMIT ?) subquery0 - WHERE (buyer.id = subquery0.buyer_id) AND (buyer.name = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq(LocalDate.parse("2012-04-05")) - ``` - - - -### UpdateJoin.joinSubqueryEliminatedColumn - - - -```scala -Buyer - .update(_.name `=` "James Bond") - // Make sure the `SELECT shipping_info0.shipping_info_id AS shipping_info_id` - // column gets eliminated since it is not used outside the subquery - .join(ShippingInfo.select.sortBy(_.id).asc.take(2))(_.id `=` _.buyerId) - .set(c => c._1.dateOfBirth := LocalDate.parse("2000-01-01")) -``` - - -* - ```sql - UPDATE buyer SET date_of_birth = ? - FROM (SELECT - shipping_info0.id AS id, - shipping_info0.buyer_id AS buyer_id - FROM shipping_info shipping_info0 - ORDER BY id ASC - LIMIT ?) subquery0 - WHERE (buyer.id = subquery0.buyer_id) AND (buyer.name = ?) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq(LocalDate.parse("2000-01-01")) - ``` - - - -## UpdateSubQuery -`UPDATE` queries that use Subqueries -### UpdateSubQuery.setSubquery - -You can use subqueries to compute the values you want to update, using -aggregates like `.maxBy` to convert the `Select[T]` into an `Expr[T]` - -```scala -Product.update(_ => true).set(_.price := Product.select.maxBy(_.price)) -``` - - -* - ```sql - UPDATE product - SET price = (SELECT MAX(product1.price) AS res FROM product product1) - ``` - - - -* - ```scala - 6 - ``` - - - ----- - - - -```scala -Product.select.map(p => (p.id, p.name, p.price)) -``` - - - - -* - ```scala - Seq( - (1, "Face Mask", 1000.0), - (2, "Guitar", 1000.0), - (3, "Socks", 1000.0), - (4, "Skate Board", 1000.0), - (5, "Camera", 1000.0), - (6, "Cookie", 1000.0) - ) - ``` - - - -### UpdateSubQuery.whereSubquery - -Subqueries and aggregates can also be used in the `WHERE` clause, defined by the -predicate passed to `Table.update - -```scala -Product.update(_.price `=` Product.select.maxBy(_.price)).set(_.price := 0) -``` - - -* - ```sql - UPDATE product - SET price = ? - WHERE (product.price = (SELECT MAX(product1.price) AS res FROM product product1)) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Product.select.map(p => (p.id, p.name, p.price)) -``` - - - - -* - ```scala - Seq( - (1, "Face Mask", 8.88), - (2, "Guitar", 300.0), - (3, "Socks", 3.14), - (4, "Skate Board", 123.45), - (5, "Camera", 0.0), - (6, "Cookie", 0.1) - ) - ``` - - - -## Returning -Queries using `INSERT` or `UPDATE` with `RETURNING` -### Returning.insert.single - -ScalaSql's `.returning` clause translates to SQL's `RETURNING` syntax, letting -you perform insertions or updates and return values from the query (rather than -returning a single integer representing the rows affected). This is especially -useful for retrieving the auto-generated table IDs that many databases support. - -Note that `.returning`/`RETURNING` is not supported in MySql, H2 or HsqlDB - -```scala -Buyer.insert - .columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09")) - .returning(_.id) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) RETURNING buyer.id AS res - ``` - - - -* - ```scala - Seq(4) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### Returning.insert.dotSingle - -If your `.returning` query is expected to be a single row, the `.single` method is -supported to convert the returned `Seq[T]` into a single `T`. `.single` throws an -exception if zero or multiple rows are returned. - -```scala -Buyer.insert - .columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09")) - .returning(_.id) - .single -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) RETURNING buyer.id AS res - ``` - - - -* - ```scala - 4 - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### Returning.insert.multiple - - - -```scala -Buyer.insert - .batched(_.name, _.dateOfBirth)( - ("test buyer A", LocalDate.parse("2001-04-07")), - ("test buyer B", LocalDate.parse("2002-05-08")), - ("test buyer C", LocalDate.parse("2003-06-09")) - ) - .returning(_.id) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) - VALUES - (?, ?), - (?, ?), - (?, ?) - RETURNING buyer.id AS res - ``` - - - -* - ```scala - Seq(4, 5, 6) - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5,6 comes from auto increment - Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), - Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), - Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) - ) - ``` - - - -### Returning.insert.select - -All variants of `.insert` and `.update` support `.returning`, e.g. the example below -applies to `.insert.select`, and the examples further down demonstrate its usage with -`.update` and `.delete` - -```scala -Buyer.insert - .select( - x => (x.name, x.dateOfBirth), - Buyer.select.map(x => (x.name, x.dateOfBirth)).filter(_._1 <> "Li Haoyi") - ) - .returning(_.id) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) - SELECT - buyer1.name AS res_0, - buyer1.date_of_birth AS res_1 - FROM buyer buyer1 - WHERE (buyer1.name <> ?) - RETURNING buyer.id AS res - ``` - - - -* - ```scala - Seq(4, 5) - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5 comes from auto increment, 6 is filtered out in the select - Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -### Returning.update.single - - - -```scala -Buyer - .update(_.name `=` "James Bond") - .set(_.dateOfBirth := LocalDate.parse("2019-04-07")) - .returning(_.id) -``` - - -* - ```sql - UPDATE buyer SET date_of_birth = ? WHERE (buyer.name = ?) RETURNING buyer.id AS res - ``` - - - -* - ```scala - Seq(1) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth) -``` - - - - -* - ```scala - Seq(LocalDate.parse("2019-04-07")) - ``` - - - -### Returning.update.multiple - - - -```scala -Buyer - .update(_.name `=` "James Bond") - .set(_.dateOfBirth := LocalDate.parse("2019-04-07"), _.name := "John Dee") - .returning(c => (c.id, c.name, c.dateOfBirth)) -``` - - -* - ```sql - UPDATE buyer - SET date_of_birth = ?, name = ? WHERE (buyer.name = ?) - RETURNING buyer.id AS res_0, buyer.name AS res_1, buyer.date_of_birth AS res_2 - ``` - - - -* - ```scala - Seq((1, "John Dee", LocalDate.parse("2019-04-07"))) - ``` - - - -### Returning.delete - - - -```scala -Purchase.delete(_.shippingInfoId `=` 1).returning(_.total) -``` - - -* - ```sql - DELETE FROM purchase WHERE (purchase.shipping_info_id = ?) RETURNING purchase.total AS res - ``` - - - -* - ```scala - Seq(888.0, 900.0, 15.7) - ``` - - - ----- - - - -```scala -Purchase.select -``` - - - - -* - ```scala - Seq( - // id=1,2,3 had shippingInfoId=1 and thus got deleted - Purchase[Sc](id = 4, shippingInfoId = 2, productId = 4, count = 4, total = 493.8), - Purchase[Sc](id = 5, shippingInfoId = 2, productId = 5, count = 10, total = 10000.0), - Purchase[Sc](id = 6, shippingInfoId = 3, productId = 1, count = 5, total = 44.4), - Purchase[Sc](id = 7, shippingInfoId = 3, productId = 6, count = 13, total = 1.3) - ) - ``` - - - -## OnConflict -Queries using `ON CONFLICT DO UPDATE` or `ON CONFLICT DO NOTHING` -### OnConflict.ignore - -ScalaSql's `.onConflictIgnore` translates into SQL's `ON CONFLICT DO NOTHING` - -Note that H2 and HsqlExpr do not support `onConflictIgnore` and `onConflictUpdate`, while -MySql only supports `onConflictUpdate` but not `onConflictIgnore`. - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictIgnore(_.id) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING - ``` - - - -* - ```scala - 0 - ``` - - - ----- - -with `insert.values` - -```scala -Buyer.insert - .values( - Buyer[Sc]( - id = 1, - name = "test buyer", - dateOfBirth = LocalDate.parse("2023-09-09") - ) - ) - .onConflictIgnore(_.id) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING - ``` - - - -* - ```scala - 0 - ``` - - - ----- - -with `insert.select` - -```scala -Buyer.insert - .select( - identity, - Buyer.select - .filter(_.id === 1) - .map(b => b.copy(name = b.name + ".")) - ) - .onConflictIgnore(_.id) -``` - - -* - ```sql - INSERT INTO - buyer (id, name, date_of_birth) - SELECT - buyer0.id AS id, - (buyer0.name || ?) AS name, - buyer0.date_of_birth AS date_of_birth - FROM - buyer buyer0 - WHERE - (buyer0.id = ?) ON CONFLICT (id) DO NOTHING - ``` - - - -* - ```scala - 0 - ``` - - - -### OnConflict.ignore.returningEmpty - - - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictIgnore(_.id) - .returning(_.name) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) - ON CONFLICT (id) DO NOTHING - RETURNING buyer.name AS res - ``` - - - -* - ```scala - Seq.empty[String] - ``` - - - ----- - -with `insert.values` - -```scala -Buyer.insert - .values( - Buyer[Sc]( - id = 1, - name = "test buyer", - dateOfBirth = LocalDate.parse("2023-09-09") - ) - ) - .onConflictIgnore(_.id) - .returning(_.name) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) - ON CONFLICT (id) DO NOTHING - RETURNING buyer.name AS res - ``` - - - -* - ```scala - Seq.empty[String] - ``` - - - -### OnConflict.ignore.returningOne - - - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 4 - ) - .onConflictIgnore(_.id) - .returning(_.name) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) - ON CONFLICT (id) DO NOTHING - RETURNING buyer.name AS res - ``` - - - -* - ```scala - Seq("test buyer") - ``` - - - ----- - -with `insert.values` - -```scala -Buyer.insert - .values( - Buyer[Sc]( - id = 5, - name = "test buyer", - dateOfBirth = LocalDate.parse("2023-09-09") - ) - ) - .onConflictIgnore(_.id) - .returning(_.name) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) - ON CONFLICT (id) DO NOTHING - RETURNING buyer.name AS res - ``` - - - -* - ```scala - Seq("test buyer") - ``` - - - -### OnConflict.update - -ScalaSql's `.onConflictUpdate` translates into SQL's `ON CONFLICT DO UPDATE` - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictUpdate(_.id)(_.name := "TEST BUYER CONFLICT") -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = ? - ``` - - - -* - ```scala - 1 - ``` - - - ----- - -with `insert.values` - -```scala -Buyer.insert - .values( - Buyer[Sc]( - id = 1, - name = "test buyer", - dateOfBirth = LocalDate.parse("2023-09-09") - ) - ) - .onConflictUpdate(_.id)(_.dateOfBirth := LocalDate.parse("2023-10-10")) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET date_of_birth = ? - ``` - - - -* - ```scala - 1 - ``` - - - ----- - -with `insert.select` - -```scala -Buyer.insert - .select( - identity, - Buyer.select - .filter(_.id === 1) - .map(b => b.copy(name = b.name + ".")) - ) - .onConflictUpdate(_.id)(_.dateOfBirth := LocalDate.parse("2023-10-09")) -``` - - -* - ```sql - INSERT INTO - buyer (id, name, date_of_birth) - SELECT - buyer1.id AS id, - (buyer1.name || ?) AS name, - buyer1.date_of_birth AS date_of_birth - FROM - buyer buyer1 - WHERE - (buyer1.id = ?) ON CONFLICT (id) DO - UPDATE - SET date_of_birth = ? - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "TEST BUYER CONFLICT", LocalDate.parse("2023-10-09")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) - ) - ``` - - - -### OnConflict.computed - - - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase) -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = UPPER(buyer.name) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - -with `insert.values` - -```scala -Buyer.insert - .values( - Buyer[Sc]( - id = 3, - name = "test buyer", - dateOfBirth = LocalDate.parse("2023-09-09") - ) - ) - .onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase) -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = UPPER(buyer.name) - ``` - - - -* - ```scala - 1 - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "JAMES BOND", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "LI HAOYI", LocalDate.parse("1965-08-09")) - ) - ``` - - - -### OnConflict.returning - - - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase) - .returning(_.name) - .single -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) - ON CONFLICT (id) DO UPDATE - SET name = UPPER(buyer.name) - RETURNING buyer.name AS res - ``` - - - -* - ```scala - "JAMES BOND" - ``` - - - ----- - -with `insert.values` - -```scala -Buyer.insert - .values( - Buyer[Sc]( - id = 1, - name = "test buyer", - dateOfBirth = LocalDate.parse("2023-09-09") - ) - ) - .onConflictUpdate(_.id)(v => v.name := v.name.toLowerCase) - .returning(_.name) - .single -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) - ON CONFLICT (id) DO UPDATE - SET name = LOWER(buyer.name) - RETURNING buyer.name AS res - ``` - - - -* - ```scala - "james bond" - ``` - - - -## Values -Basic `VALUES` operations -### Values.basic - -You can use `Values` to generate a SQL `VALUES` clause - -```scala -db.values(Seq(1, 2, 3)) -``` - - -* - ```sql - VALUES (?), (?), (?) - ``` - - - -* - ```scala - Seq(1, 2, 3) - ``` - - - -### Values.contains - -`Values` supports `.contains` - -```scala -db.values(Seq(1, 2, 3)).contains(1) -``` - - -* - ```sql - SELECT (? IN (VALUES (?), (?), (?))) AS res - ``` - - - -* - ```scala - true - ``` - - - -### Values.max - -`Values` supports aggregate functions like `.max` - -```scala -db.values(Seq(1, 2, 3)).max -``` - - -* - ```sql - SELECT MAX(subquery0.column1) AS res FROM (VALUES (?), (?), (?)) subquery0 - ``` - - - -* - ```scala - 3 - ``` - - - -### Values.map - -`Values` supports most `.select` operators like `.map`, `.filter`, `.crossJoin`, and so on - -```scala -db.values(Seq(1, 2, 3)).map(_ + 1) -``` - - -* - ```sql - SELECT (subquery0.column1 + ?) AS res FROM (VALUES (?), (?), (?)) subquery0 - ``` - - - -* - ```scala - Seq(2, 3, 4) - ``` - - - -### Values.filter - - - -```scala -db.values(Seq(1, 2, 3)).filter(_ > 2) -``` - - -* - ```sql - SELECT subquery0.column1 AS res FROM (VALUES (?), (?), (?)) subquery0 WHERE (subquery0.column1 > ?) - ``` - - - -* - ```scala - Seq(3) - ``` - - - -### Values.crossJoin - - - -```scala -db.values(Seq(1, 2, 3)).crossJoin(db.values(Seq(4, 5, 6))).map { - case (a, b) => (a * 10 + b) -} -``` - - -* - ```sql - SELECT ((subquery0.column1 * ?) + subquery1.column1) AS res - FROM (VALUES (?), (?), (?)) subquery0 - CROSS JOIN (VALUES (?), (?), (?)) subquery1 - ``` - - - -* - ```scala - Seq(14, 15, 16, 24, 25, 26, 34, 35, 36) - ``` - - - -### Values.joinValuesAndTable - -You can also mix `values` calls and normal `selects` in the same query, e.g. with joins - -```scala -for { - name <- db.values(Seq("Socks", "Face Mask", "Camera")) - product <- Product.join(_.name === name) -} yield (name, product.price) -``` - - -* - ```sql - SELECT subquery0.column1 AS res_0, product1.price AS res_1 - FROM (VALUES (?), (?), (?)) subquery0 - JOIN product product1 ON (product1.name = subquery0.column1) - ``` - - - -* - ```scala - Seq(("Socks", 3.14), ("Face Mask", 8.88), ("Camera", 1000.0)) - ``` - - - -### Values.multiple.tuple - -`values` supports tuples and other data structures as well - -```scala -db.values(Seq((1, 2), (3, 4), (5, 6))) -``` - - -* - ```sql - VALUES (?, ?), (?, ?), (?, ?) - ``` - - - -* - ```scala - Seq((1, 2), (3, 4), (5, 6)) - ``` - - - -### Values.multiple.caseClass - - - -```scala -db.values( - Seq( - Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "world", LocalDate.parse("2004-05-06")) - ) -) -``` - - -* - ```sql - VALUES (?, ?, ?), (?, ?, ?) - ``` - - - -* - ```scala - Seq( - Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "world", LocalDate.parse("2004-05-06")) - ) - ``` - - - -### Values.multiple.map - -`values` supports tuples and other data structures as well - -```scala -db.values(Seq((1, 2), (3, 4), (5, 6))).map { case (a, b) => (a + 10, b + 100) } -``` - - -* - ```sql - SELECT (subquery0.column1 + ?) AS res_0, (subquery0.column2 + ?) AS res_1 - FROM (VALUES (?, ?), (?, ?), (?, ?)) subquery0 - ``` - - - -* - ```scala - Seq((11, 102), (13, 104), (15, 106)) - ``` - - - -### Values.multiple.mapCaseClass - -`values` supports tuples and other data structures as well - -```scala -{ - val buyers = Seq( - Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "world", LocalDate.parse("2004-05-06")) - ) - val query = db.values(buyers).map { b => (b.id + 100, b) } - query -} -``` - - -* - ```sql - SELECT - (subquery0.column1 + ?) AS res_0, - subquery0.column1 AS res_1_id, - subquery0.column2 AS res_1_name, - subquery0.column3 AS res_1_date_of_birth - FROM (VALUES (?, ?, ?), (?, ?, ?)) subquery0 - ``` - - - -* - ```scala - Seq( - (101, Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03"))), - (102, Buyer[Sc](2, "world", LocalDate.parse("2004-05-06"))) - ) - ``` - - - -### Values.multiple.caseClassContains - -You can use `.contains` on multi-column Scala values, which are translated -to a SQL `IN` clause on a tuple. - -```scala -{ - val buyers = Seq( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) - ) - Buyer.select.filter(!db.values(buyers).contains(_)) -} -``` - - -* - ```sql - SELECT - buyer0.id AS id, - buyer0.name AS name, - buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - WHERE (NOT - ((buyer0.id, buyer0.name, buyer0.date_of_birth) IN (VALUES (?, ?, ?), (?, ?, ?)))) - ``` - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) - ) - ``` - - - -## LateralJoin - - `JOIN LATERAL`, for the databases that support it. This allows you to use the - expressions defined in tables on the left-hand-side of the join in a - subquery on the right-hand-side of the join, v.s. normal `JOIN`s which only - allow you to use left-hand-side expressions in the `ON` expression but not - in the `FROM` subquery. - -### LateralJoin.crossJoinLateral - - - -```scala -Buyer.select - .crossJoinLateral(b => ShippingInfo.select.filter { s => b.id `=` s.buyerId }) - .map { case (b, s) => (b.name, s.shippingDate) } -``` - - -* - ```sql - SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 - FROM buyer buyer0 - CROSS JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -### LateralJoin.crossJoinLateralFor - - - -```scala -for { - b <- Buyer.select - s <- ShippingInfo.select.filter { s => b.id `=` s.buyerId }.crossJoinLateral() -} yield (b.name, s.shippingDate) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 - FROM buyer buyer0 - CROSS JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -### LateralJoin.joinLateral - - - -```scala -Buyer.select - .joinLateral(b => ShippingInfo.select.filter { s => b.id `=` s.buyerId })((_, _) => true) - .map { case (b, s) => (b.name, s.shippingDate) } -``` - - -* - ```sql - SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 - FROM buyer buyer0 - JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 - ON ? - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -### LateralJoin.joinLateralFor - - - -```scala -for { - b <- Buyer.select - s <- ShippingInfo.select.filter { s => b.id `=` s.buyerId }.joinLateral(_ => Expr(true)) -} yield (b.name, s.shippingDate) -``` - - -* - ```sql - SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 - FROM buyer buyer0 - JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 - ON ? - ``` - - - -* - ```scala - Seq( - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2010-02-03")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -### LateralJoin.leftJoin - -ScalaSql supports `LEFT JOIN`s, `RIGHT JOIN`s and `OUTER JOIN`s via the -`.leftJoin`/`.rightJoin`/`.outerJoin` methods - -```scala -Buyer.select.leftJoinLateral(b => ShippingInfo.select.filter(b.id `=` _.buyerId))((_, _) => - Expr(true) -) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - subquery1.id AS res_1_id, - subquery1.buyer_id AS res_1_buyer_id, - subquery1.shipping_date AS res_1_shipping_date - FROM buyer buyer0 - LEFT JOIN LATERAL (SELECT - shipping_info1.id AS id, - shipping_info1.buyer_id AS buyer_id, - shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 ON ? - ``` - - - -* - ```scala - Seq( - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) - ), - (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), None) - ) - ``` - - - -### LateralJoin.leftJoinFor - -ScalaSql supports `LEFT JOIN`s, `RIGHT JOIN`s and `OUTER JOIN`s via the -`.leftJoin`/`.rightJoin`/`.outerJoin` methods - -```scala -for { - b <- Buyer.select - s <- ShippingInfo.select.filter(b.id `=` _.buyerId).leftJoinLateral(_ => Expr(true)) -} yield (b, s) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - subquery1.id AS res_1_id, - subquery1.buyer_id AS res_1_buyer_id, - subquery1.shipping_date AS res_1_shipping_date - FROM buyer buyer0 - LEFT JOIN LATERAL (SELECT - shipping_info1.id AS id, - shipping_info1.buyer_id AS buyer_id, - shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 ON ? - ``` - - - -* - ```scala - Seq( - ( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) - ), - ( - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) - ), - (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), None) - ) - ``` - - - -## WindowFunction -Window functions using `OVER` -### WindowFunction.simple.rank - -Window functions like `rank()` are supported. You can use the `.over`, `.partitionBy`, -and `.sortBy` - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.rank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Int)]( - (1, 15.7, 1), - (1, 888.0, 2), - (1, 900.0, 3), - (2, 493.8, 1), - (2, 10000.0, 2), - (3, 1.3, 1), - (3, 44.4, 2) - ) - ``` - - - -### WindowFunction.simple.rowNumber - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.rowNumber().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - ROW_NUMBER() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Int)]( - (1, 15.7, 1), - (1, 888.0, 2), - (1, 900.0, 3), - (2, 493.8, 1), - (2, 10000.0, 2), - (3, 1.3, 1), - (3, 44.4, 2) - ) - ``` - - - -### WindowFunction.simple.denseRank - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.denseRank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - DENSE_RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Int)]( - (1, 15.7, 1), - (1, 888.0, 2), - (1, 900.0, 3), - (2, 493.8, 1), - (2, 10000.0, 2), - (3, 1.3, 1), - (3, 44.4, 2) - ) - ``` - - - ----- - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.denseRank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - DENSE_RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Int)]( - (1, 15.7, 1), - (1, 888.0, 2), - (1, 900.0, 3), - (2, 493.8, 1), - (2, 10000.0, 2), - (3, 1.3, 1), - (3, 44.4, 2) - ) - ``` - - - -### WindowFunction.simple.percentRank - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.percentRank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - PERCENT_RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 0.0), - (1, 888.0, 0.5), - (1, 900.0, 1.0), - (2, 493.8, 0.0), - (2, 10000.0, 1.0), - (3, 1.3, 0.0), - (3, 44.4, 1.0) - ) - ``` - - - -### WindowFunction.simple.cumeDist - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.cumeDist().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - CUME_DIST() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 0.3333333333333333), - (1, 888.0, 0.6666666666666666), - (1, 900.0, 1.0), - (2, 493.8, 0.5), - (2, 10000.0, 1.0), - (3, 1.3, 0.5), - (3, 44.4, 1.0) - ) - ``` - - - -### WindowFunction.simple.ntile - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.ntile(3).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - NTILE(?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Int)]( - (1, 15.7, 1), - (1, 888.0, 2), - (1, 900.0, 3), - (2, 493.8, 1), - (2, 10000.0, 2), - (3, 1.3, 1), - (3, 44.4, 2) - ) - ``` - - - -### WindowFunction.simple.lag - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.lag(p.total, 1, -1.0).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - LAG(purchase0.total, ?, ?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, -1.0), - (1, 888.0, 15.7), - (1, 900.0, 888.0), - (2, 493.8, -1.0), - (2, 10000.0, 493.8), - (3, 1.3, -1.0), - (3, 44.4, 1.3) - ) - ``` - - - -### WindowFunction.simple.lead - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.lead(p.total, 1, -1.0).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - LEAD(purchase0.total, ?, ?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 888.0), - (1, 888.0, 900.0), - (1, 900.0, -1.0), - (2, 493.8, 10000.0), - (2, 10000.0, -1.0), - (3, 1.3, 44.4), - (3, 44.4, -1.0) - ) - ``` - - - -### WindowFunction.simple.firstValue - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.firstValue(p.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - FIRST_VALUE(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 15.7), - (1, 888.0, 15.7), - (1, 900.0, 15.7), - (2, 493.8, 493.8), - (2, 10000.0, 493.8), - (3, 1.3, 1.3), - (3, 44.4, 1.3) - ) - ``` - - - -### WindowFunction.simple.lastValue - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.lastValue(p.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - LAST_VALUE(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 15.7), - (1, 888.0, 888.0), - (1, 900.0, 900.0), - (2, 493.8, 493.8), - (2, 10000.0, 10000.0), - (3, 1.3, 1.3), - (3, 44.4, 44.4) - ) - ``` - - - -### WindowFunction.simple.nthValue - - - -```scala -Purchase.select.map(p => - ( - p.shippingInfoId, - p.total, - db.nthValue(p.total, 2).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - NTH_VALUE(purchase0.total, ?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 0.0), - (1, 888.0, 888.0), - (1, 900.0, 888.0), - (2, 493.8, 0.0), - (2, 10000.0, 10000.0), - (3, 1.3, 0.0), - (3, 44.4, 44.4) - ) - ``` - - - -### WindowFunction.aggregate.sumBy - -You can use `.mapAggregate` to use aggregate functions as window function - -```scala -Purchase.select.mapAggregate((p, ps) => - ( - p.shippingInfoId, - p.total, - ps.sumBy(_.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - SUM(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq( - (1, 15.7, 15.7), - (1, 888.0, 903.7), - (1, 900.0, 1803.7), - (2, 493.8, 493.8), - (2, 10000.0, 10493.8), - (3, 1.3, 1.3), - (3, 44.4, 45.699999999999996) - ) - ``` - - - -### WindowFunction.aggregate.avgBy - -Window functions like `rank()` are supported. You can use the `.over`, `.partitionBy`, -and `.sortBy` - -```scala -Purchase.select.mapAggregate((p, ps) => - ( - p.shippingInfoId, - p.total, - ps.avgBy(_.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - AVG(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq( - (1, 15.7, 15.7), - (1, 888.0, 451.85), - (1, 900.0, 601.2333333333333), - (2, 493.8, 493.8), - (2, 10000.0, 5246.9), - (3, 1.3, 1.3), - (3, 44.4, 22.849999999999998) - ) - ``` - - - -### WindowFunction.frames - -You can have further control over the window function call via `.frameStart`, -`.frameEnd`, `.exclude` - -```scala -Purchase.select.mapAggregate((p, ps) => - ( - p.shippingInfoId, - p.total, - ps.sumBy(_.total) - .over - .partitionBy(p.shippingInfoId) - .sortBy(p.total) - .asc - .frameStart - .preceding() - .frameEnd - .following() - .exclude - .currentRow - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - SUM(purchase0.total) - OVER (PARTITION BY purchase0.shipping_info_id - ORDER BY purchase0.total ASC - ROWS BETWEEN UNBOUNDED PRECEDING - AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 1788.0), - (1, 888.0, 915.7), - (1, 900.0, 903.7), - (2, 493.8, 10000.0), - (2, 10000.0, 493.8), - (3, 1.3, 44.4), - (3, 44.4, 1.3) - ) - ``` - - - -### WindowFunction.filter - -ScalaSql allows `.filter` to be used after `over` to add a SQL `FILTER` clause -to your window function call, allowing you to exclude certain rows from the -window. - -```scala -Purchase.select.mapAggregate((p, ps) => - ( - p.shippingInfoId, - p.total, - ps.sumBy(_.total) - .over - .filter(p.total > 100) - .partitionBy(p.shippingInfoId) - .sortBy(p.total) - .asc - ) -) -``` - - -* - ```sql - SELECT - purchase0.shipping_info_id AS res_0, - purchase0.total AS res_1, - SUM(purchase0.total) - FILTER (WHERE (purchase0.total > ?)) - OVER (PARTITION BY purchase0.shipping_info_id - ORDER BY purchase0.total ASC) AS res_2 - FROM purchase purchase0 - ``` - - - -* - ```scala - Seq[(Int, Double, Double)]( - (1, 15.7, 0.0), - (1, 888.0, 888.0), - (1, 900.0, 1788.0), - (2, 493.8, 493.8), - (2, 10000.0, 10493.8), - (3, 1.3, 0.0), - (3, 44.4, 0.0) - ) - ``` - - - -## GetGeneratedKeys -`INSERT` operations with `.getGeneratedKeys`. Not supported by Sqlite (see https://github.com/xerial/sqlite-jdbc/issues/980) -### GetGeneratedKeys.single.values - -`getGeneratedKeys` on an `insert` returns the primary key, even if it was provided - explicitly. - -```scala -Buyer.insert - .values( - Buyer[Sc](17, "test buyer", LocalDate.parse("2023-09-09")) - ) - .getGeneratedKeys[Int] -``` - - -* - ```sql - INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) - ``` - - - -* - ```scala - Seq(17) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](17, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### GetGeneratedKeys.single.columns - -All styles of `INSERT` query support `.getGeneratedKeys`, with this example -using `insert.columns` rather than `insert.values`. You can also retrieve -the generated primary keys using any compatible type, here shown using `Long` -rather than `Int` - -```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 4 - ) - .getGeneratedKeys[Long] -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) - ``` - - - -* - ```scala - Seq(4L) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### GetGeneratedKeys.single.partial - -If the primary key was not provided but was auto-generated by the database, -`getGeneratedKeys` returns the generated value - -```scala -Buyer.insert - .columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09")) - .getGeneratedKeys[Int] -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) - ``` - - - -* - ```scala - Seq(4) - ``` - - - ----- - - - -```scala -Buyer.select.filter(_.name `=` "test buyer") -``` - - - - -* - ```scala - Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))) - ``` - - - -### GetGeneratedKeys.batch.partial - -`getGeneratedKeys` can return multiple generated primary key values for -a batch insert statement - -```scala -Buyer.insert - .batched(_.name, _.dateOfBirth)( - ("test buyer A", LocalDate.parse("2001-04-07")), - ("test buyer B", LocalDate.parse("2002-05-08")), - ("test buyer C", LocalDate.parse("2003-06-09")) - ) - .getGeneratedKeys[Int] -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) - VALUES (?, ?), (?, ?), (?, ?) - ``` - - - -* - ```scala - Seq(4, 5, 6) - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5,6 comes from auto increment - Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), - Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), - Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) - ) - ``` - - - -### GetGeneratedKeys.select.simple - -`getGeneratedKeys` can return multiple generated primary key values for -an `insert` based on a `select` - -```scala -Buyer.insert - .select( - x => (x.name, x.dateOfBirth), - Buyer.select.map(x => (x.name, x.dateOfBirth)).filter(_._1 <> "Li Haoyi") - ) - .getGeneratedKeys[Int] -``` - - -* - ```sql - INSERT INTO buyer (name, date_of_birth) - SELECT buyer0.name AS res_0, buyer0.date_of_birth AS res_1 - FROM buyer buyer0 - WHERE (buyer0.name <> ?) - ``` - - - -* - ```scala - Seq(4, 5) - ``` - - - ----- - - - -```scala -Buyer.select -``` - - - - -* - ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), - // id=4,5 comes from auto increment, 6 is filtered out in the select - Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), - Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) - ) - ``` - - - -## Schema - - If your table belongs to a schema other than the default schema of your database, you can specify this in your table definition with - `override def schemaName = "otherschema"` - -### Schema.schema.select - - - -```scala -Invoice.select -``` - - -* - ```sql - SELECT invoice0.id AS id, invoice0.total AS total, invoice0.vendor_name AS vendor_name - FROM otherschema.invoice invoice0 - ``` - - - -* - ```scala - Seq( - Invoice[Sc](id = 1, total = 150.4, vendor_name = "Siemens"), - Invoice[Sc](id = 2, total = 213.3, vendor_name = "Samsung"), - Invoice[Sc](id = 3, total = 407.2, vendor_name = "Shell") - ) - ``` - - - -### Schema.schema.insert.columns - - - -```scala -Invoice.insert.columns( - _.total := 200.3, - _.vendor_name := "Huawei" -) -``` - - -* - ```sql - INSERT INTO otherschema.invoice (total, vendor_name) VALUES (?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - -### Schema.schema.insert.values - - - -```scala -Invoice.insert - .values( - Invoice[Sc]( - id = 0, - total = 200.3, - vendor_name = "Huawei" - ) - ) - .skipColumns(_.id) -``` - - -* - ```sql - INSERT INTO otherschema.invoice (total, vendor_name) VALUES (?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - -### Schema.schema.update - - - -```scala -Invoice - .update(_.id === 1) - .set( - _.total := 200.3, - _.vendor_name := "Huawei" - ) -``` - - -* - ```sql - UPDATE otherschema.invoice - SET - total = ?, - vendor_name = ? - WHERE - (invoice.id = ?) - ``` - - - -* - ```scala - 1 - ``` - - - -### Schema.schema.delete - - - -```scala -Invoice.delete(_.id === 1) -``` - - -* - ```sql - DELETE FROM otherschema.invoice WHERE (invoice.id = ?) - ``` - - - -* - ```scala - 1 - ``` - - - -### Schema.schema.insert into - - - -```scala -Invoice.insert.select( - i => (i.total, i.vendor_name), - Invoice.select.map(i => (i.total, i.vendor_name)) -) -``` - - -* - ```sql - INSERT INTO - otherschema.invoice (total, vendor_name) - SELECT - invoice0.total AS res_0, - invoice0.vendor_name AS res_1 - FROM - otherschema.invoice invoice0 - ``` - - - -* - ```scala - 4 - ``` - - - -### Schema.schema.join - - - -```scala -Invoice.select.join(Invoice)(_.id `=` _.id).map(_._1.id) -``` - - -* - ```sql - SELECT - invoice0.id AS res - FROM - otherschema.invoice invoice0 - JOIN otherschema.invoice invoice1 ON (invoice0.id = invoice1.id) - ``` - - - -* - ```scala - Seq(2, 3, 4, 5, 6, 7, 8, 9) - ``` - - - -## EscapedTableName - - If your table name is a reserved sql world, e.g. `order`, you can specify this in your table definition with - `override def escape = true` - -### EscapedTableName.escape table name.select - - - -```scala -Select.select -``` - - -* - ```sql - SELECT select0.id AS id, select0.name AS name - FROM "select" select0 - ``` - - - -* - ```scala - Seq.empty[Select[Sc]] - ``` - - - -### EscapedTableName.escape table name.select with filter - - - -```scala -Select.select.filter(_.id `=` 1) -``` - - -* - ```sql - SELECT select0.id AS id, select0.name AS name - FROM "select" select0 - WHERE (select0.id = ?) - ``` - - - -* - ```scala - Seq.empty[Select[Sc]] - ``` - - - -### EscapedTableName.escape table name.delete - - - -```scala -Select.delete(_ => true) -``` - - -* - ```sql - DELETE FROM "select" WHERE ? - ``` - - - -* - ```scala - 0 - ``` - - - -### EscapedTableName.escape table name.join - - - -```scala -Select.select.join(Select)(_.id `=` _.id) -``` - - -* - ```sql - SELECT - select0.id AS res_0_id, - select0.name AS res_0_name, - select1.id AS res_1_id, - select1.name AS res_1_name - FROM - "select" select0 - JOIN "select" select1 ON (select0.id = select1.id) - ``` - - - -* - ```scala - Seq.empty[(Select[Sc], Select[Sc])] - ``` - - - -### EscapedTableName.escape table name.update - - - -```scala -Select.update(_ => true).set(_.name := "hello") -``` - - -* - ```sql - UPDATE "select" SET name = ? - ``` - - - -* - ```scala - 0 - ``` - - - -### EscapedTableName.escape table name.update where - - - -```scala -Select.update(_.id `=` 1).set(_.name := "hello") -``` - - -* - ```sql - UPDATE "select" SET name = ? WHERE ("select".id = ?) - ``` - - - -* - ```scala - 0 - ``` - - - -### EscapedTableName.escape table name.insert - - - -```scala -Select.insert.values( - Select[Sc]( - id = 0, - name = "hello" - ) -) -``` - - -* - ```sql - INSERT INTO "select" (id, name) VALUES (?, ?) - ``` - - - -* - ```scala - 1 - ``` - - - -## EscapedTableNameWithReturning - - If your table name is a reserved sql world, e.g. `order`, you can specify this in your table definition with - `override def escape = true` - -### EscapedTableNameWithReturning.insert with returning - - - -```scala -Select.insert - .values( - Select[Sc]( - id = 0, - name = "hello" - ) - ) - .returning(_.id) -``` - - -* - ```sql - INSERT INTO "select" (id, name) VALUES (?, ?) RETURNING "select".id AS res - ``` - - - -* - ```scala - Seq(0) - ``` - - - -## SubQuery -Queries that explicitly use subqueries (e.g. for `JOIN`s) or require subqueries to preserve the Scala semantics of the various operators -### SubQuery.sortTakeJoin - -A ScalaSql `.join` referencing a `.select` translates straightforwardly -into a SQL `JOIN` on a subquery - -```scala -Purchase.select - .join(Product.select.sortBy(_.price).desc.take(1))(_.productId `=` _.id) - .map { case (purchase, product) => purchase.total } -``` - - -* - ```sql - SELECT purchase0.total AS res - FROM purchase purchase0 - JOIN (SELECT product1.id AS id, product1.price AS price - FROM product product1 - ORDER BY price DESC - LIMIT ?) subquery1 - ON (purchase0.product_id = subquery1.id) - ``` - - - -* - ```scala - Seq(10000.0) - ``` - - - -### SubQuery.sortTakeFrom - -Some sequences of operations cannot be expressed as a single SQL query, -and thus translate into an outer query wrapping a subquery inside the `FROM`. -An example of this is performing a `.join` after a `.take`: SQL does not -allow you to put `JOIN`s after `LIMIT`s, and so the only way to write this -in SQL is as a subquery. - -```scala -Product.select.sortBy(_.price).desc.take(1).join(Purchase)(_.id `=` _.productId).map { - case (product, purchase) => purchase.total -} -``` - - -* - ```sql - SELECT purchase1.total AS res - FROM (SELECT product0.id AS id, product0.price AS price - FROM product product0 - ORDER BY price DESC - LIMIT ?) subquery0 - JOIN purchase purchase1 ON (subquery0.id = purchase1.product_id) - ``` - - - -* - ```scala - Seq(10000.0) - ``` - - - -### SubQuery.sortTakeFromAndJoin - -This example shows a ScalaSql query that results in a subquery in both -the `FROM` and the `JOIN` clause of the generated SQL query. - -```scala -Product.select - .sortBy(_.price) - .desc - .take(3) - .join(Purchase.select.sortBy(_.count).desc.take(3))(_.id `=` _.productId) - .map { case (product, purchase) => (product.name, purchase.count) } -``` - - -* - ```sql - SELECT - subquery0.name AS res_0, - subquery1.count AS res_1 - FROM (SELECT - product0.id AS id, - product0.name AS name, - product0.price AS price - FROM product product0 - ORDER BY price DESC - LIMIT ?) subquery0 - JOIN (SELECT - purchase1.product_id AS product_id, - purchase1.count AS count - FROM purchase purchase1 - ORDER BY count DESC - LIMIT ?) subquery1 - ON (subquery0.id = subquery1.product_id) - ``` - - - -* - ```scala - Seq(("Camera", 10)) - ``` - - - -### SubQuery.sortLimitSortLimit - -Performing multiple sorts with `.take`s in between is also something -that requires subqueries, as a single query only allows a single `LIMIT` -clause after the `ORDER BY` - -```scala -Product.select.sortBy(_.price).desc.take(4).sortBy(_.price).asc.take(2).map(_.name) -``` - - -* - ```sql - SELECT subquery0.name AS res - FROM (SELECT - product0.name AS name, - product0.price AS price - FROM product product0 - ORDER BY price DESC - LIMIT ?) subquery0 - ORDER BY subquery0.price ASC - LIMIT ? - ``` - - - -* - ```scala - Seq("Face Mask", "Skate Board") - ``` - - - -### SubQuery.sortGroupBy - - - -```scala -Purchase.select.sortBy(_.count).take(5).groupBy(_.productId)(_.sumBy(_.total)) -``` - - -* - ```sql - SELECT subquery0.product_id AS res_0, SUM(subquery0.total) AS res_1 - FROM (SELECT - purchase0.product_id AS product_id, - purchase0.count AS count, - purchase0.total AS total - FROM purchase purchase0 - ORDER BY count - LIMIT ?) subquery0 - GROUP BY subquery0.product_id - ``` - - - -* - ```scala - Seq((1, 44.4), (2, 900.0), (3, 15.7), (4, 493.8), (5, 10000.0)) - ``` - - - -### SubQuery.groupByJoin - - - -```scala -Purchase.select.groupBy(_.productId)(_.sumBy(_.total)).join(Product)(_._1 `=` _.id).map { - case (productId, total, product) => (product.name, total) -} -``` - - -* - ```sql - SELECT - product1.name AS res_0, - subquery0.res_1 AS res_1 - FROM (SELECT - purchase0.product_id AS res_0, - SUM(purchase0.total) AS res_1 - FROM purchase purchase0 - GROUP BY purchase0.product_id) subquery0 - JOIN product product1 ON (subquery0.res_0 = product1.id) - ``` - - - -* - ```scala - Seq( - ("Camera", 10000.0), - ("Cookie", 1.3), - ("Face Mask", 932.4), - ("Guitar", 900.0), - ("Skate Board", 493.8), - ("Socks", 15.7) - ) - ``` - - - -### SubQuery.subqueryInFilter - -You can use `.select`s and aggregate operations like `.size` anywhere an expression is -expected; these translate into SQL subqueries as expressions. SQL -subqueries-as-expressions require that the subquery returns exactly 1 row and 1 column, -which is something the aggregate operation (in this case `.sum`/`COUNT(1)`) helps us -ensure. Here, we do subquery in a `.filter`/`WHERE`. - -```scala -Buyer.select.filter(c => ShippingInfo.select.filter(p => c.id `=` p.buyerId).size `=` 0) -``` - - -* - ```sql - SELECT - buyer0.id AS id, - buyer0.name AS name, - buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - WHERE ((SELECT - COUNT(1) AS res - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) = ?) - ``` - - - -* - ```scala - Seq(Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))) - ``` - - - -### SubQuery.subqueryInMap - -Similar to the above example, but we do the subquery/aggregate in -a `.map` instead of a `.filter` - -```scala -Buyer.select.map(c => (c, ShippingInfo.select.filter(p => c.id `=` p.buyerId).size)) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - (SELECT COUNT(1) AS res - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) AS res_1 - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq( - (Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), 1), - (Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), 2), - (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), 0) - ) - ``` - - - -### SubQuery.subqueryInMapNested - - - -```scala -Buyer.select.map(c => (c, ShippingInfo.select.filter(p => c.id `=` p.buyerId).size `=` 1)) -``` - - -* - ```sql - SELECT - buyer0.id AS res_0_id, - buyer0.name AS res_0_name, - buyer0.date_of_birth AS res_0_date_of_birth, - ((SELECT - COUNT(1) AS res - FROM shipping_info shipping_info1 - WHERE (buyer0.id = shipping_info1.buyer_id)) = ?) AS res_1 - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq( - (Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), true), - (Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), false), - (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), false) - ) - ``` - - - -### SubQuery.selectLimitUnionSelect - - - -```scala -Buyer.select - .map(_.name.toLowerCase) - .take(2) - .unionAll(Product.select.map(_.kebabCaseName.toLowerCase)) -``` - - -* - ```sql - SELECT subquery0.res AS res - FROM (SELECT - LOWER(buyer0.name) AS res - FROM buyer buyer0 - LIMIT ?) subquery0 - UNION ALL - SELECT LOWER(product0.kebab_case_name) AS res - FROM product product0 - ``` - - - -* - ```scala - Seq("james bond", "叉烧包", "face-mask", "guitar", "socks", "skate-board", "camera", "cookie") - ``` - - - -### SubQuery.selectUnionSelectLimit - - - -```scala -Buyer.select - .map(_.name.toLowerCase) - .unionAll(Product.select.map(_.kebabCaseName.toLowerCase).take(2)) -``` - - -* - ```sql - SELECT LOWER(buyer0.name) AS res - FROM buyer buyer0 - UNION ALL - SELECT subquery0.res AS res - FROM (SELECT - LOWER(product0.kebab_case_name) AS res - FROM product product0 - LIMIT ?) subquery0 - ``` - - - -* - ```scala - Seq("james bond", "叉烧包", "li haoyi", "face-mask", "guitar") - ``` - - - -### SubQuery.exceptAggregate - - - -```scala -Product.select - .map(p => (p.name.toLowerCase, p.price)) - // `p.name.toLowerCase` and `p.kebabCaseName.toLowerCase` are not eliminated, because - // they are important to the semantics of EXCEPT (and other non-UNION-ALL operators) - .except(Product.select.map(p => (p.kebabCaseName.toLowerCase, p.price))) - .aggregate(ps => (ps.maxBy(_._2), ps.minBy(_._2))) -``` - - -* - ```sql - SELECT - MAX(subquery0.res_1) AS res_0, - MIN(subquery0.res_1) AS res_1 - FROM (SELECT - LOWER(product0.name) AS res_0, - product0.price AS res_1 - FROM product product0 - EXCEPT - SELECT - LOWER(product0.kebab_case_name) AS res_0, - product0.price AS res_1 - FROM product product0) subquery0 - ``` - - - -* - ```scala - (123.45, 8.88) - ``` - - - -### SubQuery.unionAllAggregate - - - -```scala -Product.select - .map(p => (p.name.toLowerCase, p.price)) - // `p.name.toLowerCase` and `p.kebabCaseName.toLowerCase` get eliminated, - // as they are not selected by the enclosing query, and cannot affect the UNION ALL - .unionAll(Product.select.map(p => (p.kebabCaseName.toLowerCase, p.price))) - .aggregate(ps => (ps.maxBy(_._2), ps.minBy(_._2))) -``` - - -* - ```sql - SELECT - MAX(subquery0.res_1) AS res_0, - MIN(subquery0.res_1) AS res_1 - FROM (SELECT product0.price AS res_1 - FROM product product0 - UNION ALL - SELECT product0.price AS res_1 - FROM product product0) subquery0 - ``` - - - -* - ```scala - (1000.0, 0.1) - ``` - - - -### SubQuery.deeplyNested - -Subqueries can be arbitrarily nested. This example traverses four tables -to find the price of the most expensive product bought by each Buyer, but -instead of using `JOIN`s it uses subqueries nested 4 layers deep. While this -example is contrived, it demonstrates how nested ScalaSql `.select` calls -translate directly into nested SQL subqueries. - -To turn the ScalaSql `Select[T]` into an `Expr[T]`, you can either use -an aggregate method like `.sumBy(...): Expr[Int]` that generates a `SUM(...)` -aggregate, or via the `.toExpr` method that leaves the subquery untouched. -SQL requires that subqueries used as expressions must return a single row -and single column, and if the query returns some other number of rows/columns -most databases will throw an exception, though some like Sqlite will pick -the first row/column arbitrarily. - -```scala -Buyer.select.map { buyer => - buyer.name -> - ShippingInfo.select - .filter(_.buyerId === buyer.id) - .map { shippingInfo => - Purchase.select - .filter(_.shippingInfoId === shippingInfo.id) - .map { purchase => - Product.select - .filter(_.id === purchase.productId) - .map(_.price) - .sorted - .desc - .take(1) - .toExpr - } - .sorted - .desc - .take(1) - .toExpr - } - .sorted - .desc - .take(1) - .toExpr -} -``` - - -* - ```sql - SELECT - buyer0.name AS res_0, - (SELECT - (SELECT - (SELECT product3.price AS res - FROM product product3 - WHERE (product3.id = purchase2.product_id) - ORDER BY res DESC - LIMIT ?) AS res - FROM purchase purchase2 - WHERE (purchase2.shipping_info_id = shipping_info1.id) - ORDER BY res DESC - LIMIT ?) AS res - FROM shipping_info shipping_info1 - WHERE (shipping_info1.buyer_id = buyer0.id) - ORDER BY res DESC - LIMIT ?) AS res_1 - FROM buyer buyer0 - ``` - - - -* - ```scala - Seq( - ("James Bond", 1000.0), - ("叉烧包", 300.0), - ("Li Haoyi", 0.0) - ) - ``` - - - -## WithCte -Basic `WITH`/Common-Table-Expression operations -### WithCte.simple - -ScalaSql supports `WITH`-clauses, also known as "Common Table Expressions" -(CTEs), via the `.withCte` syntax. - -```scala -db.withCte(Buyer.select.map(_.name)) { bs => - bs.map(_ + "-suffix") -} -``` - - -* - ```sql - WITH cte0 (res) AS (SELECT buyer0.name AS res FROM buyer buyer0) - SELECT (cte0.res || ?) AS res - FROM cte0 - ``` - - - -* - ```scala - Seq("James Bond-suffix", "叉烧包-suffix", "Li Haoyi-suffix") - ``` - - - -### WithCte.multiple - -Multiple `withCte` blocks can be stacked, turning into chained `WITH` clauses -in the generated SQL - -```scala -db.withCte(Buyer.select) { bs => - db.withCte(ShippingInfo.select) { sis => - bs.join(sis)(_.id === _.buyerId) - .map { case (b, s) => (b.name, s.shippingDate) } - } -} -``` - - -* - ```sql - WITH - cte0 (id, name) AS (SELECT - buyer0.id AS id, buyer0.name AS name FROM buyer buyer0), - cte1 (buyer_id, shipping_date) AS (SELECT - shipping_info1.buyer_id AS buyer_id, - shipping_info1.shipping_date AS shipping_date - FROM shipping_info shipping_info1) - SELECT cte0.name AS res_0, cte1.shipping_date AS res_1 - FROM cte0 - JOIN cte1 ON (cte0.id = cte1.buyer_id) - ``` - - - -* - ```scala - Seq( - ("叉烧包", LocalDate.parse("2010-02-03")), - ("James Bond", LocalDate.parse("2012-04-05")), - ("叉烧包", LocalDate.parse("2012-05-06")) - ) - ``` - - - -### WithCte.eliminated - -Only the necessary columns are exported from the `WITH` clause; columns that -are un-used in the downstream `SELECT` clause are eliminated - -```scala -db.withCte(Buyer.select) { bs => - bs.map(_.name + "-suffix") -} -``` - - -* - ```sql - WITH cte0 (name) AS (SELECT buyer0.name AS name FROM buyer buyer0) - SELECT (cte0.name || ?) AS res - FROM cte0 - ``` - - - -* - ```scala - Seq("James Bond-suffix", "叉烧包-suffix", "Li Haoyi-suffix") - ``` - - - -### WithCte.subquery - -ScalaSql's `withCte` can be used anywhere a `.select` operator can be used. The -generated `WITH` clauses may be wrapped in sub-queries in scenarios where they -cannot be easily combined into a single query - -```scala -db.withCte(Buyer.select) { bs => - db.withCte(ShippingInfo.select) { sis => - bs.join(sis)(_.id === _.buyerId) - } -}.join( - db.withCte(Product.select) { prs => - Purchase.select.join(prs)(_.productId === _.id) - } -)(_._2.id === _._1.shippingInfoId) - .map { case (b, s, (pu, pr)) => (b.name, pr.name) } -``` - - -* - ```sql - SELECT subquery0.res_0_name AS res_0, subquery1.res_1_name AS res_1 - FROM (WITH - cte0 (id, name) - AS (SELECT buyer0.id AS id, buyer0.name AS name FROM buyer buyer0), - cte1 (id, buyer_id) - AS (SELECT shipping_info1.id AS id, shipping_info1.buyer_id AS buyer_id - FROM shipping_info shipping_info1) - SELECT cte0.name AS res_0_name, cte1.id AS res_1_id - FROM cte0 - JOIN cte1 ON (cte0.id = cte1.buyer_id)) subquery0 - JOIN (WITH - cte1 (id, name) - AS (SELECT product1.id AS id, product1.name AS name FROM product product1) - SELECT - purchase2.shipping_info_id AS res_0_shipping_info_id, - cte1.name AS res_1_name - FROM purchase purchase2 - JOIN cte1 ON (purchase2.product_id = cte1.id)) subquery1 - ON (subquery0.res_1_id = subquery1.res_0_shipping_info_id) - ``` - - - -* - ```scala - Seq[(String, String)]( - ("James Bond", "Camera"), - ("James Bond", "Skate Board"), - ("叉烧包", "Cookie"), - ("叉烧包", "Face Mask"), - ("叉烧包", "Face Mask"), - ("叉烧包", "Guitar"), - ("叉烧包", "Socks") - ) - ``` - - - -## ExprOps -Operations that can be performed on `Expr[T]` for any `T` -### ExprOps.numeric.greaterThan - - - -```scala -Expr(6) > Expr(2) -``` - - -* - ```sql - SELECT (? > ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.numeric.lessThan - - - -```scala -Expr(6) < Expr(2) -``` - - -* - ```sql - SELECT (? < ?) AS res - ``` - - - -* - ```scala - false - ``` - - - -### ExprOps.numeric.greaterThanOrEquals - - - -```scala -Expr(6) >= Expr(2) -``` - - -* - ```sql - SELECT (? >= ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.numeric.lessThanOrEquals - - - -```scala -Expr(6) <= Expr(2) -``` - - -* - ```sql - SELECT (? <= ?) AS res - ``` - - - -* - ```scala - false - ``` - - - -### ExprOps.string.greaterThan - - - -```scala -Expr("A") > Expr("B") -``` - - -* - ```sql - SELECT (? > ?) AS res - ``` - - - -* - ```scala - false - ``` - - - -### ExprOps.string.lessThan - - - -```scala -Expr("A") < Expr("B") -``` - - -* - ```sql - SELECT (? < ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.string.greaterThanOrEquals - - - -```scala -Expr("A") >= Expr("B") -``` - - -* - ```sql - SELECT (? >= ?) AS res - ``` - - - -* - ```scala - false - ``` - - - -### ExprOps.string.lessThanOrEquals - - - -```scala -Expr("A") <= Expr("B") -``` - - -* - ```sql - SELECT (? <= ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.boolean.greaterThan - - - -```scala -Expr(true) > Expr(false) -``` - - -* - ```sql - SELECT (? > ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.boolean.lessThan - - - -```scala -Expr(true) < Expr(true) -``` - - -* - ```sql - SELECT (? < ?) AS res - ``` - - - -* - ```scala - false - ``` - - - -### ExprOps.boolean.greaterThanOrEquals - - - -```scala -Expr(true) >= Expr(true) -``` - - -* - ```sql - SELECT (? >= ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.boolean.lessThanOrEquals - - - -```scala -Expr(true) <= Expr(true) -``` - - -* - ```sql - SELECT (? <= ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprOps.cast.byte - - - -```scala -Expr(45.12).cast[Byte] -``` - - -* - ```sql - SELECT CAST(? AS INTEGER) AS res - ``` - - - -* - ```scala - 45: Byte - ``` - - - -### ExprOps.cast.short - - - -```scala -Expr(1234.1234).cast[Short] -``` - - -* - ```sql - SELECT CAST(? AS SMALLINT) AS res - ``` - - - -* - ```scala - 1234: Short - ``` - - - -### ExprOps.cast.int - - - -```scala -Expr(1234.1234).cast[Int] -``` - - -* - ```sql - SELECT CAST(? AS INTEGER) AS res - ``` - - - -* - ```scala - 1234 - ``` - - - -### ExprOps.cast.long - - - -```scala -Expr(1234.1234).cast[Long] -``` - - -* - ```sql - SELECT CAST(? AS BIGINT) AS res - ``` - - - -* - ```scala - 1234L - ``` - - - -### ExprOps.cast.string - - - -```scala -Expr(1234.5678).cast[String] -``` - - -* - ```sql - SELECT CAST(? AS VARCHAR) AS res - ``` - - - -* - ```scala - "1234.5678" - ``` - - - -### ExprOps.cast.localdate - - - -```scala -Expr("2001-02-03").cast[java.time.LocalDate] -``` - - -* - ```sql - SELECT CAST(? AS DATE) AS res - ``` - - - -* - ```scala - java.time.LocalDate.parse("2001-02-03") - ``` - - - -### ExprOps.cast.localdatetime - - - -```scala -Expr("2023-11-12 03:22:41").cast[java.time.LocalDateTime] -``` - - -* - ```sql - SELECT CAST(? AS TIMESTAMP) AS res - ``` - - - -* - ```scala - java.time.LocalDateTime.parse("2023-11-12T03:22:41") - ``` - - - -### ExprOps.cast.utildate - - - -```scala -Expr("2023-11-12 03:22:41").cast[java.util.Date] -``` - - -* - ```sql - SELECT CAST(? AS TIMESTAMP) AS res - ``` - - - -* - ```scala - new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2023-11-12 03:22:41") - ``` - - - -### ExprOps.cast.instant - - - -```scala -Expr("2007-12-03 10:15:30.00").cast[java.time.Instant] -``` - - -* - ```sql - SELECT CAST(? AS TIMESTAMP) AS res - ``` - - - -* - ```scala - java.time.Instant.parse("2007-12-03T02:15:30.00Z") - ``` - - - -### ExprOps.cast.castNamed - - - -```scala -Expr(1234.5678).castNamed[String](sql"CHAR(3)") -``` - - -* - ```sql - SELECT CAST(? AS CHAR(3)) AS res - ``` - - - -* - ```scala - "123" - ``` - - - -## ExprBooleanOps -Operations that can be performed on `Expr[Boolean]` -### ExprBooleanOps.and - - - -```scala -Expr(true) && Expr(true) -``` - - -* - ```sql - SELECT (? AND ?) AS res - ``` - - - -* - ```scala - true - ``` - - - ----- - - - -```scala -Expr(false) && Expr(true) -``` - - -* - ```sql - SELECT (? AND ?) AS res - ``` - - - -* - ```scala - false - ``` - - - -### ExprBooleanOps.or - - - -```scala -Expr(false) || Expr(false) -``` - - -* - ```sql - SELECT (? OR ?) AS res - ``` - - - -* - ```scala - false - ``` - - - ----- - - - -```scala -!Expr(false) -``` - - -* - ```sql - SELECT (NOT ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -## ExprNumericOps -Operations that can be performed on `Expr[T]` when `T` is numeric -### ExprNumericOps.plus - - - -```scala -Expr(6) + Expr(2) -``` - - -* - ```sql - SELECT (? + ?) AS res - ``` - - - -* - ```scala - 8 - ``` - - - -### ExprNumericOps.minus - - - -```scala -Expr(6) - Expr(2) -``` - - -* - ```sql - SELECT (? - ?) AS res - ``` - - - -* - ```scala - 4 - ``` - - - -### ExprNumericOps.times - - - -```scala -Expr(6) * Expr(2) -``` - - -* - ```sql - SELECT (? * ?) AS res - ``` - - - -* - ```scala - 12 - ``` - - - -### ExprNumericOps.divide - - - -```scala -Expr(6) / Expr(2) -``` - - -* - ```sql - SELECT (? / ?) AS res - ``` - - - -* - ```scala - 3 - ``` - - - -### ExprNumericOps.modulo - - - -```scala -Expr(6) % Expr(2) -``` - - -* - ```sql - SELECT MOD(?, ?) AS res - ``` - - - -* - ```scala - 0 - ``` - - - -### ExprNumericOps.bitwiseAnd - - - -```scala -Expr(6) & Expr(2) -``` - - -* - ```sql - SELECT (? & ?) AS res - ``` - - - -* - ```scala - 2 - ``` - - - -### ExprNumericOps.bitwiseOr - - - -```scala -Expr(6) | Expr(3) -``` - - -* - ```sql - SELECT (? | ?) AS res - ``` - - - -* - ```scala - 7 - ``` - - - -### ExprNumericOps.between - - - -```scala -Expr(4).between(Expr(2), Expr(6)) -``` - - -* - ```sql - SELECT ? BETWEEN ? AND ? AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprNumericOps.unaryPlus - - - -```scala -+Expr(-4) -``` - - -* - ```sql - SELECT +? AS res - ``` - - - -* - ```scala - -4 - ``` - - - -### ExprNumericOps.unaryMinus - - - -```scala --Expr(-4) -``` - - -* - ```sql - SELECT -? AS res - ``` - - - -* - ```scala - 4 - ``` - - - -### ExprNumericOps.unaryTilde - - - -```scala -~Expr(-4) -``` - - -* - ```sql - SELECT ~? AS res - ``` - - - -* - ```scala - 3 - ``` - - - -### ExprNumericOps.abs - - - -```scala -Expr(-4).abs -``` - - -* - ```sql - SELECT ABS(?) AS res - ``` - - - -* - ```scala - 4 - ``` - - - -### ExprNumericOps.mod - - - -```scala -Expr(8).mod(Expr(3)) -``` - - -* - ```sql - SELECT MOD(?, ?) AS res - ``` - - - -* - ```scala - 2 - ``` - - - -### ExprNumericOps.ceil - - - -```scala -Expr(4.3).ceil -``` - - -* - ```sql - SELECT CEIL(?) AS res - ``` - - - -* - ```scala - 5.0 - ``` - - - -### ExprNumericOps.floor - - - -```scala -Expr(4.7).floor -``` - - -* - ```sql - SELECT FLOOR(?) AS res - ``` - - - -* - ```scala - 4.0 - ``` - - - -### ExprNumericOps.precedence - - - -```scala -(Expr(2) + Expr(3)) * Expr(4) -``` - - -* - ```sql - SELECT ((? + ?) * ?) AS res - ``` - - - -* - ```scala - 20 - ``` - - - -### ExprNumericOps.sign - - - -```scala -Expr(-100).sign -``` - - -* - ```sql - SELECT SIGN(?) AS res - ``` - - - -* - ```scala - -1 - ``` - - - -## ExprSeqNumericOps -Operations that can be performed on `Expr[Seq[T]]` where `T` is numeric -### ExprSeqNumericOps.sum - - - -```scala -Purchase.select.map(_.count).sum -``` - - -* - ```sql - SELECT SUM(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 140 - ``` - - - -### ExprSeqNumericOps.min - - - -```scala -Purchase.select.map(_.count).min -``` - - -* - ```sql - SELECT MIN(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 3 - ``` - - - -### ExprSeqNumericOps.max - - - -```scala -Purchase.select.map(_.count).max -``` - - -* - ```sql - SELECT MAX(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 100 - ``` - - - -### ExprSeqNumericOps.avg - - - -```scala -Purchase.select.map(_.count).avg -``` - - -* - ```sql - SELECT AVG(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 20 - ``` - - - -## ExprSeqOps -Operations that can be performed on `Expr[Seq[_]]` -### ExprSeqOps.size - - - -```scala -Purchase.select.size -``` - - -* - ```sql - SELECT COUNT(1) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 7 - ``` - - - -### ExprSeqOps.sumBy.simple - - - -```scala -Purchase.select.sumBy(_.count) -``` - - -* - ```sql - SELECT SUM(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 140 - ``` - - - -### ExprSeqOps.sumBy.some - - - -```scala -Purchase.select.sumByOpt(_.count) -``` - - -* - ```sql - SELECT SUM(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - Option(140) - ``` - - - -### ExprSeqOps.sumBy.none - - - -```scala -Purchase.select.filter(_ => false).sumByOpt(_.count) -``` - - -* - ```sql - SELECT SUM(purchase0.count) AS res FROM purchase purchase0 WHERE ? - ``` - - - -* - ```scala - Option.empty[Int] - ``` - - - -### ExprSeqOps.minBy.simple - - - -```scala -Purchase.select.minBy(_.count) -``` - - -* - ```sql - SELECT MIN(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 3 - ``` - - - -### ExprSeqOps.minBy.some - - - -```scala -Purchase.select.minByOpt(_.count) -``` - - -* - ```sql - SELECT MIN(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - Option(3) - ``` - - - -### ExprSeqOps.minBy.none - - - -```scala -Purchase.select.filter(_ => false).minByOpt(_.count) -``` - - -* - ```sql - SELECT MIN(purchase0.count) AS res FROM purchase purchase0 WHERE ? - ``` - - - -* - ```scala - Option.empty[Int] - ``` - - - -### ExprSeqOps.maxBy.simple - - - -```scala -Purchase.select.maxBy(_.count) -``` - - -* - ```sql - SELECT MAX(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 100 - ``` - - - -### ExprSeqOps.maxBy.some - - - -```scala -Purchase.select.maxByOpt(_.count) -``` - - -* - ```sql - SELECT MAX(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - Option(100) - ``` - - - -### ExprSeqOps.maxBy.none - - - -```scala -Purchase.select.filter(_ => false).maxByOpt(_.count) -``` - - -* - ```sql - SELECT MAX(purchase0.count) AS res FROM purchase purchase0 WHERE ? - ``` - - - -* - ```scala - Option.empty[Int] - ``` - - - -### ExprSeqOps.avgBy.simple - - - -```scala -Purchase.select.avgBy(_.count) -``` - - -* - ```sql - SELECT AVG(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - 20 - ``` - - - -### ExprSeqOps.avgBy.some - - - -```scala -Purchase.select.avgByOpt(_.count) -``` - - -* - ```sql - SELECT AVG(purchase0.count) AS res FROM purchase purchase0 - ``` - - - -* - ```scala - Option(20) - ``` - - - -### ExprSeqOps.avgBy.none - - - -```scala -Purchase.select.filter(_ => false).avgByOpt(_.count) -``` - - -* - ```sql - SELECT AVG(purchase0.count) AS res FROM purchase purchase0 WHERE ? - ``` - - - -* - ```scala - Option.empty[Int] - ``` - - - -### ExprSeqOps.mkString.simple - - - -```scala -Buyer.select.map(_.name).mkString() -``` - - -* - ```sql - SELECT STRING_AGG(buyer0.name || '', '') AS res FROM buyer buyer0 - ``` - - - -* - ```scala - "James Bond叉烧包Li Haoyi" - ``` - - - -### ExprSeqOps.mkString.sep - - - -```scala -Buyer.select.map(_.name).mkString(", ") -``` - - -* - ```sql - SELECT STRING_AGG(buyer0.name || '', ?) AS res FROM buyer buyer0 - ``` - - - -* - ```scala - "James Bond, 叉烧包, Li Haoyi" - ``` - - - -## ExprStringOps -Operations that can be performed on `Expr[String]` -### ExprStringOps.plus - - - -```scala -Expr("hello") + Expr("world") -``` - - -* - ```sql - SELECT (? || ?) AS res - ``` - - - -* - ```scala - "helloworld" - ``` - - - -### ExprStringOps.like - - - -```scala -Expr("hello").like("he%") -``` - - -* - ```sql - SELECT (? LIKE ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprStringOps.length - - - -```scala -Expr("hello").length -``` - - -* - ```sql - SELECT LENGTH(?) AS res - ``` - - - -* - ```scala - 5 - ``` - - - -### ExprStringOps.octetLength - - - -```scala -Expr("叉烧包").octetLength -``` - - -* - ```sql - SELECT OCTET_LENGTH(?) AS res - ``` - - - -* - ```scala - 9 - ``` - - - -### ExprStringOps.position - - - -```scala -Expr("hello").indexOf("ll") -``` - - -* - ```sql - SELECT POSITION(? IN ?) AS res - ``` - - - -* - ```scala - 3 - ``` - - - -### ExprStringOps.toLowerCase - - - -```scala -Expr("Hello").toLowerCase -``` - - -* - ```sql - SELECT LOWER(?) AS res - ``` - - - -* - ```scala - "hello" - ``` - - - -### ExprStringOps.trim - - - -```scala -Expr(" Hello ").trim -``` - - -* - ```sql - SELECT TRIM(?) AS res - ``` - - - -* - ```scala - "Hello" - ``` - - - -### ExprStringOps.ltrim - - - -```scala -Expr(" Hello ").ltrim -``` - - -* - ```sql - SELECT LTRIM(?) AS res - ``` - - - -* - ```scala - "Hello " - ``` - - - -### ExprStringOps.rtrim - - - -```scala -Expr(" Hello ").rtrim -``` - - -* - ```sql - SELECT RTRIM(?) AS res - ``` - - - -* - ```scala - " Hello" - ``` - - - -### ExprStringOps.substring - - - -```scala -Expr("Hello").substring(2, 2) -``` - - -* - ```sql - SELECT SUBSTRING(?, ?, ?) AS res - ``` - - - -* - ```scala - "el" - ``` - - - -### ExprStringOps.startsWith - - - -```scala -Expr("Hello").startsWith("Hel") -``` - - -* - ```sql - SELECT (? LIKE ? || '%') AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprStringOps.endsWith - - - -```scala -Expr("Hello").endsWith("llo") -``` - - -* - ```sql - SELECT (? LIKE '%' || ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprStringOps.contains - - - -```scala -Expr("Hello").contains("ll") -``` - - -* - ```sql - SELECT (? LIKE '%' || ? || '%') AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprStringOps.replace - - - -```scala -Expr("Hello").replace("ll", "rr") -``` - - -* - ```sql - SELECT REPLACE(?, ?, ?) AS res - ``` - - - -* - ```scala - "Herro" - ``` - - - -## ExprBlobOps -Operations that can be performed on `Expr[Bytes]` -### ExprBlobOps.plus - - - -```scala -Expr(Bytes("hello")) + Expr(Bytes("world")) -``` - - -* - ```sql - SELECT (? || ?) AS res - ``` - - - -* - ```scala - Bytes("helloworld") - ``` - - - -### ExprBlobOps.like - - - -```scala -Expr(Bytes("hello")).like(Bytes("he%")) -``` - - -* - ```sql - SELECT (? LIKE ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprBlobOps.length - - - -```scala -Expr(Bytes("hello")).length -``` - - -* - ```sql - SELECT LENGTH(?) AS res - ``` - - - -* - ```scala - 5 - ``` - - - -### ExprBlobOps.octetLength - - - -```scala -Expr(Bytes("叉烧包")).octetLength -``` - - -* - ```sql - SELECT OCTET_LENGTH(?) AS res - ``` - - - -* - ```scala - 9 - ``` - - - -### ExprBlobOps.position - - - -```scala -Expr(Bytes("hello")).indexOf(Bytes("ll")) -``` - - -* - ```sql - SELECT POSITION(? IN ?) AS res - ``` - - - -* - ```scala - 3 - ``` - - - -### ExprBlobOps.substring - - - -```scala -Expr(Bytes("Hello")).substring(2, 2) -``` - - -* - ```sql - SELECT SUBSTRING(?, ?, ?) AS res - ``` - - - -* - ```scala - Bytes("el") - ``` - - - -### ExprBlobOps.startsWith - - - -```scala -Expr(Bytes("Hello")).startsWith(Bytes("Hel")) -``` - - -* - ```sql - SELECT (? LIKE ? || '%') AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprBlobOps.endsWith - - - -```scala -Expr(Bytes("Hello")).endsWith(Bytes("llo")) -``` - - -* - ```sql - SELECT (? LIKE '%' || ?) AS res - ``` - - - -* - ```scala - true - ``` - - - -### ExprBlobOps.contains - - - -```scala -Expr(Bytes("Hello")).contains(Bytes("ll")) -``` - - -* - ```sql - SELECT (? LIKE '%' || ? || '%') AS res - ``` - - - -* - ```scala - true - ``` - - - -## ExprMathOps -Math operations; supported by H2/Postgres/MySql, not supported by Sqlite -### ExprMathOps.power - - - -```scala -db.power(10, 3) -``` - - -* - ```sql - SELECT POWER(?, ?) AS res - ``` - - - -* - ```scala - 1000.0 - ``` - - - -### ExprMathOps.sqrt - - - -```scala -db.sqrt(9) -``` - - -* - ```sql - SELECT SQRT(?) AS res - ``` - - - -* - ```scala - 3.0 - ``` - - - -### ExprMathOps.ln - - - -```scala -db.ln(16.0) -``` - - -* - ```sql - SELECT LN(?) AS res - ``` - - - - - -### ExprMathOps.log - - - -```scala -db.log(2, 8) -``` - - -* - ```sql - SELECT LOG(?, ?) AS res - ``` - - - - - -### ExprMathOps.log10 - - - -```scala -db.log10(16.0) -``` - - -* - ```sql - SELECT LOG10(?) AS res - ``` - - - - - -### ExprMathOps.exp - - - -```scala -db.exp(16.0) -``` - - -* - ```sql - SELECT EXP(?) AS res - ``` - - - - - -### ExprMathOps.sin - - - -```scala -db.sin(16.0) -``` - - -* - ```sql - SELECT SIN(?) AS res - ``` - - - - - -### ExprMathOps.cos - - - -```scala -db.cos(16.0) -``` - - -* - ```sql - SELECT COS(?) AS res - ``` - - - - - -### ExprMathOps.tan - - - -```scala -db.tan(16.0) -``` - - -* - ```sql - SELECT TAN(?) AS res - ``` - - - - - -### ExprMathOps.asin - - - -```scala -db.asin(1.0) -``` - - -* - ```sql - SELECT ASIN(?) AS res - ``` - - - - - -### ExprMathOps.acos - - - -```scala -db.acos(1.0) -``` - - -* - ```sql - SELECT ACOS(?) AS res - ``` - - - - - -### ExprMathOps.atan - - - -```scala -db.atan(1.0) -``` - - -* - ```sql - SELECT ATAN(?) AS res - ``` - - - - - -### ExprMathOps.atan2 - - - -```scala -db.atan2(16.0, 23.0) -``` - - -* - ```sql - SELECT ATAN2(?, ?) AS res - ``` - - - - - -### ExprMathOps.pi - - - -```scala -db.pi -``` - - -* - ```sql - SELECT PI() AS res - ``` - - - - - -### ExprMathOps.degrees - - - -```scala -db.degrees(180) -``` - - -* - ```sql - SELECT DEGREES(?) AS res - ``` - - - - - -### ExprMathOps.radians - - - -```scala -db.radians(180) -``` - - -* - ```sql - SELECT RADIANS(?) AS res - ``` - - - - - -## DataTypes -Basic operations on all the data types that ScalaSql supports mapping between Database types and Scala types -### DataTypes.constant - -This example demonstrates a range of different data types being written -and read back via ScalaSQL - -```scala -object MyEnum extends Enumeration { - val foo, bar, baz = Value - - implicit def make: String => Value = withName -} -case class DataTypes[T[_]]( - myTinyInt: T[Byte], - mySmallInt: T[Short], - myInt: T[Int], - myBigInt: T[Long], - myDouble: T[Double], - myBoolean: T[Boolean], - myLocalDate: T[LocalDate], - myLocalTime: T[LocalTime], - myLocalDateTime: T[LocalDateTime], - myUtilDate: T[Date], - myInstant: T[Instant], - myVarBinary: T[geny.Bytes], - myUUID: T[java.util.UUID], - myEnum: T[MyEnum.Value] -) - -object DataTypes extends Table[DataTypes] - -val value = DataTypes[Sc]( - myTinyInt = 123.toByte, - mySmallInt = 12345.toShort, - myInt = 12345678, - myBigInt = 12345678901L, - myDouble = 3.14, - myBoolean = true, - myLocalDate = LocalDate.parse("2023-12-20"), - myLocalTime = LocalTime.parse("10:15:30"), - myLocalDateTime = LocalDateTime.parse("2011-12-03T10:15:30"), - myUtilDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2011-12-03T10:15:30.000"), - myInstant = Instant.parse("2011-12-03T10:15:30Z"), - myVarBinary = new geny.Bytes(Array[Byte](1, 2, 3, 4, 5, 6, 7, 8)), - myUUID = new java.util.UUID(1234567890L, 9876543210L), - myEnum = MyEnum.bar -) - -db.run( - DataTypes.insert.columns( - _.myTinyInt := value.myTinyInt, - _.mySmallInt := value.mySmallInt, - _.myInt := value.myInt, - _.myBigInt := value.myBigInt, - _.myDouble := value.myDouble, - _.myBoolean := value.myBoolean, - _.myLocalDate := value.myLocalDate, - _.myLocalTime := value.myLocalTime, - _.myLocalDateTime := value.myLocalDateTime, - _.myUtilDate := value.myUtilDate, - _.myInstant := value.myInstant, - _.myVarBinary := value.myVarBinary, - _.myUUID := value.myUUID, - _.myEnum := value.myEnum - ) -) ==> 1 - -db.run(DataTypes.select) ==> Seq(value) -``` - - - - - - -### DataTypes.nonRoundTrip - -In general, databases do not store timezones and offsets together with their timestamps: -"TIMESTAMP WITH TIMEZONE" is a lie and it actually stores UTC and renders to whatever -timezone the client queries it from. Thus values of type `OffsetDateTime` can preserve -their instant, but cannot be round-tripped preserving the offset. - -```scala -case class NonRoundTripTypes[T[_]]( - myZonedDateTime: T[ZonedDateTime], - myOffsetDateTime: T[OffsetDateTime] -) - -object NonRoundTripTypes extends Table[NonRoundTripTypes] - -val value = NonRoundTripTypes[Sc]( - myZonedDateTime = ZonedDateTime.parse("2011-12-03T10:15:30+01:00[Europe/Paris]"), - myOffsetDateTime = OffsetDateTime.parse("2011-12-03T10:15:30+00:00") -) - -def normalize(v: NonRoundTripTypes[Sc]) = v.copy[Sc]( - myZonedDateTime = v.myZonedDateTime.withZoneSameInstant(ZoneId.systemDefault), - myOffsetDateTime = v.myOffsetDateTime.withOffsetSameInstant(OffsetDateTime.now.getOffset) -) - -db.run( - NonRoundTripTypes.insert.columns( - _.myOffsetDateTime := value.myOffsetDateTime, - _.myZonedDateTime := value.myZonedDateTime - ) -) ==> 1 - -db.run(NonRoundTripTypes.select).map(normalize) ==> Seq(normalize(value)) -``` - - - - - - -### DataTypes.enclosing - -You can nest `case class`es in other `case class`es to DRY up common sets of -table columns. These nested `case class`es have their columns flattened out -into the enclosing `case class`'s columns, such that at the SQL level it is -all flattened out without nesting. - -```scala -// case class Nested[T[_]]( -// fooId: T[Int], -// myBoolean: T[Boolean], -// ) -// object Nested extends Table[Nested] -// -// case class Enclosing[T[_]]( -// barId: T[Int], -// myString: T[String], -// foo: Nested[T] -// ) -// object Enclosing extends Table[Enclosing] -val value1 = Enclosing[Sc]( - barId = 1337, - myString = "hello", - foo = Nested[Sc]( - fooId = 271828, - myBoolean = true - ) -) -val value2 = Enclosing[Sc]( - barId = 31337, - myString = "world", - foo = Nested[Sc]( - fooId = 1618, - myBoolean = false - ) -) - -val insertColumns = Enclosing.insert.columns( - _.barId := value1.barId, - _.myString := value1.myString, - _.foo.fooId := value1.foo.fooId, - _.foo.myBoolean := value1.foo.myBoolean -) -db.renderSql(insertColumns) ==> - "INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)" - -db.run(insertColumns) ==> 1 - -val insertValues = Enclosing.insert.values(value2) -db.renderSql(insertValues) ==> - "INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)" - -db.run(insertValues) ==> 1 - -db.renderSql(Enclosing.select) ==> """ - SELECT - enclosing0.bar_id AS bar_id, - enclosing0.my_string AS my_string, - enclosing0.foo_id AS foo_id, - enclosing0.my_boolean AS my_boolean - FROM enclosing enclosing0 - """ - -db.run(Enclosing.select) ==> Seq(value1, value2) -``` - - - - - - -### DataTypes.JoinNullable proper type mapping - - - -```scala -case class A[T[_]](id: T[Int], bId: T[Option[Int]]) -object A extends Table[A] - -object Custom extends Enumeration { - val Foo, Bar = Value - - implicit def make: String => Value = withName -} - -case class B[T[_]](id: T[Int], custom: T[Custom.Value]) -object B extends Table[B] -db.run(A.insert.columns(_.id := 1, _.bId := None)) -val result = db.run(A.select.leftJoin(B)(_.id === _.id).single) -result._2 ==> None -``` - - - - - - -### DataTypes.enclosing - with SimpleTable - -You can nest `case class`es in other `case class`es to DRY up common sets of -table columns. These nested `case class`es have their columns flattened out -into the enclosing `case class`'s columns, such that at the SQL level it is -all flattened out without nesting. - -**Important**: When using nested `case class`es with `SimpleTable`, -make sure to extend `SimpleTable.Nested` in the nested class. - -```scala -// case class Nested( -// fooId: Int, -// myBoolean: Boolean, -// ) extends SimpleTable.Nested -// object Nested extends SimpleTable[Nested] -// -// case class Enclosing( -// barId: Int, -// myString: String, -// foo: Nested -// ) -// object Enclosing extends SimpleTable[Enclosing] -val value1 = Enclosing( - barId = 1337, - myString = "hello", - foo = Nested( - fooId = 271828, - myBoolean = true - ) -) -val value2 = Enclosing( - barId = 31337, - myString = "world", - foo = Nested( - fooId = 1618, - myBoolean = false - ) -) - -val insertColumns = Enclosing.insert.columns( - _.barId := value1.barId, - _.myString := value1.myString, - _.foo.fooId := value1.foo.fooId, - _.foo.myBoolean := value1.foo.myBoolean -) -db.renderSql(insertColumns) ==> - "INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)" - -db.run(insertColumns) ==> 1 - -val insertValues = Enclosing.insert.values(value2) -db.renderSql(insertValues) ==> - "INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)" - -db.run(insertValues) ==> 1 - -db.renderSql(Enclosing.select) ==> """ - SELECT - enclosing0.bar_id AS bar_id, - enclosing0.my_string AS my_string, - enclosing0.foo_id AS foo_id, - enclosing0.my_boolean AS my_boolean - FROM enclosing enclosing0 - """ - -db.run(Enclosing.select) ==> Seq(value1, value2) -``` - - - - - - -## Optional -Queries using columns that may be `NULL`, `Expr[Option[T]]` or `Option[T]` in Scala -### Optional - - - -```scala -OptCols.insert.batched(_.myInt, _.myInt2)( - (None, None), - (Some(1), Some(2)), - (Some(3), None), - (None, Some(4)) -) -``` - - - - -* - ```scala - 4 - ``` - - - -### Optional.selectAll - -Nullable columns are modelled as `T[Option[V]]` fields on your `case class`, -and are returned to you as `Option[V]` values when you run a query. These -can be `Some` or `None` - -```scala -OptCols.select -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.groupByMaxGet - -Some aggregates return `Expr[Option[V]]`s, et.c. `.maxByOpt` - -```scala -OptCols.select.groupBy(_.myInt)(_.maxByOpt(_.myInt2.get)) -``` - - -* - ```sql - SELECT opt_cols0.my_int AS res_0, MAX(opt_cols0.my_int2) AS res_1 - FROM opt_cols opt_cols0 - GROUP BY opt_cols0.my_int - ``` - - - -* - ```scala - Seq(None -> Some(4), Some(1) -> Some(2), Some(3) -> None) - ``` - - - -### Optional.isDefined - -`.isDefined` on `Expr[Option[V]]` translates to a SQL -`IS NOT NULL` check - -```scala -OptCols.select.filter(_.myInt.isDefined) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int IS NOT NULL) - ``` - - - -* - ```scala - Seq(OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None)) - ``` - - - -### Optional.isEmpty - -`.isEmpty` on `Expr[Option[V]]` translates to a SQL -`IS NULL` check - -```scala -OptCols.select.filter(_.myInt.isEmpty) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int IS NULL) - ``` - - - -* - ```scala - Seq(OptCols[Sc](None, None), OptCols[Sc](None, Some(4))) - ``` - - - -### Optional.sqlEquals.nonOptionHit - -Backticked `=` equality in ScalaSQL translates to a raw `=` -in SQL. This follows SQL `NULL` semantics, meaning that -`None = None` returns `false` rather than `true` - -```scala -OptCols.select.filter(_.myInt `=` 1) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int = ?) - ``` - - - -* - ```scala - Seq(OptCols[Sc](Some(1), Some(2))) - ``` - - - -### Optional.sqlEquals.nonOptionMiss - - - -```scala -OptCols.select.filter(_.myInt `=` 2) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int = ?) - ``` - - - -* - ```scala - Seq[OptCols[Sc]]() - ``` - - - -### Optional.sqlEquals.optionMiss - - - -```scala -OptCols.select.filter(_.myInt `=` Option.empty[Int]) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int = ?) - ``` - - - -* - ```scala - Seq[OptCols[Sc]]() - ``` - - - -### Optional.scalaEquals.someHit - -`===` equality in ScalaSQL translates to a `IS NOT DISTINCT` in SQL. -This roughly follows Scala `==` semantics, meaning `None === None` -returns `true` - -```scala -OptCols.select.filter(_.myInt === Option(1)) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int IS NOT DISTINCT FROM ?) - ``` - - - -* - ```scala - Seq(OptCols[Sc](Some(1), Some(2))) - ``` - - - -### Optional.scalaEquals.noneHit - - - -```scala -OptCols.select.filter(_.myInt === Option.empty[Int]) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int IS NOT DISTINCT FROM ?) - ``` - - - -* - ```scala - Seq(OptCols[Sc](None, None), OptCols[Sc](None, Some(4))) - ``` - - - -### Optional.scalaEquals.notEqualsSome - - - -```scala -OptCols.select.filter(_.myInt !== Option(1)) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int IS DISTINCT FROM ?) - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(3), None), - OptCols[Sc](None, Some(value = 4)) - ) - ``` - - - -### Optional.scalaEquals.notEqualsNone - - - -```scala -OptCols.select.filter(_.myInt !== Option.empty[Int]) -``` - - -* - ```sql - SELECT - opt_cols0.my_int AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - WHERE (opt_cols0.my_int IS DISTINCT FROM ?) - ``` - - - -* - ```scala - Seq( - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None) - ) - ``` - - - -### Optional.map - -You can use operators like `.map` and `.flatMap` to work with -your `Expr[Option[V]]` values. These roughly follow the semantics -that you would be familiar with from Scala. - -```scala -OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.map(_ + 10))) -``` - - -* - ```sql - SELECT - (opt_cols0.my_int + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(11), Some(2)), - OptCols[Sc](Some(13), None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.map2 - - - -```scala -OptCols.select.map(_.myInt.map(_ + 10)) -``` - - -* - ```sql - SELECT (opt_cols0.my_int + ?) AS res FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq(None, Some(11), Some(13), None) - ``` - - - -### Optional.flatMap - - - -```scala -OptCols.select - .map(d => d.copy[Expr](myInt = d.myInt.flatMap(v => d.myInt2.map(v2 => v + v2 + 10)))) -``` - - -* - ```sql - SELECT - ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(13), Some(2)), - // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.mapGet - -You can use `.get` to turn an `Expr[Option[V]]` into an `Expr[V]`. This follows -SQL semantics, such that `NULL`s anywhere in that selected column automatically -will turn the whole column `None` (if it's an `Expr[Option[V]]` column) or `null` -(if it's not an optional column) - -```scala -OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.map(_ + d.myInt2.get + 1))) -``` - - -* - ```sql - SELECT - ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(4), Some(2)), - // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.rawGet - - - -```scala -OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.get + d.myInt2.get + 1)) -``` - - -* - ```sql - SELECT - ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(4), Some(2)), - // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.getOrElse - - - -```scala -OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.getOrElse(-1))) -``` - - -* - ```sql - SELECT - COALESCE(opt_cols0.my_int, ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](Some(-1), None), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None), - OptCols[Sc](Some(-1), Some(4)) - ) - ``` - - - -### Optional.orElse - - - -```scala -OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.orElse(d.myInt2))) -``` - - -* - ```sql - SELECT - COALESCE(opt_cols0.my_int, opt_cols0.my_int2) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None), - OptCols[Sc](Some(4), Some(4)) - ) - ``` - - - -### Optional.filter - -`.filter` follows normal Scala semantics, and translates to a `CASE`/`WHEN (foo)`/`ELSE NULL` - -```scala -OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.filter(_ < 2))) -``` - - -* - ```sql - SELECT - CASE - WHEN (opt_cols0.my_int < ?) THEN opt_cols0.my_int - ELSE NULL - END AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.sorting.nullsLast - -`.nullsLast` and `.nullsFirst` translate to SQL `NULLS LAST` and `NULLS FIRST` clauses - -```scala -OptCols.select.sortBy(_.myInt).nullsLast -``` - - -* - ```sql - SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ORDER BY my_int NULLS LAST - ``` - - - -* - ```scala - Seq( - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None), - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.sorting.nullsFirst - - - -```scala -OptCols.select.sortBy(_.myInt).nullsFirst -``` - - -* - ```sql - SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ORDER BY my_int NULLS FIRST - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None) - ) - ``` - - - -### Optional.sorting.ascNullsLast - - - -```scala -OptCols.select.sortBy(_.myInt).asc.nullsLast -``` - - -* - ```sql - SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ORDER BY my_int ASC NULLS LAST - ``` - - - -* - ```scala - Seq( - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None), - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.sorting.ascNullsFirst - - - -```scala -OptCols.select.sortBy(_.myInt).asc.nullsFirst -``` - - -* - ```sql - SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ORDER BY my_int ASC NULLS FIRST - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](Some(3), None) - ) - ``` - - - -### Optional.sorting.descNullsLast - - - -```scala -OptCols.select.sortBy(_.myInt).desc.nullsLast -``` - - -* - ```sql - SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ORDER BY my_int DESC NULLS LAST - ``` - - - -* - ```scala - Seq( - OptCols[Sc](Some(3), None), - OptCols[Sc](Some(1), Some(2)), - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)) - ) - ``` - - - -### Optional.sorting.descNullsFirst - - - -```scala -OptCols.select.sortBy(_.myInt).desc.nullsFirst -``` - - -* - ```sql - SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ORDER BY my_int DESC NULLS FIRST - ``` - - - -* - ```scala - Seq( - OptCols[Sc](None, None), - OptCols[Sc](None, Some(4)), - OptCols[Sc](Some(3), None), - OptCols[Sc](Some(1), Some(2)) - ) - ``` - - - -### Optional.sorting.roundTripOptionalValues - -This example demonstrates a range of different data types being written -as options, both with Some(v) and None values - -```scala -object MyEnum extends Enumeration { - val foo, bar, baz = Value - - implicit def make: String => Value = withName -} -case class OptDataTypes[T[_]]( - myTinyInt: T[Option[Byte]], - mySmallInt: T[Option[Short]], - myInt: T[Option[Int]], - myBigInt: T[Option[Long]], - myDouble: T[Option[Double]], - myBoolean: T[Option[Boolean]], - myLocalDate: T[Option[LocalDate]], - myLocalTime: T[Option[LocalTime]], - myLocalDateTime: T[Option[LocalDateTime]], - myUtilDate: T[Option[Date]], - myInstant: T[Option[Instant]], - myVarBinary: T[Option[geny.Bytes]], - myUUID: T[Option[java.util.UUID]], - myEnum: T[Option[MyEnum.Value]] -) - -object OptDataTypes extends Table[OptDataTypes] { - override def tableName: String = "data_types" -} - -val rowSome = OptDataTypes[Sc]( - myTinyInt = Some(123.toByte), - mySmallInt = Some(12345.toShort), - myInt = Some(12345678), - myBigInt = Some(12345678901L), - myDouble = Some(3.14), - myBoolean = Some(true), - myLocalDate = Some(LocalDate.parse("2023-12-20")), - myLocalTime = Some(LocalTime.parse("10:15:30")), - myLocalDateTime = Some(LocalDateTime.parse("2011-12-03T10:15:30")), - myUtilDate = Some( - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2011-12-03T10:15:30.000") - ), - myInstant = Some(Instant.parse("2011-12-03T10:15:30Z")), - myVarBinary = Some(new geny.Bytes(Array[Byte](1, 2, 3, 4, 5, 6, 7, 8))), - myUUID = Some(new java.util.UUID(1234567890L, 9876543210L)), - myEnum = Some(MyEnum.bar) -) - -val rowNone = OptDataTypes[Sc]( - myTinyInt = None, - mySmallInt = None, - myInt = None, - myBigInt = None, - myDouble = None, - myBoolean = None, - myLocalDate = None, - myLocalTime = None, - myLocalDateTime = None, - myUtilDate = None, - myInstant = None, - myVarBinary = None, - myUUID = None, - myEnum = None -) - -db.run( - OptDataTypes.insert.values(rowSome, rowNone) -) ==> 2 - -db.run(OptDataTypes.select) ==> Seq(rowSome, rowNone) -``` - - - - - - -### Optional.filter - with SimpleTable - -`.filter` follows normal Scala semantics, and translates to a `CASE`/`WHEN (foo)`/`ELSE NULL` - -```scala -OptCols.select.map(d => d.updates(_.myInt(_.filter(_ < 2)))) -``` - - -* - ```sql - SELECT - CASE - WHEN (opt_cols0.my_int < ?) THEN opt_cols0.my_int - ELSE NULL - END AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols(None, None), - OptCols(Some(1), Some(2)), - OptCols(None, None), - OptCols(None, Some(4)) - ) - ``` - - - -### Optional.getOrElse - with SimpleTable - - - -```scala -OptCols.select.map(d => d.updates(_.myInt(_.getOrElse(-1)))) -``` - - -* - ```sql - SELECT - COALESCE(opt_cols0.my_int, ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols(Some(-1), None), - OptCols(Some(1), Some(2)), - OptCols(Some(3), None), - OptCols(Some(-1), Some(4)) - ) - ``` - - - -### Optional.rawGet - with SimpleTable - - - -```scala -OptCols.select.map(d => d.updates(_.myInt := d.myInt.get + d.myInt2.get + 1)) -``` - - -* - ```sql - SELECT - ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols(None, None), - OptCols(Some(4), Some(2)), - // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too - OptCols(None, None), - OptCols(None, Some(4)) - ) - ``` - - - -### Optional.orElse - with SimpleTable - - - -```scala -OptCols.select.map(d => d.updates(_.myInt(_.orElse(d.myInt2)))) -``` - - -* - ```sql - SELECT - COALESCE(opt_cols0.my_int, opt_cols0.my_int2) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols(None, None), - OptCols(Some(1), Some(2)), - OptCols(Some(3), None), - OptCols(Some(4), Some(4)) - ) - ``` - - - -### Optional.flatMap - with SimpleTable - - - -```scala -OptCols.select - .map(d => d.updates(_.myInt(_.flatMap(v => d.myInt2.map(v2 => v + v2 + 10))))) -``` - - -* - ```sql - SELECT - ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols(None, None), - OptCols(Some(13), Some(2)), - // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too - OptCols(None, None), - OptCols(None, Some(4)) - ) - ``` - - - -### Optional.map - with SimpleTable - -You can use operators like `.map` and `.flatMap` to work with -your `Expr[Option[V]]` values. These roughly follow the semantics -that you would be familiar with from Scala. - -```scala -OptCols.select.map(d => d.updates(_.myInt(_.map(_ + 10)))) -``` - - -* - ```sql - SELECT - (opt_cols0.my_int + ?) AS my_int, - opt_cols0.my_int2 AS my_int2 - FROM opt_cols opt_cols0 - ``` - - - -* - ```scala - Seq( - OptCols(None, None), - OptCols(Some(11), Some(2)), - OptCols(Some(13), None), - OptCols(None, Some(4)) - ) - ``` - - - -## PostgresDialect -Operations specific to working with Postgres Databases -### PostgresDialect.distinctOn - -ScalaSql's Postgres dialect provides the `.distinctOn` operator, which translates -into a SQL `DISTINCT ON` clause - -```scala -Purchase.select.distinctOn(_.shippingInfoId).sortBy(_.shippingInfoId).desc -``` - - -* - ```sql - SELECT - DISTINCT ON (purchase0.shipping_info_id) purchase0.id AS id, - purchase0.shipping_info_id AS shipping_info_id, - purchase0.product_id AS product_id, - purchase0.count AS count, - purchase0.total AS total - FROM purchase purchase0 - ORDER BY shipping_info_id DESC - ``` - - - -* - ```scala - Seq( - Purchase[Sc](6, 3, 1, 5, 44.4), - Purchase[Sc](4, 2, 4, 4, 493.8), - Purchase[Sc](2, 1, 2, 3, 900.0) - ) - ``` - - - -### PostgresDialect.forUpdate - -ScalaSql's Postgres dialect provides the `.forUpdate` operator, which translates -into a SQL `SELECT ... FOR UPDATE` clause - -```scala -Invoice.select.filter(_.id === 1).forUpdate -``` - - -* - ```sql - SELECT - invoice0.id AS id, - invoice0.total AS total, - invoice0.vendor_name AS vendor_name - FROM otherschema.invoice invoice0 - WHERE (invoice0.id = ?) - FOR UPDATE - ``` - - - -* - ```scala - Seq( - Invoice[Sc](1, 150.4, "Siemens") - ) - ``` - - - -### PostgresDialect.ltrim2 - - - -```scala -Expr("xxHellox").ltrim("x") -``` - - -* - ```sql - SELECT LTRIM(?, ?) AS res - ``` - - - -* - ```scala - "Hellox" - ``` - - - -### PostgresDialect.rtrim2 - - - -```scala -Expr("xxHellox").rtrim("x") -``` - - -* - ```sql - SELECT RTRIM(?, ?) AS res - ``` - - - -* - ```scala - "xxHello" - ``` - - - -### PostgresDialect.reverse - - - -```scala -Expr("Hello").reverse -``` - - -* - ```sql - SELECT REVERSE(?) AS res - ``` - - - -* - ```scala - "olleH" - ``` - - - -### PostgresDialect.lpad - - - -```scala -Expr("Hello").lpad(10, "xy") -``` - - -* - ```sql - SELECT LPAD(?, ?, ?) AS res - ``` +db.run(Enclosing.select) ==> Seq(value1, value2) +``` -* - ```scala - "xyxyxHello" - ``` -### PostgresDialect.rpad +### DataTypes.JoinNullable proper type mapping ```scala -Expr("Hello").rpad(10, "xy") -``` +case class A[T[_]](id: T[Int], bId: T[Option[Int]]) +object A extends Table[A] +object Custom extends Enumeration { + val Foo, Bar = Value -* - ```sql - SELECT RPAD(?, ?, ?) AS res - ``` + implicit def make: String => Value = withName +} + +case class B[T[_]](id: T[Int], custom: T[Custom.Value]) +object B extends Table[B] +db.run(A.insert.columns(_.id := 1, _.bId := None)) +val result = db.run(A.select.leftJoin(B)(_.id === _.id).single) +result._2 ==> None +``` -* - ```scala - "Helloxyxyx" - ``` -### PostgresDialect.concat +## Optional +Queries using columns that may be `NULL`, `Expr[Option[T]]` or `Option[T]` in Scala +### Optional ```scala -db.concat("i ", "am", " cow", 1337) +OptCols.insert.batched(_.myInt, _.myInt2)( + (None, None), + (Some(1), Some(2)), + (Some(3), None), + (None, Some(4)) +) ``` -* - ```sql - SELECT CONCAT(?, ?, ?, ?) AS res - ``` - * ```scala - "i am cow1337" + 4 ``` -### PostgresDialect.concatWs - +### Optional.selectAll +Nullable columns are modelled as `T[Option[V]]` fields on your `case class`, +and are returned to you as `Option[V]` values when you run a query. These +can be `Some` or `None` ```scala -db.concatWs(" ", "i", "am", "cow", 1337) +OptCols.select ``` * ```sql - SELECT CONCAT_WS(?, ?, ?, ?, ?) AS res + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "i am cow 1337" + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None), + OptCols[Sc](None, Some(4)) + ) ``` -### PostgresDialect.format - +### Optional.groupByMaxGet +Some aggregates return `Expr[Option[V]]`s, et.c. `.maxByOpt` ```scala -db.format("i am cow %s hear me moo %s", 1337, 31337) +OptCols.select.groupBy(_.myInt)(_.maxByOpt(_.myInt2.get)) ``` * ```sql - SELECT FORMAT(?, ?, ?) AS res + SELECT opt_cols0.my_int AS res_0, MAX(opt_cols0.my_int2) AS res_1 + FROM opt_cols opt_cols0 + GROUP BY opt_cols0.my_int ``` * ```scala - "i am cow 1337 hear me moo 31337" + Seq(None -> Some(4), Some(1) -> Some(2), Some(3) -> None) ``` -### PostgresDialect.random - +### Optional.isDefined +`.isDefined` on `Expr[Option[V]]` translates to a SQL +`IS NOT NULL` check ```scala -db.random +OptCols.select.filter(_.myInt.isDefined) ``` * ```sql - SELECT RANDOM() AS res + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int IS NOT NULL) ``` +* + ```scala + Seq(OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None)) + ``` + -## MySqlDialect -Operations specific to working with MySql Databases -### MySqlDialect.forUpdate +### Optional.isEmpty -ScalaSql's MySql dialect provides the `.forUpdate` operator, which translates -into a SQL `SELECT ... FOR UPDATE` clause +`.isEmpty` on `Expr[Option[V]]` translates to a SQL +`IS NULL` check ```scala -Buyer.select.filter(_.id === 1).forUpdate +OptCols.select.filter(_.myInt.isEmpty) ``` * ```sql SELECT - buyer0.id AS id, - buyer0.name AS name, - buyer0.date_of_birth AS date_of_birth - FROM buyer buyer0 - WHERE (buyer0.id = ?) - FOR UPDATE + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int IS NULL) ``` * ```scala - Seq( - Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) - ) + Seq(OptCols[Sc](None, None), OptCols[Sc](None, Some(4))) ``` -### MySqlDialect.reverse - +### Optional.sqlEquals.nonOptionHit +Backticked `=` equality in ScalaSQL translates to a raw `=` +in SQL. This follows SQL `NULL` semantics, meaning that +`None = None` returns `false` rather than `true` ```scala -Expr("Hello").reverse +OptCols.select.filter(_.myInt `=` 1) ``` * ```sql - SELECT REVERSE(?) AS res + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int = ?) ``` * ```scala - "olleH" + Seq(OptCols[Sc](Some(1), Some(2))) ``` -### MySqlDialect.lpad +### Optional.sqlEquals.nonOptionMiss ```scala -Expr("Hello").lpad(10, "xy") +OptCols.select.filter(_.myInt `=` 2) ``` * ```sql - SELECT LPAD(?, ?, ?) AS res + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int = ?) ``` * ```scala - "xyxyxHello" + Seq[OptCols[Sc]]() ``` -### MySqlDialect.rpad +### Optional.sqlEquals.optionMiss ```scala -Expr("Hello").rpad(10, "xy") +OptCols.select.filter(_.myInt `=` Option.empty[Int]) ``` * ```sql - SELECT RPAD(?, ?, ?) AS res + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int = ?) ``` * ```scala - "Helloxyxyx" + Seq[OptCols[Sc]]() ``` -### MySqlDialect.conflict.ignore - +### Optional.scalaEquals.someHit +`===` equality in ScalaSQL translates to a `IS NOT DISTINCT` in SQL. +This roughly follows Scala `==` semantics, meaning `None === None` +returns `true` ```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictUpdate(x => x.id := x.id) +OptCols.select.filter(_.myInt === Option(1)) ``` * ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE id = buyer.id + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int <=> ?) ``` * ```scala - 1 + Seq(OptCols[Sc](Some(1), Some(2))) ``` -### MySqlDialect.conflict.update +### Optional.scalaEquals.noneHit ```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictUpdate(_.name := "TEST BUYER CONFLICT") +OptCols.select.filter(_.myInt === Option.empty[Int]) ``` * ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name = ? + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (opt_cols0.my_int <=> ?) ``` * ```scala - 2 + Seq(OptCols[Sc](None, None), OptCols[Sc](None, Some(4))) ``` ----- +### Optional.scalaEquals.notEqualsSome ```scala -Buyer.select +OptCols.select.filter(_.myInt !== Option(1)) ``` +* + ```sql + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (NOT (opt_cols0.my_int <=> ?)) + ``` + * ```scala Seq( - Buyer[Sc](1, "TEST BUYER CONFLICT", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) + OptCols[Sc](None, None), + OptCols[Sc](Some(3), None), + OptCols[Sc](None, Some(value = 4)) ) ``` -### MySqlDialect.conflict.updateComputed +### Optional.scalaEquals.notEqualsNone ```scala -Buyer.insert - .columns( - _.name := "test buyer", - _.dateOfBirth := LocalDate.parse("2023-09-09"), - _.id := 1 // This should cause a primary key conflict - ) - .onConflictUpdate(v => v.name := v.name.toUpperCase) +OptCols.select.filter(_.myInt !== Option.empty[Int]) ``` * ```sql - INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name = UPPER(buyer.name) - ``` - - - -* - ```scala - 2 + SELECT + opt_cols0.my_int AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + WHERE (NOT (opt_cols0.my_int <=> ?)) ``` ----- - - - -```scala -Buyer.select -``` - - - - * ```scala Seq( - Buyer[Sc](1, "JAMES BOND", LocalDate.parse("2001-02-03")), - Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), - Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None) ) ``` -### MySqlDialect.concat - +### Optional.map +You can use operators like `.map` and `.flatMap` to work with +your `Expr[Option[V]]` values. These roughly follow the semantics +that you would be familiar with from Scala. ```scala -db.concat("i ", "am", " cow", 1337) +OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.map(_ + 10))) ``` * ```sql - SELECT CONCAT(?, ?, ?, ?) AS res + SELECT + (opt_cols0.my_int + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "i am cow1337" + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(11), Some(2)), + OptCols[Sc](Some(13), None), + OptCols[Sc](None, Some(4)) + ) ``` -### MySqlDialect.concatWs +### Optional.map2 ```scala -db.concatWs(" ", "i", "am", "cow", 1337) +OptCols.select.map(_.myInt.map(_ + 10)) ``` * ```sql - SELECT CONCAT_WS(?, ?, ?, ?, ?) AS res + SELECT (opt_cols0.my_int + ?) AS res FROM opt_cols opt_cols0 ``` * ```scala - "i am cow 1337" + Seq(None, Some(11), Some(13), None) ``` -### MySqlDialect.rand +### Optional.flatMap ```scala -db.rand +OptCols.select + .map(d => d.copy[Expr](myInt = d.myInt.flatMap(v => d.myInt2.map(v2 => v + v2 + 10)))) ``` * ```sql - SELECT RAND() AS res + SELECT + ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` +* + ```scala + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(13), Some(2)), + // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) + ``` -## SqliteDialect -Operations specific to working with Sqlite Databases -### SqliteDialect.ltrim2 +### Optional.mapGet +You can use `.get` to turn an `Expr[Option[V]]` into an `Expr[V]`. This follows +SQL semantics, such that `NULL`s anywhere in that selected column automatically +will turn the whole column `None` (if it's an `Expr[Option[V]]` column) or `null` +(if it's not an optional column) ```scala -Expr("xxHellox").ltrim("x") +OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.map(_ + d.myInt2.get + 1))) ``` * ```sql - SELECT LTRIM(?, ?) AS res + SELECT + ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "Hellox" + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(4), Some(2)), + // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) ``` -### SqliteDialect.rtrim2 +### Optional.rawGet ```scala -Expr("xxHellox").rtrim("x") +OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.get + d.myInt2.get + 1)) ``` * ```sql - SELECT RTRIM(?, ?) AS res + SELECT + ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "xxHello" + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(4), Some(2)), + // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) ``` -### SqliteDialect.glob +### Optional.getOrElse ```scala -Expr("*cop*").glob("roflcopter") +OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.getOrElse(-1))) ``` * ```sql - SELECT GLOB(?, ?) AS res + SELECT + COALESCE(opt_cols0.my_int, ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - true + Seq( + OptCols[Sc](Some(-1), None), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None), + OptCols[Sc](Some(-1), Some(4)) + ) ``` -### SqliteDialect.changes +### Optional.orElse ```scala -db.changes +OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.orElse(d.myInt2))) ``` * ```sql - SELECT CHANGES() AS res + SELECT + COALESCE(opt_cols0.my_int, opt_cols0.my_int2) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` +* + ```scala + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None), + OptCols[Sc](Some(4), Some(4)) + ) + ``` -### SqliteDialect.totalChanges +### Optional.filter +`.filter` follows normal Scala semantics, and translates to a `CASE`/`WHEN (foo)`/`ELSE NULL` ```scala -db.totalChanges +OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.filter(_ < 2))) ``` * ```sql - SELECT TOTAL_CHANGES() AS res + SELECT + CASE + WHEN (opt_cols0.my_int < ?) THEN opt_cols0.my_int + ELSE NULL + END AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` +* + ```scala + Seq( + OptCols[Sc](None, None), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) + ``` -### SqliteDialect.typeOf +### Optional.sorting.nullsLast +`.nullsLast` and `.nullsFirst` translate to SQL `NULLS LAST` and `NULLS FIRST` clauses ```scala -db.typeOf(123) +OptCols.select.sortBy(_.myInt).nullsLast ``` * ```sql - SELECT TYPEOF(?) AS res + SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + ORDER BY my_int IS NULL ASC, my_int ``` * ```scala - "integer" + Seq( + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None), + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) ``` -### SqliteDialect.lastInsertRowId +### Optional.sorting.nullsFirst ```scala -db.lastInsertRowId +OptCols.select.sortBy(_.myInt).nullsFirst ``` * ```sql - SELECT LAST_INSERT_ROWID() AS res + SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + ORDER BY my_int IS NULL DESC, my_int ``` +* + ```scala + Seq( + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None) + ) + ``` + -### SqliteDialect.char +### Optional.sorting.ascNullsLast ```scala -db.char(108, 111, 108) +OptCols.select.sortBy(_.myInt).asc.nullsLast ``` * ```sql - SELECT CHAR(?, ?, ?) AS res + SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + ORDER BY my_int IS NULL ASC, my_int ASC ``` * ```scala - "lol" + Seq( + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None), + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) ``` -### SqliteDialect.format +### Optional.sorting.ascNullsFirst ```scala -db.format("i am cow %s hear me moo %s", 1337, 31337) +OptCols.select.sortBy(_.myInt).asc.nullsFirst ``` * ```sql - SELECT FORMAT(?, ?, ?) AS res + SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + ORDER BY my_int ASC ``` * ```scala - "i am cow 1337 hear me moo 31337" + Seq( + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](Some(3), None) + ) ``` -### SqliteDialect.hex +### Optional.sorting.descNullsLast ```scala -db.hex(new geny.Bytes(Array(1, 10, 100, -127))) +OptCols.select.sortBy(_.myInt).desc.nullsLast ``` * ```sql - SELECT HEX(?) AS res + SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + ORDER BY my_int DESC ``` * ```scala - "010A6481" + Seq( + OptCols[Sc](Some(3), None), + OptCols[Sc](Some(1), Some(2)), + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)) + ) ``` -### SqliteDialect.unhex +### Optional.sorting.descNullsFirst ```scala -db.unhex("010A6481") +OptCols.select.sortBy(_.myInt).desc.nullsFirst ``` * ```sql - SELECT UNHEX(?) AS res + SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 + ORDER BY my_int IS NULL DESC, my_int DESC ``` * ```scala - new geny.Bytes(Array(1, 10, 100, -127)) + Seq( + OptCols[Sc](None, None), + OptCols[Sc](None, Some(4)), + OptCols[Sc](Some(3), None), + OptCols[Sc](Some(1), Some(2)) + ) ``` -### SqliteDialect.zeroBlob - +### Optional.sorting.roundTripOptionalValues +This example demonstrates a range of different data types being written +as options, both with Some(v) and None values ```scala -db.zeroBlob(16) -``` +object MyEnum extends Enumeration { + val foo, bar, baz = Value + implicit def make: String => Value = withName +} +case class OptDataTypes[T[_]]( + myTinyInt: T[Option[Byte]], + mySmallInt: T[Option[Short]], + myInt: T[Option[Int]], + myBigInt: T[Option[Long]], + myDouble: T[Option[Double]], + myBoolean: T[Option[Boolean]], + myLocalDate: T[Option[LocalDate]], + myLocalTime: T[Option[LocalTime]], + myLocalDateTime: T[Option[LocalDateTime]], + myUtilDate: T[Option[Date]], + myInstant: T[Option[Instant]], + myVarBinary: T[Option[geny.Bytes]], + myUUID: T[Option[java.util.UUID]], + myEnum: T[Option[MyEnum.Value]] +) -* - ```sql - SELECT ZEROBLOB(?) AS res - ``` +object OptDataTypes extends Table[OptDataTypes] { + override def tableName: String = "data_types" +} +val rowSome = OptDataTypes[Sc]( + myTinyInt = Some(123.toByte), + mySmallInt = Some(12345.toShort), + myInt = Some(12345678), + myBigInt = Some(12345678901L), + myDouble = Some(3.14), + myBoolean = Some(true), + myLocalDate = Some(LocalDate.parse("2023-12-20")), + myLocalTime = Some(LocalTime.parse("10:15:30")), + myLocalDateTime = Some(LocalDateTime.parse("2011-12-03T10:15:30")), + myUtilDate = Some( + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2011-12-03T10:15:30.000") + ), + myInstant = Some(Instant.parse("2011-12-03T10:15:30Z")), + myVarBinary = Some(new geny.Bytes(Array[Byte](1, 2, 3, 4, 5, 6, 7, 8))), + myUUID = Some(new java.util.UUID(1234567890L, 9876543210L)), + myEnum = Some(MyEnum.bar) +) + +val rowNone = OptDataTypes[Sc]( + myTinyInt = None, + mySmallInt = None, + myInt = None, + myBigInt = None, + myDouble = None, + myBoolean = None, + myLocalDate = None, + myLocalTime = None, + myLocalDateTime = None, + myUtilDate = None, + myInstant = None, + myVarBinary = None, + myUUID = None, + myEnum = None +) +db.run( + OptDataTypes.insert.values(rowSome, rowNone) +) ==> 2 + +db.run(OptDataTypes.select) ==> Seq(rowSome, rowNone) +``` -* - ```scala - new geny.Bytes(new Array[Byte](16)) - ``` -## H2Dialect -Operations specific to working with H2 Databases -### H2Dialect.ltrim2 +### Optional.filter - with SimpleTable + +`.filter` follows normal Scala semantics, and translates to a `CASE`/`WHEN (foo)`/`ELSE NULL` ```scala -Expr("xxHellox").ltrim("x") +OptCols.select.map(d => d.updates(_.myInt(_.filter(_ < 2)))) ``` * ```sql - SELECT LTRIM(?, ?) AS res + SELECT + CASE + WHEN (opt_cols0.my_int < ?) THEN opt_cols0.my_int + ELSE NULL + END AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "Hellox" + Seq( + OptCols(None, None), + OptCols(Some(1), Some(2)), + OptCols(None, None), + OptCols(None, Some(4)) + ) ``` -### H2Dialect.rtrim2 +### Optional.getOrElse - with SimpleTable ```scala -Expr("xxHellox").rtrim("x") +OptCols.select.map(d => d.updates(_.myInt(_.getOrElse(-1)))) ``` * ```sql - SELECT RTRIM(?, ?) AS res + SELECT + COALESCE(opt_cols0.my_int, ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "xxHello" + Seq( + OptCols(Some(-1), None), + OptCols(Some(1), Some(2)), + OptCols(Some(3), None), + OptCols(Some(-1), Some(4)) + ) ``` -### H2Dialect.lpad +### Optional.rawGet - with SimpleTable ```scala -Expr("Hello").lpad(10, "xy") +OptCols.select.map(d => d.updates(_.myInt := d.myInt.get + d.myInt2.get + 1)) ``` * ```sql - SELECT LPAD(?, ?, ?) AS res + SELECT + ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "xxxxxHello" + Seq( + OptCols(None, None), + OptCols(Some(4), Some(2)), + // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too + OptCols(None, None), + OptCols(None, Some(4)) + ) ``` -### H2Dialect.rpad +### Optional.orElse - with SimpleTable ```scala -Expr("Hello").rpad(10, "xy") +OptCols.select.map(d => d.updates(_.myInt(_.orElse(d.myInt2)))) ``` * ```sql - SELECT RPAD(?, ?, ?) AS res + SELECT + COALESCE(opt_cols0.my_int, opt_cols0.my_int2) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "Helloxxxxx" + Seq( + OptCols(None, None), + OptCols(Some(1), Some(2)), + OptCols(Some(3), None), + OptCols(Some(4), Some(4)) + ) ``` -### H2Dialect.concat +### Optional.flatMap - with SimpleTable ```scala -db.concat("i ", "am", " cow", 1337) +OptCols.select + .map(d => d.updates(_.myInt(_.flatMap(v => d.myInt2.map(v2 => v + v2 + 10))))) ``` * ```sql - SELECT CONCAT(?, ?, ?, ?) AS res + SELECT + ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "i am cow1337" + Seq( + OptCols(None, None), + OptCols(Some(13), Some(2)), + // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too + OptCols(None, None), + OptCols(None, Some(4)) + ) ``` -### H2Dialect.concatWs - +### Optional.map - with SimpleTable +You can use operators like `.map` and `.flatMap` to work with +your `Expr[Option[V]]` values. These roughly follow the semantics +that you would be familiar with from Scala. ```scala -db.concatWs(" ", "i", "am", "cow", 1337) +OptCols.select.map(d => d.updates(_.myInt(_.map(_ + 10)))) ``` * ```sql - SELECT CONCAT_WS(?, ?, ?, ?, ?) AS res + SELECT + (opt_cols0.my_int + ?) AS my_int, + opt_cols0.my_int2 AS my_int2 + FROM opt_cols opt_cols0 ``` * ```scala - "i am cow 1337" + Seq( + OptCols(None, None), + OptCols(Some(11), Some(2)), + OptCols(Some(13), None), + OptCols(None, Some(4)) + ) ``` diff --git a/mill b/mill index d03a045c..22777fc7 100755 --- a/mill +++ b/mill @@ -1,51 +1,307 @@ #!/usr/bin/env sh -# This is a wrapper script, that automatically download mill from GitHub release pages -# You can give the required mill version with MILL_VERSION env variable -# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. +# +# This script determines the Mill version to use by trying these sources +# - env-variable `MILL_VERSION` +# - local file `.mill-version` +# - local file `.config/mill-version` +# - `mill-version` from YAML fronmatter of current buildfile +# - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) +# - env-variable `DEFAULT_MILL_VERSION` +# +# If a version has the suffix '-native' a native binary will be used. +# If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. +# If no such suffix is found, the script will pick a default based on version and platform. +# +# Once a version was determined, it tries to use either +# - a system-installed mill, if found and it's version matches +# - an already downloaded version under ~/.cache/mill/download +# +# If no working mill version was found on the system, +# this script downloads a binary file from Maven Central or Github Pages (this is version dependent) +# into a cache location (~/.cache/mill/download). +# +# Mill Project URL: https://github.com/com-lihaoyi/mill +# Script Version: 1.0.0-M1-21-7b6fae-DIRTY892b63e8 +# +# If you want to improve this script, please also contribute your changes back! +# This script was generated from: dist/scripts/src/mill.sh +# +# Licensed under the Apache License, Version 2.0 set -e if [ -z "${DEFAULT_MILL_VERSION}" ] ; then - DEFAULT_MILL_VERSION=0.11.12 + DEFAULT_MILL_VERSION=1.0.0-RC1 fi -if [ -z "$MILL_VERSION" ] ; then + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + echo "The --mill-version option is no longer supported." 1>&2 +fi + +MILL_BUILD_SCRIPT="" + +if [ -f "build.mill" ] ; then + MILL_BUILD_SCRIPT="build.mill" +elif [ -f "build.mill.scala" ] ; then + MILL_BUILD_SCRIPT="build.mill.scala" +elif [ -f "build.sc" ] ; then + MILL_BUILD_SCRIPT="build.sc" +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then if [ -f ".mill-version" ] ; then - MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" elif [ -f ".config/mill-version" ] ; then - MILL_VERSION="$(head -n 1 .config/mill-version 2> /dev/null)" - elif [ -f "mill" ] && [ "$0" != "mill" ] ; then - MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) - else - MILL_VERSION=$DEFAULT_MILL_VERSION + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then + MILL_VERSION="$(cat ${MILL_BUILD_SCRIPT} | grep '//[|] *mill-version: *' | sed 's;//| *mill-version: *;;')" fi fi -if [ "x${XDG_CACHE_HOME}" != "x" ] ; then - MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" -else - MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" fi -MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" -version_remainder="$MILL_VERSION" -MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" -MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via a '//| mill-version: ' comment or a '.mill-version' file." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi -if [ ! -s "$MILL_EXEC_PATH" ] ; then - mkdir -p "$MILL_DOWNLOAD_PATH" - if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then - ASSEMBLY="-assembly" +MILL_NATIVE_SUFFIX="-native" +MILL_JVM_SUFFIX="-jvm" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +set_artifact_suffix(){ + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 fi - DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download - MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') - MILL_DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/$MILL_VERSION/mill-dist-$MILL_VERSION.jar" - curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" - chmod +x "$DOWNLOAD_FILE" - mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" +} + +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + set_artifact_suffix + ;; + + *"$MILL_JVM_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_JVM_SUFFIX"} + ;; + + *) + case "$MILL_VERSION" in + 0.1.*) ;; + 0.2.*) ;; + 0.3.*) ;; + 0.4.*) ;; + 0.5.*) ;; + 0.6.*) ;; + 0.7.*) ;; + 0.8.*) ;; + 0.9.*) ;; + 0.10.*) ;; + 0.11.*) ;; + 0.12.*) ;; + *) + set_artifact_suffix + esac + ;; +esac + +MILL="${MILL_DOWNLOAD_PATH}/$MILL_VERSION$ARTIFACT_SUFFIX" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] || [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + case $MILL_VERSION in + 0.12.0 | 0.12.1 | 0.12.2 | 0.12.3 | 0.12.4 | 0.12.5 | 0.12.6 | 0.12.7 | 0.12.8 | 0.12.9 | 0.12.10 | 0.12.11 ) + DOWNLOAD_EXT="jar" + ;; + 0.12.* ) + DOWNLOAD_EXT="exe" + ;; + 0.* ) + DOWNLOAD_EXT="jar" + ;; + *) + DOWNLOAD_EXT="exe" + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${DOWNLOAD_EXT}" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + if [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + echo $DOWNLOAD_URL + echo $MILL + exit 0 + fi + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + unset DOWNLOAD_FILE - unset MILL_DOWNLOAD_URL + unset DOWNLOAD_SUFFIX fi if [ -z "$MILL_MAIN_CLI" ] ; then @@ -53,15 +309,19 @@ if [ -z "$MILL_MAIN_CLI" ] ; then fi MILL_FIRST_ARG="" - - # first arg is a long flag for "--interactive" or starts with "-i" -if [ "$1" = "--bsp" ] || [ "${1#"-i"}" != "$1" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--no-daemon" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then # Need to preserve the first position of those listed options MILL_FIRST_ARG=$1 shift fi unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL unset MILL_VERSION +unset MILL_REPO_URL -exec $MILL_EXEC_PATH $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" +# -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" diff --git a/readme.md b/readme.md index f7a7111c..7c356d34 100644 --- a/readme.md +++ b/readme.md @@ -71,7 +71,7 @@ you are able to investigate and fix them! To get started with ScalaSql, add it to your `build.sc` file as follows: ```scala -ivy"com.lihaoyi::scalasql:0.1.20" +ivy"com.lihaoyi::scalasql:0.2.0" ``` ScalaSql supports Scala 2.13.x and >=3.6.2 @@ -84,7 +84,7 @@ Add the following to your `build.sc` file as follows: ```scala -ivy"com.lihaoyi::scalasql-namedtuples:0.1.20" +ivy"com.lihaoyi::scalasql-namedtuples:0.2.0" ``` And taking the example above, the only thing that needs to change is the following: @@ -131,6 +131,7 @@ And you now have the option to return named tuples from queries: * [MySql](scalasql/test/src/example/MySqlExample.scala) * [Sqlite](scalasql/test/src/example/SqliteExample.scala) * [H2](scalasql/test/src/example/H2Example.scala) + * [Microsoft SQL Server](scalasql/test/src/example/MsSqlExample.scala) * [HikariCP](scalasql/test/src/example/HikariCpExample.scala) (and other connection pools) * [ScalaSql Tutorial](docs/tutorial.md): a structured walkthrough of how to use ScalaSql, @@ -176,6 +177,10 @@ And you now have the option to return named tuples from queries: ## Changelog +### 0.2.0 + +* Initial support for Microsoft SQL Server via `scalasql.dialects.MsSqlDialect` [#94](https://github.com/com-lihaoyi/scalasql/pull/94) + ### 0.1.20 * Support for `SimpleTable` classes which do not need a `T[_]` higher kinded parameter,