I am learning to access databases (specifically Sqlite and Postgres) in Rust. Database access requires a stack of libraries that consist of the following layers:
- A database access API specification. These specifications help library developers to provide a consistent API to access databases.
- Low-level database access libraries built on top of the specification.
- Object-Relational-Mapper (ORM) to map tables to classes.
- Migration Assistants Manage changes to the database schema
There are other types of libraries such as connection pools. However the layers mentioned above are the most common.
A project that uses a database will use libraries for each layer in the stack. An example stack from 3 popular programming languages are:
Layer | Python | Java | Golang |
---|---|---|---|
Specification | Python DB API | JDBC | database/sql |
DB Access Libraries | sqlite3 | sqlite-jdbc | sqlite3 |
ORM | SqlAlchemy | Hibernate | GORM |
Migration | Alembic | Flyway | golang-migrate |
Data Access Stack in Rust Link to heading
Database Access API Specification Link to heading
Rust does not yet have an API specification. While not a specification, the closest alternative is SQLx which has re-implemented database access to Sqlite, Postgres and MySQL with the same interface.
The lack of a specification adds a burden for libraries that have to work with different types of databases. For example, in Java an ORM is initialized with a JDBC driver. The ORM can use classes like Connection and Statement from the JDBC driver and assume that the APIs and behaviour are consistent across JDBC drivers.
This is not the case in Rust. Consider the native drivers for Postgres
and MySQL. Both the drivers have a Statement
class.
Struct mysql::Statement
has the following functions:
pub fn columns(&self) -> &[Column]
pub fn params(&self) -> &[Column]
pub fn id(&self) -> u32
pub fn connection_id(&self) -> u32
pub fn num_params(&self) -> u16
pub fn num_columns(&self) -> u16
Struct postgres::Statement
has the following functions:
pub fn params(&self) -> &[Type]
pub fn columns(&self) -> &[Column]
params
returns a different type. mysql::Statement
has more member functions.
Therefore a library that needs to connect to both MySQL and Postgres has to implement custom code for each one.
Database Access Libraries Link to heading
There are access libraries for all popular databases across the spectrum of OLTP and OLAP databases. The main concern is that some popular access libraries are of unknown quality.
The top open source OLTP databases: Postgres, MySQL and SQLite have native and well-documented drivers.
Object-Relational-Mapper & Migration Assistants Link to heading
There are two popular ORMs which also provide support for migrations:
There is a comparison of both the projects by the maintainers of SeaORM.
For starter projects both of these look like good choices. I could not find any information on which one scales better for web applications that have to scale.
My Starter Stack Link to heading
I am working on a command line application that has to access either Postgres or SQLite databases. It runs SQL queries and does not need an ORM. I will be using SQLx as it provides the same interface to access both databases.
The lack of a specification does not affect this project. I have However, it will be difficult if the project has to be extended to databases such as Snowflake and Presto in the future.