Skip to content

Commit

Permalink
More documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
szeiger committed Aug 15, 2012
1 parent 2ef90ba commit be1c55e
Show file tree
Hide file tree
Showing 3 changed files with 332 additions and 1 deletion.
120 changes: 120 additions & 0 deletions src/sphinx/code/LiftedEmbedding.scala
@@ -0,0 +1,120 @@
package scala.slick.docsnippets

import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

class LiftedEmbedding {

//#foreignkey
object Suppliers extends Table[(Int, String, String, String, String, String)]("SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey)
//...
//#foreignkey
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def city = column[String]("CITY")
def state = column[String]("STATE")
def zip = column[String]("ZIP")
// Every table needs a * projection with the same type as the table's type parameter
def * = id ~ name ~ street ~ city ~ state ~ zip
//#foreignkey
}

//#foreignkey
//#tabledef
//#reptypes
//#foreignkey
object Coffees extends Table[(String, Int, Double, Int, Int)]("COFFEES") {
//#foreignkey
def name = column[String]("COF_NAME", O.PrimaryKey)
//#reptypes
//#foreignkey
def supID = column[Int]("SUP_ID")
//#foreignkey
//#reptypes
def price = column[Double]("PRICE")
//#foreignkey
//#tabledef
//...
//#tabledef
//#foreignkey
//#reptypes
def sales = column[Int]("SALES")
def total = column[Int]("TOTAL")
def * = name ~ supID ~ price ~ sales ~ total
//#tabledef
//#foreignkeynav
//#foreignkey
def supplier = foreignKey("SUP_FK", supID, Suppliers)(_.id)
//#foreignkey
def supplier2 = Suppliers.where(_.id === supID)
//#foreignkeynav
//#foreignkey
//#tabledef
//#reptypes
}
//#foreignkey
//#reptypes
//#tabledef

//#plaintypes
case class Coffee(name: String, price: Double)
val l: List[Coffee] = //...
//#plaintypes
Nil
//#plaintypes
val l2 = l.filter(_.price > 8.0).map(_.name)
// ^ ^ ^
// Double Double String
//#plaintypes

//#reptypes
val q = Query(Coffees)
val q2 = q.filter(_.price > 8.0).map(_.name)
// ^ ^ ^
// Rep[Double] Rep[Double] Rep[String]
//#reptypes

//#mappedtable
case class User(id: Option[Int], first: String, last: String)

object Users extends Table[User]("users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def first = column[String]("first")
def last = column[String]("last")
def * = id.? ~ first ~ last <> (User, User.unapply _)
}
//#mappedtable

//#index
//#primarykey
object A extends Table[(Int, Int)]("a") {
def k1 = column[Int]("k1")
def k2 = column[Int]("k2")
def * = k1 ~ k2
//#index
def pk = primaryKey("pk_a", (k1, k2))
//#primarykey
//#index
def idx = index("idx_a", (k1, k2), unique = true)
//#primarykey
}
//#primarykey
//#index

val db: Database = null
//#ddl
val ddl = Coffees.ddl ++ Suppliers.ddl
db withSession {
ddl.create
//...
ddl.drop
}
//#ddl

//#ddl2
ddl.createStatements.foreach(println)
ddl.dropStatements.foreach(println)
//#ddl2

}
1 change: 1 addition & 0 deletions src/sphinx/index.rst
Expand Up @@ -42,5 +42,6 @@ Table of Contents
:maxdepth: 4

gettingstarted
lifted-embedding
direct-embedding
sql
212 changes: 211 additions & 1 deletion src/sphinx/lifted-embedding.rst
@@ -1,4 +1,214 @@
Lifted Embedding
================

tbd
The *lifted embedding* is the standard API for type-safe queries and updates
in Slick. Please see :doc:`gettingstarted` for an introduction. This chapter
describes the available features in more detail.

The name *Lifted Embedding* refers to the fact that you are not working with
standard Scala types (as in the :doc:`direct embedding <direct-embedding>`)
but with types that are *lifted* into a the ``scala.slick.lifted.Rep`` type
constructor. This becomes clear when you compare the types of a simple
Scala collections example

.. includecode:: code/LiftedEmbedding.scala#plaintypes

... with the types of similar code using the lifted embedding:

.. includecode:: code/LiftedEmbedding.scala#reptypes

All plain types are lifted into ``Rep``. The same is true for the record
type ``Coffees`` which is a subtype of ``Rep[(String, Int, Double, Int, Int)]``.
Even the literal ``8.0`` is automatically lifted to a ``Rep[Double]`` by an
implicit conversion because that is what the ``>`` operator on
``Rep[Double]`` expects for the right-hand side.

Tables
------

In order to use the lifted embedding, you need to define ``Table`` objects
for your database tables:

.. includecode:: code/LiftedEmbedding.scala#tabledef

Note that Slick clones your table objects under the covers, so you should not
add any extra state to them (extra methods are fine though). Also make sure
that an actual ``object`` for a table is not defined in a *static* location
(i.e. at the top level or nested only inside other objects) because this can
cause problems in certain situations due to an overeager optimization performed
by scalac. Using a ``val`` for your table (with an anonymous structural type
or a separate ``class`` definition) is fine everywhere.

All columns are defined through the ``column`` method. Note that they need to
be defined with ``def`` and not ``val`` due to the cloning. Each column has a
Scala type and a column name for the database (usually in upper-case). The
following primitive types are supported out of the box (with certain
limitations imposed by the individual database drivers):

- Boolean
- java.sql.Blob
- Byte
- Array[Byte]
- java.sql.Clob
- java.sql.Date
- Double
- Float
- Int
- Long
- Short
- String
- java.sql.Time
- java.sql.Timestamp
- Unit
- java.util.UUID
- BigDecimal

Nullable columns are represented by ``Option[T]`` where ``T`` is one of the
supported primitive types.

After the column name, you can add optional column options to a ``column``
definition. The applicable options are available through the table's ``O``
object. The following ones are defined for ``BasicProfile``:

``NotNull``, ``Nullable``
Explicitly mark the column a nullable or non-nullable when creating the
DDL statements for the table. Nullability is otherwise determined from the
type (Option or non-Option).

``PrimaryKey``
Mark the column as a (non-compound) primary key when creating the DDL
statements.

``Default[T](defaultValue: T)``
Specify a default value for inserting data the table without this column.
This information is only used for creating DDL statements so that the
database can fill in the missing information.

``DBType(dbType: String)``
Use a non-standard database-specific type for the DDL statements (e.g.
``DBType("VARCHAR(20)")`` for a ``String`` column).

``AutoInc``
Mark the column as an auto-incrementing key when creating the DDL
statements. Unlike the other column options, this one also has a meaning
outside of DDL creation: Many databases do not allow non-AutoInc columns to
be returned when inserting data (often silently ignoring other columns), so
Slick will check if the return column is properly marked as AutoInc where
needed.

Every table requires a ``*`` method contatining a default projection.
This describes what you get back when you return rows (in the form of a
table object) from a query. Slick's ``*`` projection does not have to match
the one in the database. You can add new columns (e.g. with computed values)
or omit some columns as you like. The non-lifted type corresponding to the
``*`` projection is given as a type parameter to ``Table``. For simple,
non-mapped tables, this will be a single column type or a tuple of column
types.

Mapped Tables
-------------

It is possible to define a mapped table that uses a custom type for its ``*``
projection by adding a bi-directional mapping with the ``<>`` operator:

.. includecode:: code/LiftedEmbedding.scala#mappedtable

It is optimized for case classes (with a simple ``apply`` method and an
``unapply`` method that wraps its result in an ``Option``) but there is also
an overload that operates directly on the mapped types.

Constraints
-----------

A foreign key constraint can be defined with a table's ``foreignKey`` method.
It takes a name for the constraint, the local column (or projection, so you
can define compound foreign keys), the linked table, and a function from that
table to the corresponding column(s). When creating the DDL statements for the
table, the foreign key definition is added to it.

.. includecode:: code/LiftedEmbedding.scala#foreignkey

Independent of the actual constraint defined in the database, such a foreign
key can be used to navigate to the linked data with a *join*. For this
purpose, it behaves the same as a manually defined utility method for finding
the joined data:

.. includecode:: code/LiftedEmbedding.scala#foreignkeynav

A primary key constraint can be defined in a similar fashion by adding a
method that calls ``primaryKey``. This is useful for defining compound
primary keys (which cannot be done with the ``O.PrimaryKey`` column option):

.. includecode:: code/LiftedEmbedding.scala#primarykey

Other indexes are defined in a similar way with the ``index`` method. They
are non-unique by default unless you set the ``unique`` parameter:

.. includecode:: code/LiftedEmbedding.scala#index

All constraints are discovered reflectively by searching for methods with
the appropriate return types which are defined in the table. This behavior
can be customized by overriding the ``tableConstraints`` method.

Data Definition Language
------------------------

DDL statements for a table can be created with its ``ddl`` method. Multiple
``DDL`` objects can be concatenated with ``++`` to get a compound ``DDL``
object which can create and drop all entities in the correct order, even in
the presence of cyclic dependencies between tables. The statements are
executed with the ``create`` and ``drop`` methods:

.. includecode:: code/LiftedEmbedding.scala#ddl

You can use the ``createStatements`` and ``dropStatements`` methods to get
the SQL code:

.. includecode:: code/LiftedEmbedding.scala#ddl2

Expressions
-----------

Primitive (non-compound, non-collection) values are representend by type
``Column[T]`` (a sub-type of ``Rep[R]``) where a ``TypeMapper[T]`` must
exist. Only some special methods for internal use and those that deal with
conversions between nullable and non-nullable columns are defined directly in
the ``Column`` class.

The operators and other methods which are commonly used in the lifted
embedding are added through implicit conversions defined in
``ExtensionMethodConversions``. The actual methods can be found in
the classes ``AnyExtensionMethods``, ``ColumnExtensionMethods``,
``NumericColumnExtensionMethods``, ``BooleanColumnExtensionMethods`` and
``StringColumnExtensionMethods``.

Collection values are represented by the ``Query`` class (a ``Rep[Seq[T]]``)
which contains many standard collection methods like ``flatMap``,
``filter``, ``take`` and ``groupBy``. Due to the two different component
types of a ``Query`` (lifted and plain), the signatures for these methods are
very complex but the semantics are essentially the same as for Scala
collections.

Additional methods for queries of non-compound values are added via an
implicit conversion to ``SingleColumnQueryExtensionMethods``.

Joins
-----

Unions
------

Aggregation
-----------

Querying
--------

Inserting and Updating
----------------------

Query Templates
---------------

User-Defined Functions and Types
--------------------------------

0 comments on commit be1c55e

Please sign in to comment.