Skip to content

Add SimpleTable construct based on Named Tuples #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mill-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.12.0
0.12.10
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.8.1"
version = "3.9.5"

align.preset = none
align.openParenCallSite = false
Expand Down
86 changes: 62 additions & 24 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import de.tobiasroeser.mill.vcs.version.VcsVersion
import com.goyeau.mill.scalafix.ScalafixModule
import mill._, scalalib._, publish._

val scalaVersions = Seq("2.13.12", "3.6.2")
val scala3 = "3.6.2"
val scalaVersions = Seq("2.13.12", scala3)
val scala3NamedTuples = "3.7.0"

trait Common extends CrossScalaModule with PublishModule with ScalafixModule{
def scalaVersion = crossScalaVersion
trait CommonBase extends ScalaModule with PublishModule with ScalafixModule { common =>

def publishVersion = VcsVersion.vcsState().format()

Expand All @@ -33,18 +34,13 @@ trait Common extends CrossScalaModule with PublishModule with ScalafixModule{
Seq("-Wunused:privates,locals,explicits,implicits,params") ++
Option.when(scalaVersion().startsWith("2."))("-Xsource:3")
}
}


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 semanticDbVersion: T[String] =
// last version that works with Scala 2.13.12
"4.12.3"

object test extends ScalaTests with ScalafixModule{
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",
Expand All @@ -61,10 +57,51 @@ trait ScalaSql extends Common{ common =>
ivy"com.zaxxer:HikariCP:5.1.0"
)

def recordedTestsFile: String
def recordedSuiteDescriptionsFile: String

def testFramework = "scalasql.UtestFramework"

def forkArgs = Seq("-Duser.timezone=Asia/Singapore")
def forkEnv = Map("MILL_WORKSPACE_ROOT" -> T.workspace.toString())

def forkEnv = Map(
"MILL_WORKSPACE_ROOT" -> T.workspace.toString(),
"SCALASQL_RECORDED_TESTS_NAME" -> recordedTestsFile,
"SCALASQL_RECORDED_SUITE_DESCRIPTIONS_NAME" -> recordedSuiteDescriptionsFile
)
}
}
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 moduleDeps: Seq[PublishModule] = Seq(scalasql(scala3).query)

// override def scalacOptions: Target[Seq[String]] = T {
// super.scalacOptions() :+ "-Xprint:inlining"
// }

object test extends CommonTest {
def resources = scalasql(scala3).test.resources
def moduleDeps = super.moduleDeps ++ Seq(scalasql(scala3), scalasql(scala3).test)
def recordedTestsFile: String = "recordedTestsNT.json"
def recordedSuiteDescriptionsFile: String = "recordedSuiteDescriptionsNT.json"
}
}

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()}"
)

override def consoleScalacOptions: T[Seq[String]] = Seq("-Xprint:typer")

object test extends CommonTest {
def recordedTestsFile: String = "recordedTests.json"
def recordedSuiteDescriptionsFile: String = "recordedSuiteDescriptions.json"
}

private def indent(code: Iterable[String]): String =
Expand All @@ -74,15 +111,14 @@ trait ScalaSql extends Common{ common =>
def ivyDeps = Agg(
ivy"com.lihaoyi::geny:1.0.0",
ivy"com.lihaoyi::sourcecode:0.3.1",
ivy"com.lihaoyi::pprint:0.8.1",
ivy"com.lihaoyi::pprint:0.8.1"
) ++ Option.when(scalaVersion().startsWith("2."))(
ivy"org.scala-lang:scala-reflect:${scalaVersion()}"
)

def generatedSources = T {
def commaSep0(i: Int, f: Int => String) = Range.inclusive(1, i).map(f).mkString(", ")


val queryableRowDefs = for (i <- Range.inclusive(2, 22)) yield {
def commaSep(f: Int => String) = commaSep0(i, f)
s"""implicit def Tuple${i}Queryable[${commaSep(j => s"Q$j")}, ${commaSep(j => s"R$j")}](
Expand All @@ -98,7 +134,6 @@ trait ScalaSql extends Common{ common =>
|}""".stripMargin
}


os.write(
T.dest / "Generated.scala",
s"""package scalasql.core.generated
Expand All @@ -113,15 +148,13 @@ trait ScalaSql extends Common{ common =>

}


object operations extends Common with CrossValue{
object operations extends Common with CrossValue {
def moduleDeps = Seq(core)
}

object query extends Common with CrossValue{
object query extends Common with CrossValue {
def moduleDeps = Seq(core)


def generatedSources = T {
def commaSep0(i: Int, f: Int => String) = Range.inclusive(1, i).map(f).mkString(", ")

Expand All @@ -139,7 +172,9 @@ trait ScalaSql extends Common{ common =>
| )
|
|""".stripMargin
s"""def batched[${commaSep(j => s"T$j")}](${commaSep(j => s"f$j: V[Column] => Column[T$j]")})(
s"""def batched[${commaSep(j => s"T$j")}](${commaSep(j =>
s"f$j: V[Column] => Column[T$j]"
)})(
| items: (${commaSep(j => s"Expr[T$j]")})*
|)(implicit qr: Queryable[V[Column], R]): scalasql.query.InsertColumns[V, R] $impl""".stripMargin
}
Expand All @@ -165,12 +200,15 @@ trait ScalaSql extends Common{ common =>

val commaSepQ = commaSep(j => s"Q$j")
val commaSepR = commaSep(j => s"R$j")
val joinAppendType = s"scalasql.query.JoinAppend[($commaSepQ), QA, ($commaSepQ, QA), ($commaSepR, RA)]"
val joinAppendType =
s"scalasql.query.JoinAppend[($commaSepQ), QA, ($commaSepQ, QA), ($commaSepR, RA)]"
s"""
|implicit def append$i[$commaSepQ, QA, $commaSepR, RA](
| implicit qr0: Queryable.Row[($commaSepQ, QA), ($commaSepR, RA)],
| @annotation.nowarn("msg=never used") qr20: Queryable.Row[QA, RA]): $joinAppendType = new $joinAppendType {
| override def appendTuple(t: ($commaSepQ), v: QA): ($commaSepQ, QA) = (${commaSep(j => s"t._$j")}, v)
| override def appendTuple(t: ($commaSepQ), v: QA): ($commaSepQ, QA) = (${commaSep(j =>
s"t._$j"
)}, v)
|
| def qr: Queryable.Row[($commaSepQ, QA), ($commaSepR, RA)] = qr0
|}""".stripMargin
Expand Down
4 changes: 3 additions & 1 deletion docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,9 @@ try {

throw new FooException
}
} catch { case e: FooException => /*donothing*/ }
} catch {
case e: FooException => /*donothing*/
}

dbClient.transaction(_.run(Purchase.select.size)) ==> 7
```
Expand Down
8 changes: 6 additions & 2 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -1213,7 +1213,9 @@ try {

throw new Exception()
}
} catch { case e: Exception => /*do nothing*/ }
} catch {
case e: Exception => /*do nothing*/
}

dbClient.transaction { implicit db =>
db.run(City.select.filter(_.countryCode === "SGP").single) ==>
Expand Down Expand Up @@ -1255,7 +1257,9 @@ dbClient.transaction { implicit db =>
db.run(City.select.filter(_.countryCode === "SGP")) ==> Seq()
throw new Exception()
}
} catch { case e: Exception => /*do nothing*/ }
} catch {
case e: Exception => /*do nothing*/
}

db.run(City.select.filter(_.countryCode === "SGP").single) ==>
City[Sc](3208, "Singapore", "SGP", district = "", population = 4017733)
Expand Down
108 changes: 108 additions & 0 deletions scalasql/namedtuples/src/SimpleTable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package scalasql.namedtuples

import scala.NamedTuple.AnyNamedTuple

import scalasql.query.Table
import scalasql.core.DialectTypeMappers
import scalasql.core.Queryable
import scalasql.query.Column
import scalasql.core.Sc
import scalasql.core.Expr

class SimpleTable[C <: SimpleTable.Source]()(
using name: sourcecode.Name,
metadata0: SimpleTable.Metadata[C]
) extends Table[SimpleTable.Lift[C]](using name, metadata0.metadata0) {
given simpleTableImplicitMetadata: SimpleTable.WrappedMetadata[C] =
SimpleTable.WrappedMetadata(metadata0)
}

object SimpleTable {

/**
* Marker class that signals that a data type is convertable to an SQL table row.
* @note this must be a class to convince the match type reducer that it provably can't be mixed
* into various column types such as java.util.Date, geny.Bytes, or scala.Option.
*/
abstract class Source

type Lift[C] = [T[_]] =>> T[Internal.Tombstone.type] match {
case Expr[?] => Record[C, T]
case _ => C
}

final class Record[C, T[_]](data: IArray[AnyRef]) extends Selectable:
type Fields = NamedTuple.Map[
NamedTuple.From[C],
[X] =>> X match {
case Source => Record[X, T]
case _ => T[X]
}
]
def recordIterator: Iterator[Any] = data.iterator.asInstanceOf[Iterator[Any]]
def apply(i: Int): AnyRef = data(i)
def updates(fs: ((u: RecordUpdater[C, T]) => u.Patch)*): Record[C, T] =
val u = recordUpdater[C, T]
val arr = IArray.genericWrapArray(data).toArray
fs.foreach: f =>
val patch = f(u)
val idx = patch.idx
arr(idx) = patch.f(arr(idx))
Record(IArray.unsafeFromArray(arr))

inline def selectDynamic(name: String): AnyRef =
apply(compiletime.constValue[Record.IndexOf[name.type, Record.Names[C], 0]])

private object RecordUpdaterImpl extends RecordUpdater[Any, [T] =>> Any]
def recordUpdater[C, T[_]]: RecordUpdater[C, T] =
RecordUpdaterImpl.asInstanceOf[RecordUpdater[C, T]]
sealed trait RecordUpdater[C, T[_]] extends Selectable:
final case class Patch(idx: Int, f: AnyRef => AnyRef)
type Fields = NamedTuple.Map[
NamedTuple.From[C],
[X] =>> X match {
case Source => (Record[X, T] => Record[X, T]) => Patch
case _ => (T[X] => T[X]) => Patch
}
]
def apply(i: Int): (AnyRef => AnyRef) => Patch =
f => Patch(i, f)
inline def selectDynamic(name: String): (AnyRef => AnyRef) => Patch =
apply(compiletime.constValue[Record.IndexOf[name.type, Record.Names[C], 0]])

object Record:
import scala.compiletime.ops.int.*
type Names[C] = NamedTuple.Names[NamedTuple.From[C]]
type IndexOf[N, T <: Tuple, Acc <: Int] <: Int = T match {
case EmptyTuple => -1
case N *: _ => Acc
case _ *: t => IndexOf[N, t, S[Acc]]
}
def fromIArray(data: IArray[AnyRef]): Record[Any, [T] =>> Any] =
Record(data)

object Internal {
case object Tombstone
}

opaque type WrappedMetadata[C] = Metadata[C]
object WrappedMetadata {
def apply[C](metadata: Metadata[C]): WrappedMetadata[C] = metadata
extension [C](m: WrappedMetadata[C]) {
def metadata: Metadata[C] = m
}
}
class Metadata[C](val metadata0: Table.Metadata[Lift[C]]):
def rowExpr(
mappers: DialectTypeMappers
): Queryable.Row[Record[C, Expr], C] =
metadata0
.queryable(
metadata0.walkLabels0,
mappers,
new Table.Metadata.QueryableProxy(metadata0.queryables(mappers, _))
)
.asInstanceOf[Queryable.Row[Record[C, Expr], C]]

object Metadata extends SimpleTableMacros
}
Loading