Getting started
DORM is an SQL ORM using the data mapper pattern for the D programming language built on top of RORM.
Setting up a Project
DORM uses the DUB package manager that usually comes bundled with the D compilers or can be installed through most package managers. To use DORM in your existing project, simply add dorm
to your dub.json/dub.sdl recipe. This can be done on the command line using
Then either start by writing code immediately or setup the common file structure like what the project templates do. (See below)
See Defining Models to continue.
The dorm
dependency automatically downloads the rorm-lib
library as pre-compiled binary for the target platform as well as a pre-compiled rorm-cli
CLI application for the host platform. Due to possible FFI changes for rorm-lib, no automatic updates to the underlying libraries are done. DORM always downloads a pinned version of rorm-lib, previously verified to be working by the maintainers of the DORM project. The library releases are GPG-signed, but the signature is not checked by DORM. File integrity for the correct version is however checked by DORM. (the checksum is stored with the pinned dependency version and link)
Info
The automatic rorm-lib
download also downloads the rorm-cli
CLI tool. Both these binaries are stored inside the dependency package directory (e.g. ~/.dub/packages/dorm-v0.1.0/dorm/
) rorm-cli
is simply wrapped by dub run dorm
, so it may also be installed globally and used as standalone application. (Using it directly has much faster startup time than using dub run dorm
and will not trigger dub to do any internet lookup to see if the package is up-to-date)
You can change which tool to show in this documentation in these tabs:
dub run dorm -- args...
will be used in the instructions, which comes with the disadvantage of being slower to run, but does not put anything in PATH.
rorm-cli args...
will be used in the instructions, which comes with the advantage of being a regular executable that can be updated separately.
Starting a new Project
If you want to start a new project, dorm comes with project templates you can use:
# DORM with use with vibe-d (web server)
dub init <project-name> -t dorm
# or standalone
dub init <project-name> -t dorm -- base
What this does is adding the dorm
and dorm:build-models
as dependencies to the project, adds a models.d
file that contains all the models in a declarative format (see section below) and puts some basic connection code inside of app.d. (See DORM APIs)
Defining Models
A model is the code representation for data stored in an SQL database. In DORM they are defined using a class
that inherits from Model
:
/**
Contains declarative definitions of database models. (maps to SQL tables)
*/
module models;
import dorm.design; // (1)
mixin RegisterModels; // (2)
// example model
class User : Model
{
@Id long id;
@maxLength(255)
string username;
short age;
}
class Car : Model
{
@maxLength(255)
string brand;
@maxLength(255)
string color;
@primaryKey
long serialNo;
}
-
imports common things needed in this modeling module. Should not be used outside of
module models;
because it adds quite a lot of stuff to the global namespace, which might not be useful elsewhere. -
mixin RegisterModels
makes it so the models defined in this module can be exported to the internal JSON representation (.models.json
) that is used byrorm-cli
/dub run dorm
to automatically create migration files that can be used to initialize the DB.Note that this only does something if the
dorm:build-models
dependency is added to the active compilation.For more infos see Exporting Models
This will then later automatically be used by the auto-migrator to create actual SQL tables and handle application upgrades for users automatically. Some types, for example strings, have mandatory annotations like @maxLength
. See Model Declaration for further details.
All Models in DORM should be put into a single file, commonly called models.d
. This file should be put directly inside inside the source root folder of the project for applications. Libraries defining models for applications should make their models abstract
classes when intended to make them extensible or define mixin templates
to use them as-is when requested by the application developer.
Exporting Models
The dorm:build-models
DUB dependency, which is added by default using the dorm dub init
template, will automatically generate a .models.json
file whenever the project is built from the models definition D file. For this to work, mixin RegisterModels;
must be included inside the models definition file.
Then, after having built the project at least once, using dub build
or dub run
, the current models are automatically contained inside the .models.json
file. This file can then further be used by the CLI tool (dub run dorm
or rorm-cli
when installed as standalone)
Internally this works adding a postBuildCommand
when building as an executable and automatically running the app that was built with the argument --DORM-dump-models-json
. This argument is handled by the RegisterModels
mixin using a shared static this()
module constructor. This will be run before any other code that doesn't import the models definition module and exit the app before reaching user-code.
Info
The key takeaway here is that you just need to add dorm:build-models
as a dependency, run dub build
or dub run
once and then you can use the auto-migrator to generate migrations and create the database.
Generating Migrations
Migrations can be created by running this CLI command:
This command will read the automatically generated models definition file .models.json
to compute the required database migrations as TOML files in the directory migrations/
.
Note
The actual database table creation (SQL) is done in a later step using rorm-cli migrate
. First we need to setup the database connection information.
Migrations can also be created manually, without the need for the auto-migrator or the dorm:build-models
sub-package. Head over to the migration files documentation for details about the file format.
For more information see rorm-cli make-migrations
Configuring the Database
DORM and the CLI tool (rorm-cli
or dub run dorm
) need to know how to connect to the database for migrations and for use in code. By convention this is stored inside a database.toml
TOML configuration file inside the app/project folder, e.g. where the migrations/
folder is present. The TOML file format is very similar to INI.
The database connection configuration consists of a [Database]
section which contains a Driver
key and some driver-specific options. A simple example using an SQLite database looks like this:
Tip
If you don't have any database.toml
file yet, you can create a template one using
Creating Tables / Running Migrations
When both a migrations
folder and the database.toml
exist, as described in the previous two sections, running
will create missing tables, alter fields, etc.
For more information see rorm-cli migrate
Intro to DORM APIs
The entry-point to all DB-related DORM APIs is the DormDB struct. To use it, you need to first setup the DORM runtime using mixin SetupDormRuntime;
somewhere in global scope. Then you can create a DormDB
instance simply by calling its constructor with connection options as parameter:
// app.d
import dorm.api.db;
import models;
// required to use DORM (use in global scope)
mixin SetupDormRuntime;
void main()
{
auto appConfig = parseTomlConfig!BareConfiguration("database.toml");
auto db = DormDB(appConfig.database);
}
The db
variable here can be used to do all kinds of DB-related operations. Most APIs use the Builder pattern.
The parseTomlConfig!BareConfiguration("database.toml")
call reads the database.toml file in the current directory and parses it as TOML file. DORM defines BareConfiguration
like so:
struct BareConfiguration
{
import mir.serde : serdeKeys;
// make sure you put custom fields before database!
/// DORM database settings
@serdeKeys("Database") DBConnectOptions database;
}
by copy-pasting this definition you can simply define your own configuration struct and add custom server configuration things inside there for a unified config file for all the app settings. Then in the parseTomlConfig
call the custom struct is passed in as template parameter instead of BareConfiguration
. It's also possible to use a different configuration entirely, however rorm-cli
/ dub run dorm
will be unable to use the correct database connection parameters in that case.
The following examples assume the Models definition example from Defining Models is used.
Select / Find / Query
Use the select
method on the DormDB
object to start a SELECT
operation. You can query for Models or Patches. This returns a builder and can further use the methods condition
, limit
/offset
/range
and orderBy
to manipulate what this select does.
The data can be retrieved using:
- stream
as a regular D range that can be iterated over, be filtered, etc., which lazily fetches the data from the DB,
- array
to eagerly fetch all data into a single array,
- findOne
to fetch one row or error if there is none,
- findOptional
to fetch one row or return null if there is none.
Insert
Use the insert
method on the DormDB
object to perform an INSERT
operation. You can pass a regular Model, a list of Models, a Patch or a list of Patches into this.
Update
Use the update
method on the DormDB
object to start an UPDATE
operation. You pass a Model class into the template parameter. This returns a builder struct on which you can call the methods condition
and set!"fieldName"(value)
or set(Patch)
to manipulate what will be done. When ready to send the update, call .await
to perform the update.
Remove / Delete
Use the remove
method on the DormDB
object to either delete an instance from the DB or start a DELETE
operation. When passing in a Model instance or a Patch with the primary key field, it will be deleted immediately. When passing in a Model type as template parameter, a builder is returned. On this builder all methods immediately finish right now, so it's not strictly a builder, but rather groups together the different delete methods. You can call byCondition
, single
or all
to actually perform the delete.
// SELECT brand, color, serial_no FROM car
auto car = db.select!Car.findOptional;
if (!car.isNull)
{
// DELETE FROM car WHERE serial_no = ?
db.remove(car.get);
}
// DELETE FROM car WHERE serial_no > 1337
db.remove!Car
.byCondition(c => c.serialNo.greaterThan(1337));
// DELETE FROM car
auto affectedRows = db.remove!Car.all();
Patches
Often you don't want to get every available column when querying data and often you don't want to provide every defined column when inserting data. To avoid clashing with the default values or potentially using wrong values, DORM decides to always use all provided columns inside the passed in object. You can however pass in partial objects, so called "Patches", which can take a few different forms:
- usually they are structs
, annotated with @DormPatch!MyModel
, containing just the fields that you want to use (without annotations)
- they can also simply be a struct
immediately inside a Model
class (called implicit patches) - these are not annotated with DormPatch
and contain the definition of all fields. To avoid code duplication, you use them inside the class as members and use @embedded
to embed all members as fields.
- where applicable, tuples defining the requested field names along with the Model type as template parameter can be used as patches as well.
Patch structs may be defined anywhere (can be put inside the models
module or just before using them inside a method)
When using insert
, only the fields that are defined in the patch as well as @constructValue
annotated fields will be included in the actual SQL insert statement. Everything else is checked by DORM to be done by the database / table, which would previously be created using the rorm-cli migrator. Forgetting fields in an insert will result in a compile time error if these fields don't have any attributes that make them auto-generated.
When using select
to query rows, only the specified columns will be included in the result, which can speed up query performance.
Patches can be used inside update.set
to set all fields defined in the patch.
Patches containing the primary key can be used as parameter to the remove
method.