io.eezo.db

package io.eezo.db

Members list

Type members

Classlikes

enum Check

Attributes

Source
Check.scala
Supertypes
trait Enum
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
final class Col[T, A](val name: String)(using val codec: Column[A])

A typed reference to one column of T. DESIGN §9.1.

A typed reference to one column of T. DESIGN §9.1.

name is the database name, so a Ref field carries its _id suffix and users never write one: the named tuple's label is the field name, while the value knows the column. It carries its Column[A] because the macro has already summoned one, and the query DSL needs it to bind parameters through the same codec path as encode.

Attributes

Source
Col.scala
Supertypes
class Object
trait Matchable
class Any
object Column

Attributes

Companion
trait
Source
Column.scala
Supertypes
class Object
trait Matchable
class Any
Self type
Column.type
trait Column[A]

Attributes

Companion
object
Source
Column.scala
Supertypes
class Object
trait Matchable
class Any
Self type
Column[A]
final case class ColumnDef(name: String, pgType: PgType, nullable: Boolean, primaryKey: Boolean, checks: List[Check], references: Option[String] = ...)

The runtime description of one column, produced by the Table macro.

The runtime description of one column, produced by the Table macro.

This type deliberately renders no DDL. Every create table, alter table, and create index in the framework comes out of schema.Ddl, driven by a Change — the fresh-database path included, via Differ.diff(SchemaSnap(Nil), _). One renderer means the migration path and the reset path cannot drift apart (BACKLOG §4).

Attributes

Companion
object
Source
ColumnDef.scala
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object ColumnDef

Attributes

Companion
class
Source
ColumnDef.scala
Supertypes
trait Product
trait Mirror
class Object
trait Matchable
class Any
Self type
ColumnDef.type
trait DbApp extends Dispatch, DbInit

The database layer's entry trait: the one an application on eezo-db alone extends.

The database layer's entry trait: the one an application on eezo-db alone extends.

object Main extends DbApp {
 override def schema: Schema = AppSchema
 override def boot(): Unit   = transact { /* a job over rows */ }
}

The layer is declarative. The application names its schema and says what boot does; the framework builds the Database from DbInit's overrides, installs it around the program, and closes it after. main is inherited from Dispatch: sbt run runs the program once with a database installed, and sbt "run status|sync|freeze|migrate|reset|drop|dump|ddl" are the schema commands (design/cli.md §4). dev and routes are the http layer's and do not exist here: eezoDev on a database only application prints the unknown command line on every save, and a rerun on save loop for a job is sbt's own ~run.

A Database is installed only around what needs one. ddl, dump, freeze and help never touch it, so they work with the database down; the program and the other commands get one for their whole duration. Installing never waits on the network: the pool starts empty (initializationFailTimeout negative), so Database.connect returns at once and the pool fills in the background. It is deliberately not eager, as Phoenix is not: with Postgres down, dev keeps serving every page that needs no database, and sbt run fails on the first query that needs Postgres, after the acquire timeout, rather than refusing to start.

Attributes

Source
DbApp.scala
Supertypes
trait DbInit
trait Dispatch
class Object
trait Matchable
class Any
Known subtypes
trait EezoApp
trait DbInit

How an application names its database: the connection settings and the per connection hook.

How an application names its database: the connection settings and the per connection hook.

Split out of DbApp so that a front end which is not the application's entry point (a deploy runner, the testkit) can build the same Database from the same settings without inheriting boot. DbApp extends this, and the umbrella's EezoApp through it; users override members here and never name the trait.

Named for databaseInit, its one member that is not a string, because it is the one people get wrong; see its comment.

Two spellings of the environment, one mechanism. EEZO_DB_URL/EEZO_DB_USER/ EEZO_DB_PASS are eezo's own and always win. When they are absent and DATABASE_URL is present (the postgres://user:pass@host/db form every PaaS injects: Fly, Heroku, Render), it is parsed into the three, so a deployed application configures itself from the one secret the platform already set. This is a fallback, not a second configuration system: the members below are still the only way the settings are read.

EEZO_DB_POOL_SIZE (default 10) and EEZO_DB_ACQUIRE_TIMEOUT (milliseconds, default 5000) size the pool; DATABASE_URL has no say in either.

Attributes

Companion
object
Source
DbInit.scala
Supertypes
class Object
trait Matchable
class Any
Known subtypes
trait DbApp
trait EezoApp
object DbInit

Attributes

Companion
trait
Source
DbInit.scala
Supertypes
class Object
trait Matchable
class Any
Self type
DbInit.type
object Expr

Attributes

Companion
enum
Source
Expr.scala
Supertypes
trait Sum
trait Mirror
class Object
trait Matchable
class Any
Self type
Expr.type
enum Expr[T]

A predicate over T, as the small closed ADT BACKLOG §7 asks for. DESIGN §9.2.

A predicate over T, as the small closed ADT BACKLOG §7 asks for. DESIGN §9.2.

Values never reach the SQL string: each becomes a ? and a Bind that goes through the same Column[A] the macro summoned for encode. Injection is not defended against here, it is unrepresentable.

Attributes

Companion
object
Source
Expr.scala
Supertypes
trait Enum
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object JdbcStore

The persistent half of io.eezo.core.Store: a model's rows in Postgres, read and written through its Table[A].

The persistent half of io.eezo.core.Store: a model's rows in Postgres, read and written through its Table[A].

It is what a model deriving Table gets in the generated route table, where the in-memory half in http is what every other model gets. Neither module sees the other; core's trait is the only thing both name.

A plain factory rather than a given. Implicit search for io.eezo.core.Store[Post] looks in object Store in core and in object Post, and a given in this package sits in neither, so it would need an import at every call site to be found at all. The generated table names this object outright instead.

The Table[A] it needs resolves without help wherever it is called, because derives Table puts the instance in the model's own companion.

Attributes

Source
JdbcStore.scala
Supertypes
class Object
trait Matchable
class Any
Self type
JdbcStore.type
final case class Order[T](column: String, direction: String)

One order by term. Postgres's own NULL ordering is used — last for asc, first for desc — rather than an explicit NULLS clause. DESIGN §9.4.

One order by term. Postgres's own NULL ordering is used — last for asc, first for desc — rather than an explicit NULLS clause. DESIGN §9.4.

Attributes

Source
Expr.scala
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
enum PgType

Attributes

Source
PgType.scala
Supertypes
trait Enum
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object Query

Attributes

Companion
class
Source
Query.scala
Supertypes
trait Product
trait Mirror
class Object
trait Matchable
class Any
Self type
Query.type
final case class Query[T](table: Table[T], pred: Expr[T] = ..., ord: List[Order[T]] = ..., lim: Option[Int] = ..., off: Option[Int] = ...)

A query, as a pure description. DESIGN §9.3.

A query, as a pure description. DESIGN §9.3.

It holds no capability, so it can be built anywhere, passed around, composed conditionally and unit-tested without a database. Only the terminals require DB, and they are strict — there is no cursor here to outlive the scope that produced it.

Attributes

Companion
object
Source
Query.scala
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object Ref

Attributes

Source
Ref.scala
Supertypes
class Object
trait Matchable
class Any
Self type
Ref.type
trait RefTarget[A]

Attributes

Companion
object
Source
Ref.scala
Supertypes
class Object
trait Matchable
class Any
object RefTarget

Attributes

Companion
trait
Source
Ref.scala
Supertypes
class Object
trait Matchable
class Any
Self type
RefTarget.type
object Schema

Attributes

Companion
class
Source
Schema.scala
Supertypes
class Object
trait Matchable
class Any
Self type
Schema.type
abstract class Schema

Attributes

Companion
object
Source
Schema.scala
Supertypes
class Object
trait Matchable
class Any
final case class SchemaError(msg: String) extends RuntimeException, NoStackTrace

Attributes

Source
SchemaError.scala
Supertypes
trait Product
trait Equals
trait NoStackTrace
class Exception
class Throwable
trait Serializable
class Object
trait Matchable
class Any
Show all
object Scopes

The scopes: the whole database API's entry points. DESIGN §8.2.

The scopes: the whole database API's entry points. DESIGN §8.2.

import io.eezo.db.Scopes.* and write transact { ... }; no Database is ever named.

Attributes

Source
Scopes.scala
Supertypes
class Object
trait Matchable
class Any
Self type
Scopes.type
object Table

Attributes

Companion
trait
Source
Table.scala
Supertypes
class Object
trait Matchable
class Any
Self type
Table.type
trait Table[T]

Attributes

Companion
object
Source
Table.scala
Supertypes
class Object
trait Matchable
class Any
final case class TableDef(name: String, columns: List[ColumnDef])

A table's columns, for the statements that address rows rather than shape.

A table's columns, for the statements that address rows rather than shape.

Attributes

Source
ColumnDef.scala
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
final class TableSpec[T](val table: Table[T])

Attributes

Source
Schema.scala
Supertypes
class Object
trait Matchable
class Any

Types

type Bind = (PreparedStatement, Int) => Unit

Binds one parameter. Pure: it closes over a value and its codec, both of which capture nothing, and saying so is what lets these be stored in the tree below.

Binds one parameter. Pure: it closes over a value and its codec, both of which capture nothing, and saying so is what lets these be stored in the tree below.

Attributes

Source
Expr.scala
type ColsOf[T] = Map[From[T], [A] =>> Col[T, A]]

The columns of T, as a named tuple: cols.title is a Col[T, String].

The columns of T, as a named tuple: cols.title is a Col[T, String].

Computed from the type T, which is what makes it work at all. Backlog §7's blocker was that derives Table expands to a given with an ascribed type Table[Book], widening away any refinement — so Cols could not be a type member. There is no refinement here to widen.

Attributes

Source
Col.scala
opaque type Ref[T]

Attributes

Source
Ref.scala

Extensions

Extensions

extension [T](t: Table[T])
def all()(using db: DB): List[T]

Strict, and deliberately so: a lazy result would hold the scope and escape the block that created it. A cursor arrives with the query DSL (DESIGN §9), declaring its capture.

Strict, and deliberately so: a lazy result would hold the scope and escape the block that created it. A cursor arrives with the query DSL (DESIGN §9), declaring its capture.

Attributes

Source
Crud.scala
def delete(id: Id[T])(using tx: Tx): Int

The count rather than an exception, for the reason updateById gives: a missing row is an answer, and only the caller knows whether it matters.

The count rather than an exception, for the reason updateById gives: a missing row is an answer, and only the caller knows whether it matters.

eezo deletes by primary key and tracks no versions, so an update that lands first is erased by a later delete without either writer learning of the other: the edit is lost silently. An update that lands after the delete is not silent, it sees 0. Here 0 says only that the row was already gone.

Attributes

Source
Crud.scala
def deleteAll()(using tx: Tx): Int

The table wipe.

The table wipe.

Attributes

Source
Crud.scala
def deleteWhere(f: ColsOf[T] => Expr[T])(using tx: Tx): Int

Deleting takes a predicate, always. There is no delete() terminal on Query, so a dropped .where during a refactor cannot silently become a table wipe — it stops compiling. DESIGN §9.4, and §3.6: destructive and risky are different, and the destructive one says its own name.

Deleting takes a predicate, always. There is no delete() terminal on Query, so a dropped .where during a refactor cannot silently become a table wipe — it stops compiling. DESIGN §9.4, and §3.6: destructive and risky are different, and the destructive one says its own name.

Attributes

Source
Crud.scala
def findById(id: Id[T])(using db: DB): Option[T]

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

Extensions rather than methods on the trait, so that Table[T] stays a plain description: the migration layer, the differ and Form[A] all consume it, and none of them should acquire a dependency on a transaction capability. Reading requires DB, writing requires Tx, and TxCap <: DBCap is what lets a read sit inside a transaction and not the reverse.

No Database appears in any signature here. The scope came from transact or read.

Attributes

Source
Crud.scala
def insert(row: T)(using tx: Tx): Unit

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

Extensions rather than methods on the trait, so that Table[T] stays a plain description: the migration layer, the differ and Form[A] all consume it, and none of them should acquire a dependency on a transaction capability. Reading requires DB, writing requires Tx, and TxCap <: DBCap is what lets a read sit inside a transaction and not the reverse.

No Database appears in any signature here. The scope came from transact or read.

Attributes

Source
Crud.scala
def orderBy(f: ColsOf[T] => Order[T]): Query[T]

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

Extensions rather than methods on the trait, so that Table[T] stays a plain description: the migration layer, the differ and Form[A] all consume it, and none of them should acquire a dependency on a transaction capability. Reading requires DB, writing requires Tx, and TxCap <: DBCap is what lets a read sit inside a transaction and not the reverse.

No Database appears in any signature here. The scope came from transact or read.

Attributes

Source
Crud.scala
def query: Query[T]

Starts a query. Table[Book].where(...) is shorthand for Table[Book].query.where(...).

Starts a query. Table[Book].where(...) is shorthand for Table[Book].query.where(...).

Attributes

Source
Crud.scala
def update(row: T)(using tx: Tx): Int

Writes every column of row, matched by its own key.

Writes every column of row, matched by its own key.

The later write wins: two edits to different fields of the same row end with the second overwriting the first, including fields it never touched. That is the accepted default (DESIGN §8.6). A count of 0 is the one conflict it can still report, and it is where an optimistic concurrency check would later answer too: a version in the where turns a stale write into the same 0, so the signature would not change.

Attributes

Source
Crud.scala
def updateById(id: Id[T], row: T)(using tx: Tx): Int

The row to write found by id rather than by the key inside row.

The row to write found by id rather than by the key inside row.

The two coincide for every caller inside db, which is why update exists at all. They come apart at core's Store[A], whose update(key, row) promises to replace "the row under key": binding id here is what makes a stale key report false rather than write to row's own key and report true, which is the answer the in-memory half gives.

The count comes back rather than an exception because a write that matched nothing is an answer, not a failure. The caller that cares asks, and raising would have rolled back every other write in the same transact over a row that was simply not there.

0 is also all the conflict detection there is. eezo writes by primary key and tracks no versions, so when two writers edit the same row both see 1 and the later one wins silently: the earlier edit is lost without a trace. 0 says only that the row was never inserted or that something deleted it after it was read.

Attributes

Source
Crud.scala
def where(f: ColsOf[T] => Expr[T]): Query[T]

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

CRUD by primary key, as extensions on the derived Table[T]. DESIGN §8.2.

Extensions rather than methods on the trait, so that Table[T] stays a plain description: the migration layer, the differ and Form[A] all consume it, and none of them should acquire a dependency on a transaction capability. Reading requires DB, writing requires Tx, and TxCap <: DBCap is what lets a read sit inside a transaction and not the reverse.

No Database appears in any signature here. The scope came from transact or read.

Attributes

Source
Crud.scala

Exports

Defined exports

final type DB = DB
Exported from Capability$package$

Attributes

Source
Exports.scala
final type Tx = Tx
Exported from Capability$package$

Attributes

Source
Exports.scala