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

Merged
merged 29 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fafb80a
bump mill-version (critical)
bishabosha Apr 23, 2025
d437520
wip: add new module
bishabosha Apr 23, 2025
716309a
wip: simple table and inline defs
bishabosha Apr 23, 2025
5364d93
wip: queryable
bishabosha Apr 24, 2025
b47953b
wip: vExpr
bishabosha Apr 25, 2025
8d7fcbc
wip: use record type that maps back to case class
bishabosha Apr 26, 2025
3fa8eb3
wip: pass example tests for each dialect
bishabosha Apr 27, 2025
35fea06
wip: pass datatypes test, introduce marker class
bishabosha May 2, 2025
63b2fce
pass optiontests, add record updater
bishabosha May 2, 2025
20fd1e1
Update scalafmt to suppoer 3.7 syntax
bishabosha May 2, 2025
96b759d
REFORMAT SOURCES
bishabosha May 2, 2025
f20982f
Set semanticdb version explicitly
bishabosha May 2, 2025
f83f2af
format reference
bishabosha May 2, 2025
5e6edca
add large object test
bishabosha May 4, 2025
17c7012
dont print AST while compiling
bishabosha May 11, 2025
aa2f0ea
dont blow stack while computing size of case class
bishabosha May 11, 2025
678360e
only require SimpleTable.Source for nested
bishabosha May 13, 2025
e415f63
add named tuple queryable
bishabosha May 15, 2025
1b5ab9b
switch order of Lift
bishabosha May 18, 2025
65e0159
rename Lift, remove rowExpr
bishabosha May 18, 2025
5c606bf
only cache the factories
bishabosha May 19, 2025
9efa287
move named tuple querable to new file
bishabosha May 19, 2025
6cf41b2
remove some wrapper classes
bishabosha May 20, 2025
cf16d30
reexport everything via scalasql.simple package
bishabosha May 21, 2025
487ce9e
Add scaladoc to SimpleTable
bishabosha May 22, 2025
0de7f5c
wip: tutorial.md
bishabosha May 23, 2025
40c3237
fix formatting
bishabosha May 24, 2025
88ede3a
record SimpleTable tests in reference.md
bishabosha May 24, 2025
f152114
record more SimpleTable tests in reference.md
bishabosha May 24, 2025
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))

// 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
77 changes: 61 additions & 16 deletions docs/generateDocs.mill
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def generateTutorial(sourcePath: os.Path, destPath: os.Path) = {
var isDocs = Option.empty[Int]
var isCode = false
val outputLines = collection.mutable.Buffer.empty[String]
val snippets = collection.mutable.HashMap.empty[String, scala.collection.BufferedIterator[String]]
outputLines.append(generatedCodeHeader)
for (line <- os.read.lines(sourcePath)) {
val isDocsIndex = line.indexOf("// +DOCS")
Expand All @@ -25,6 +26,24 @@ def generateTutorial(sourcePath: os.Path, destPath: os.Path) = {
(suffix, isCode) match{
case ("", _) => outputLines.append("")

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 start = s"// +SNIPPET [$key]"
val end = s"// -SNIPPET [$key]"
while (sublines.hasNext && !sublines.head.contains(start)) {
sublines.next() // drop lines until we find the start
}
val indent = sublines.headOption.map(_.indexOf(start)).getOrElse(-1)
if (indent != -1) {
sublines.next() // skip the start line
while (sublines.hasNext && !sublines.head.contains(end)) {
outputLines.append(sublines.next().drop(indent))
}
} else {
outputLines.append("")
}
case (s"// +INCLUDE $rest", _) =>
os.read.lines(mill.api.WorkspaceRoot.workspaceRoot / os.SubPath(rest)).foreach(outputLines.append)

Expand All @@ -50,11 +69,14 @@ 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"))
.map{case (k, v) => (dropExprPrefix(k), v)}

val rawScalaStrs = records.flatMap(r => Seq(r.queryCodeString) ++ r.resultCodeString)
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")
Expand Down Expand Up @@ -124,6 +146,10 @@ def generateReference(dest: os.Path, scalafmtCallback: (Seq[os.Path], os.Path) =
|databases, due to differences in how each database parses SQL. These differences
|are typically minor, and as long as you use the right `Dialect` for your database
|ScalaSql should do the right thing for you.
|
|>**A note for users of `SimpleTable`**: The examples in this document assume usage of
|>`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]`.
|""".stripMargin
)
val recordsWithoutDuplicateSuites = records
Expand All @@ -132,15 +158,26 @@ def generateReference(dest: os.Path, scalafmtCallback: (Seq[os.Path], os.Path) =
.sortBy(_._2.head.suiteLine)
.distinctBy { case (k, v) => dropExprPrefix(k)}
.map{case (k, vs) => (dropExprPrefix(k), vs.map(r => r.copy(suiteName = dropExprPrefix(r.suiteName))))}
val ntRecordsWithoutDuplicateSuites = ntRecords
.groupBy(_.suiteName)
.toSeq
.sortBy(_._2.head.suiteLine)
.distinctBy { case (k, v) => dropNTExprPrefix(k)}
.map{case (k, vs) => (dropNTExprPrefix(k), vs.map(r => r.copy(suiteName = dropNTExprPrefix(r.suiteName))))}
.toMap

for((suiteName, suiteGroup) <- recordsWithoutDuplicateSuites) {
val seen = mutable.Set.empty[String]
outputLines.append(s"## $suiteName")
outputLines.append(suiteDescriptions(suiteName))
var lastSeen = ""
for(r <- suiteGroup){

val prettyName = (r.suiteName +: r.testPath).mkString(".")
var remainingNTRecords = ntRecordsWithoutDuplicateSuites
.get(suiteName)
.getOrElse(Seq.empty).groupBy {r =>
val prettyName = (r.suiteName +: r.testPath).mkString(".")
prettyName
}
def addRecord(r: Record, prettyName: String) = {
val titleOpt =
if (prettyName == lastSeen) Some("----")
else if (!seen(prettyName)) Some(s"### $prettyName")
Expand All @@ -151,21 +188,29 @@ def generateReference(dest: os.Path, scalafmtCallback: (Seq[os.Path], os.Path) =
lastSeen = prettyName
outputLines.append(
s"""$title
|
|${dedent(r.docs, "")}
|
|```scala
|${scalafmt(r.queryCodeString)}
|```
|
|${sqlFormat(r.sqlString)}
|
|${renderResult(r.resultCodeString)}
|
|""".stripMargin
|
|${dedent(r.docs, "")}
|
|```scala
|${scalafmt(r.queryCodeString)}
|```
|
|${sqlFormat(r.sqlString)}
|
|${renderResult(r.resultCodeString)}
|
|""".stripMargin
)
}
}
for(r <- suiteGroup){
val prettyName = (r.suiteName +: r.testPath).mkString(".")
addRecord(r, prettyName)
remainingNTRecords -= prettyName
}
for((prettyName, rs) <- remainingNTRecords; r <- rs) {
addRecord(r, prettyName)
}
}
os.write.over(dest, outputLines.mkString("\n"))
}
Expand Down
Loading