SQL_Bridge

SQL Bridge Tests & Dialyzer

An Erlang SQL Abstraction layer for interfacing with SQL databases.

Supported Databases

* MySQL
* PostgreSQL

Planned for support

* Microsoft SQL Server
* SQLite

What it does

It provides a simple layer on top of existing SQL drivers, abstracting away the pool management, and providing sensible default behaviors. Further, it provides a whole slew of methods for returning and updating data: lists, tuples, proplists, dicts, existence checks, single fields returning and setting, updating data from proplists, and more.

Connection Conventions

Configuration

module_alias

Probably the most unusual configuration to discuss is the module_alias.

This configuration option allows us to compile a module which exports all the functions of sql_bridge, but allows us to use a different module name.

In my own apps, I tend to use a module called db which serves as the alias to sql_bridge. While it's easy enough to make your own module and do an import, this configuration parameter allows us to skip that step.

This alias module is generated and loaded during the sql_bridge:start() function.

lookup

The most important configuration variable is the lookup variable. This tells SQL_Bridge which database to use at any given time.

It can take two possible values:

adapter

As the final important config variable, adapter determines which database driver to use. Currently, SQL_Bridge ships with the following adapters (And the driver upon which it depends)

Sample Config

There is a sample config file in sample.config, but here are the configuration settings currently available:

[
    {sql_bridge, [
        %% module_alias creates a module with the specified name, which can be
        %% used as an alias to the sigma_sql module.
        {module_alias, db},

        %% There are three adapters that can be used:
        %%  + sql_bridge_epgsql: PostgreSQL adapter using epgsql
        %%  + sql_bridge_mysql_otp: MySQL adapter using mysql-otp
        {adapter, sql_bridge_mysql_otp},

        %% connection parameters (self explanitory, I hope)
        {host, "127.0.0.1"},
        {port, 3306},
        {user, "user"},
        {pass, "userpass"},
        %% all connection parameters can also be determined from
        %% a module or set from environment variables
        %%
        %% e.g. by a module function repo:get_host()
        %% {host, {mod, repo, get_host }}
        %%
        %% or e.g. by an environment variable MY_HOST
        %% {host, {env, "MY_HOST"}}

        %% There are two different ways to determine database
        %%
        %% 1) All requests go to a single database, called 'database_name':
        {lookup, database_name}
        %%
        %% 2) Before a request is made, run the function
        %% `lookup_module:lookup_function()`, for which the return value will
        %% be the database name
        {lookup, {lookup_module, lookup_function}},

        %% Number of connections to establish per pool (which really means
        %% number of connections per database).
        {connections_per_pool, 10},

        %% If a connection pool is saturated, this allows additional "overflow"
        %% connections to be established up to the limit specified below.
        {overflow_connections_per_pool, 10},

        %% If you prefer your string types (varchars, texts, etc) to be
        %% returned as erlang lists rather than binaries, set the following
        %% to true:
        {stringify_binaries, false},

        %% Viable option are '$' (epgSQL Style) asnd '?' (mysql_otp style)
        {replacement_token_style, '$'},

        %% Which module and function will be the fallback key generation
        %% function.
        {key_generator_module_function, {sql_bridge_random_key_generator, generate}}
    ]}
].

The most complicated us the lookup application variable. Lookup can be one of three different kinds of values:

API

API Conventions

Abbreviations

Due to my obsession with brevity, most all function calls have hyper-terse versions which are acronyms of something. Learning those conventions will save you keystrokes, minimize the chance for typos, and shorten your code. The drawback is that it's not entirely obvious on a cursory glance what a function returns (for example: db:fffr is not exactly obvious that it stands for "(F)irst (F)ield of (F)irst (R)ecord").

But you'll learn common shortcuts:

Conveniently, however, There are also simpler, more semantic function names, like list, maps, proplist, etc, which return exactly what the name implies. All is documented below.

Prepared Statements?

SQL_Bridge currently does not offer prepared statements, but will do safe variable replacement using a similar convention, either with MySQL's ? placeholder, or PostgreSQL's $1, $2,...$X placeholder (see replacement_token_style in Configuration)

Replacement Placeholders

Which placeholder is used can be modified by the configuration variable replacement_token_style. This value can be the atoms 'mysql' or 'postgres' or it could also be the shortened version with the atom '?' or '$' respectively.

Sample MySQL Placeholders:

db:q("Select * from login where username=? or email=?", [Loginid, Email])

Sample PostgreSQL Placeholders:

db:q("Select * from login where username=$1 or email=$2", [Loginid, Email])

Key Determination

For functions where raw SQL is not provided as an argument, SQL Bridge will determine the key field by the follwing:

  1. Query the schema to determine the primary key of the table.
  2. If, the primary ke is not defined, or it's a multi-field key, the key field will be called TableName ++ "id".

Insert or Update Determination

There are some helper functions in SQL_Bridge that will attempt to determine if we're updating or inserting a new one (e.g. db:save). The basic rule is this: If the primary key field specified has a value of 0 or undefined (or entirely left off), the call will be an insert operation, relying on either the RDBMS's key generation (auto_increment, etc), or fall back to SQL Bridge's Key Generation functionality.

If the provided key for these functions is anything other than undefined, 0, the operation is treated as an update.

Functions

One more convention before showing each function

Almost all query functions in SQL_Bridge take one or two parameters.

Table Structure for our examples

For our example, we're going to have a table called player:

+----------+-----------------------------------+------+-----+---------+----------------+
| Field    | Type                              | Null | Key | Default | Extra          |
+----------+-----------------------------------+------+-----+---------+----------------+
| playerid | int(10) unsigned                  | NO   | PRI | NULL    | auto_increment |
| name     | varchar(40)                       | YES  |     | NULL    |                |
| race     | enum('dwarf','orc','elf')         | YES  |     | NULL    |                |
| class    | enum('wizard','archer','bruiser') | YES  |     | NULL    |                |
| level    | int(10) unsigned                  | YES  |     | 1       |                |
| alive    | tinyint(1)                        | NO   |     | 1       |                |
+----------+-----------------------------------+------+-----+---------+----------------+

Select Queries

Multi-record Queries
Single-record Queries

Single-record queries correspond directly to their multi-record queries, except they only return a single row. They all start with fr for "first record"

Other convenience queries
Insert, Update, Delete Queries
Insert
Update
Update or Delete from a Proplist, Map, or Record
Backwards Compatibility

For backwards compatibility, the following functions are kept as aliases of the above:

Working with Records

SQL_Bridge can work with records, however, since records are done at compile time, there are some additional steps that must be performed by you in order to accomplish this. The simplest is to use the save_record() functions:

Using a Record Handler

SQL_Bridge also has an option to intelligently convert records into a format SQL_Bridge can work with (namely, proplists and maps). You can do this by use a record_handler configuration option. If the record_handler configuration option is specified in the sql_bridge.config file, it will call that specified function passing the record as an option.

To use this, the value of record_handler must be a 2-tuple of the format {Module, Function}, where Module:Function is a function of arity 1 and returns a proplist or map.

The simplest example would be to make a module in your app like:

-module(my_record_handler).
-export([handle/1]).

handle(Foo = #foo{}) ->
 sql_bridge_utils:record_to_proplist(Foo, record_info(fields, foo));
handle(Bar = #bar{}) ->
 sql_bridge_utils:record_to_proplists(Bar, record_info(fields, bar)).

Then, in your config, set the record_handler value as follows:

[{sql_bridge, [
 ...
 {record_handler, {my_record_handler, handle}}
]}].

Once this is done, you can pass a record as the Data argument in db:save/2-3

Delete

Transactions

SQL_Bridge supports transactions through two mechanisms:

  1. db:start_trans(), db:commit(), and db:rollback() - Manually initiate a transaction. Note, if you run something like db:q("BEGIN"), SQL_Bridge is not intelligent enough to determine that you're in a transaction. Please use db:start_trans().
  2. db:trans(Fun) - Mnesia-style transactions where the contents of the function are run within a transaction. Note that Fun is of arity 0 (that is, no arguments). If the function completes successfully, the queries executed will be commited, and the return value of the Fun() will be the return value of db:trans(Fun). If Fun() crashes, the transaction will be automatically rolled back, and the return value will be {error, Reason}, where Reason is information about the crash (including a stacktrace).

Key Generation (0.7.0+)

If, for some reason, you aren't using your RDBMS's built-in key generation (like MySQL's auto_increment or Postgres' SERIAL), then you can use SQL Bridge's key generation system. ?By default, if the primary key field of a table is not using a built-in key generation, then SQL Bridge can generate a key for you when saving via db:save or its sibling db:insert (it does not apply to db:update - hopefully for obvious reasons).

The default implementation here can be found in sql_bridge_random_key_generator.erl.

Misc Utilities

Some Quirks

There are a number of quirks to get comfortable with when using sql_bridge:

Change Log

See CHANGELOG.md

TODO

About

Copyright (c) 2013-2026 Jesse Gumm

MIT License