Skip to content

Commit

Permalink
v0.8.0 MongoDB added
Browse files Browse the repository at this point in the history
  • Loading branch information
marcgrue committed Feb 25, 2024
1 parent f0ef4f3 commit aecd4d2
Show file tree
Hide file tree
Showing 25 changed files with 232 additions and 65 deletions.
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
![](project/resources/Molecule-logo.png)



Molecule is a library to write type-inferred intuitive Scala code that translates to queries and
transactions for

![](project/resources/Molecule-logo.png)

Write vanilla Scala code with your domain terms to access various databases:


![img.png](img.png)
Molecule is a Scala library to build queries and transactions with the words of your domain for various databases:

- Document
- [MongoDB](http://www.mongodb.com)
Expand All @@ -22,12 +17,23 @@ Write vanilla Scala code with your domain terms to access various databases:
- [H2](https://h2database.com/html/main.html)


Molecule generates boilerplate code from your domain data model. You can then access multiple databases in a uniform way with
"molecules" like this:

```scala
val persons = Person.name.age.Adress.street.query.get
```
The returned `persons` are typed as `List[(String, Int, String)]` and can also be fetched asynchronously as a `Future` or a `ZIO`.
Notice how the relationship from Person to Adress is easily created. Much more complex queries can also be created
easily without knowing the query languages of the underlying databases.


### Features

- Targets Scala 3.3, 2.13 and 2.12 on JVM and JS platforms
- Typed database calls directly from Client with no need for Server implementation or JSON encoding/decoding
- Fast transparent binary serialization between Client and Server with [Boopickle](https://boopickle.suzaku.io) (no manual setup)
- Single SPI of ~1700 tests adhered to by each database implementation
- Single SPI of +1800 tests adhered to by each database implementation
- No macros
- No complex type class implicits
- Maximum type inference
Expand Down Expand Up @@ -65,18 +71,19 @@ Write vanilla Scala code with your domain terms to access various databases:
- Pagination (offset/cursor)
- Sorting
- Subscriptions
- Fallback API for native database inspection/query/transaction

Documentation at [scalamolecule.org](http://scalamolecule.org) still documents the old macro-based version of molecule
but will be updated to the new version. Most concepts overlap.

### How does it work?

Run `sbt compile -Dmolecule=true` once to generate molecule-enabling boilerplate code from
1) Define a domain data model.
2) Run `sbt compile -Dmolecule=true` once to generate molecule-enabling boilerplate code from
your [domain data model definition](https://github.com/scalamolecule/molecule/tree/main/coreTests/shared/src/main/scala/molecule/coreTests/dataModels/core/dataModel).
The [sbt-molecule](https://github.com/scalamolecule/sbt-molecule) plugin automatically also creates database schemas for
all database types. Now you can easily read and write data to/from a database with plain vanilla Scala code in a fluent
style (see examples below).
3) Write molecule transactions/queries.

### Examples

Expand Down Expand Up @@ -176,8 +183,8 @@ lazy val yourProject = project.in(file("app"))
.settings(
libraryDependencies ++= Seq(
// One or more of:
"org.scalamolecule" %%% "molecule-datalog-datomic" % "0.8.0",
"org.scalamolecule" %%% "molecule-document-mongodb" % "0.8.0",
"org.scalamolecule" %%% "molecule-datalog-datomic" % "0.8.0",
"org.scalamolecule" %%% "molecule-sql-h2" % "0.8.0",
"org.scalamolecule" %%% "molecule-sql-mariadb" % "0.8.0",
"org.scalamolecule" %%% "molecule-sql-mysql" % "0.8.0",
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ inThisBuild(
organization := "org.scalamolecule",
organizationName := "ScalaMolecule",
organizationHomepage := Some(url("http://www.scalamolecule.org")),
version := "0.8.0-SNAPSHOT",
versionScheme := Some("early-semver"),
version := "0.8.0",
// version := "0.8.0-SNAPSHOT",
// scalaVersion := scala212,
scalaVersion := scala213,
// scalaVersion := scala3,
Expand Down
4 changes: 2 additions & 2 deletions core/shared/src/main/scala/molecule/core/spi/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ trait Renderer {
): Unit = {
val render = List(
if (elements.isEmpty) None else Some(elements.mkString("\n").trim),
if (dbString.isBlank) None else Some(dbString),
if (dataString.isBlank) None else Some(dataString),
if (dbString.isEmpty) None else Some(dbString),
if (dataString.isEmpty) None else Some(dataString),
).flatten.mkString("\n\n")
println(
s"""========================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,53 @@ package molecule.coreTests.dataModels.core.dataModel

import molecule.DataModel

object Partitions extends DataModel(3) {
object Partitions extends DataModel(5) {

object partA {
trait Ns {
val int = oneInt
val string = oneString
val ref1 = one[Ref1]
object gen {
trait Profession {
val name = oneString
}
trait Ref1 {
val str1 = oneString.descr("foo")
val int1 = oneInt.unique.descr("bar").alias("hej")

trait Person {
val name = oneString
val gender = oneString.enums("male", "female")
val professions = many[Profession]
}
}

object lit {
trait Book {
val title = oneString
val author = one[gen.Person]
// To avoid attr/partition name clashes we can prepend the definition object name
// (in case we would have needed an attribute named `gen` for instance)
val editor = one[gen.Person]
val cat = oneString.enums("good", "bad")
val reviewers = many[gen.Person]
}
}

object partB {
trait Ns {
val int = oneInt
val string = oneString
val ref1 = one[Ref1]

// other domain

object accounting {
trait Invoice {
val no = oneInt
val mainProduct = one[warehouse.Item]
val lines = many[InvoiceLine]
}
trait Ref1 {
val str1 = oneString.descr("foo")
val int1 = oneInt.unique.descr("bar").alias("hej")
trait InvoiceLine {
val text = oneString
val qty = oneInt
val product = one[warehouse.Item]
val invoice = one[Invoice]
}
}
}

object warehouse {
trait Item {
val name = oneString
val invoices = many[accounting.Invoice]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ trait CoreTestSuiteBase
def refs[T](test: Conn => T): T = inMem(test, RefsSchema)
def unique[T](test: Conn => T): T = inMem(test, UniquesSchema)
def validation[T](test: Conn => T): T = inMem(test, ValidationSchema)
def partition[T](test: Conn => T): T = inMem(test, PartitionsSchema)

def delay[T](ms: Int)(body: => T): Future[T]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package molecule.coreTests.spi.partitions

import molecule.core.api.ApiAsync
import molecule.core.spi.SpiAsync
import molecule.core.util.Executor._
import molecule.coreTests.async._
import molecule.coreTests.dataModels.core.dsl.Partitions._
import molecule.coreTests.setup.CoreTestSuite
import utest._


trait PartitionTests extends CoreTestSuite with ApiAsync { spi: SpiAsync =>

lazy val tests = Tests {

"Nested 2 levels" - partition { implicit conn =>
for {
_ <- lit_Book.title.Reviewers.name.Professions.*(gen_Profession.name)
.insert("book", "Jan", List("Musician")).transact

_ <- lit_Book.title.Reviewers.name.Professions.*(gen_Profession.name)
.query.get.map(_ ==> List(("book", "Jan", List("Musician"))))

// Same as
_ <- lit_Book.title.Reviewers.Professions.*(gen_Profession.name)
.query.get.map(_ ==> List(("book", List("Musician"))))
} yield ()
}


"Back only" - partition { implicit conn =>
for {
_ <- lit_Book.title("A good book").cat("good").Author.name("Marc").save.transact
_ <- lit_Book.title.Author.name._lit_Book.cat
.query.get.map(_ ==> List(("A good book", "Marc", "good")))
} yield ()
}


"Adjacent" - partition { implicit conn =>
for {
_ <- lit_Book.title.Author.name._lit_Book.Reviewers.name
.insert("book", "John", "Marc").transact

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.name
.query.get.map(_ ==> List(("book", "John", "Marc")))

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(gen_Person.name)
.query.get.map(_ ==> List(("book", "John", List("Marc"))))
} yield ()
}


"Nested" - partition { implicit conn =>
for {
_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(gen_Person.name)
.insert("book", "John", List("Marc")).transact

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.name
.query.get.map(_ ==> List(("book", "John", "Marc")))

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(gen_Person.name)
.query.get.map(_ ==> List(("book", "John", List("Marc"))))
} yield ()
}


"Nested + adjacent" - partition { implicit conn =>
for {
_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(
gen_Person.name.Professions.name
).insert("book", "John", List(("Marc", "Musician"))).transact

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.name.Professions.name
.query.get.map(_ ==> List(("book", "John", "Marc", "Musician")))

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(gen_Person.name.Professions.name)
.query.get.map(_ ==> List(("book", "John", List(("Marc", "Musician")))))
} yield ()
}


"Nested + nested" - partition { implicit conn =>
for {
_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(
gen_Person.name.Professions.*(
gen_Profession.name))
.insert("book", "John", List(("Marc", List("Musician")))).transact

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.name.Professions.name
.query.get.map(_ ==> List(("book", "John", "Marc", "Musician")))

_ <- lit_Book.title.Author.name._lit_Book.Reviewers.*(
gen_Person.name.Professions.*(
gen_Profession.name))
.query.get.map(_ ==> List(("book", "John", List(("Marc", List("Musician"))))))
} yield ()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package molecule.datalog.datomic.compliance.partitions

import molecule.coreTests.spi.partitions.PartitionTests
import molecule.datalog.datomic.setup.TestAsync_datomic

object PartitionTests extends PartitionTests with TestAsync_datomic
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ object Connection_mongodb {
val conn_Refs = MongoConn_JVM(proxy(RefsSchema), mongoClient, dbName, mongoDb)
val conn_Uniques = MongoConn_JVM(proxy(UniquesSchema), mongoClient, dbName, mongoDb)
val conn_Validation = MongoConn_JVM(proxy(ValidationSchema), mongoClient, dbName, mongoDb)
val conn_Partitions = MongoConn_JVM(proxy(PartitionsSchema), mongoClient, dbName, mongoDb)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ trait TestSuite_mongodb extends CoreTestSuite with BaseHelpers {
case RefsSchema => MongoHandler_JVM.recreateDb(c.conn_Refs)
case UniquesSchema => MongoHandler_JVM.recreateDb(c.conn_Uniques)
case ValidationSchema => MongoHandler_JVM.recreateDb(c.conn_Validation)
case PartitionsSchema => MongoHandler_JVM.recreateDb(c.conn_Partitions)
}
test(conn)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package molecule.document.mongodb.compliance.partitions

import molecule.coreTests.spi.partitions.PartitionTests
import molecule.document.mongodb.setup.TestAsync_mongodb

object PartitionTests extends PartitionTests with TestAsync_mongodb
3 changes: 2 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1")

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
addSbtPlugin("org.scalamolecule" % "sbt-molecule" % "1.7.0-SNAPSHOT")
addSbtPlugin("org.scalamolecule" % "sbt-molecule" % "1.7.0")
//addSbtPlugin("org.scalamolecule" % "sbt-molecule" % "1.7.0-SNAPSHOT")

libraryDependencies += "org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0"
Binary file added project/resources/dblogos/datomic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,15 @@ case class JdbcConn_JVM(
try {
val resultSet = ps.getGeneratedKeys
ids = List.empty[Long]
if (!refPath.last.init.contains("_")) { // no join tables, but still "_"-suffixed tables

// No join tables (also without collision prevention "_"-suffix of table names)
// ns_join_ref 2 glues
// part_ns_join_part_ref 4 glues
val glues = refPath.last.init.count(_ == '_')
if (glues != 2 && glues != 4) {
while (resultSet.next()) {
// val id = resultSet.getLong(1)
// debug("D ################# " + id)
// debug(" ################# " + id)
// ids = ids :+ id
ids = ids :+ resultSet.getLong(1)
}
Expand Down
Loading

0 comments on commit aecd4d2

Please sign in to comment.