{"id":15470807,"url":"https://github.com/choptastic/sql_bridge","last_synced_at":"2025-10-26T09:31:56.110Z","repository":{"id":10671862,"uuid":"12907660","full_name":"choptastic/sql_bridge","owner":"choptastic","description":"SQL_Bridge: An abstraction layer for Erlang SQL databases (MySQL and PostgreSQL)","archived":false,"fork":false,"pushed_at":"2025-03-06T14:27:57.000Z","size":2085,"stargazers_count":22,"open_issues_count":4,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-08-24T16:42:10.954Z","etag":null,"topics":["erlang","mysql","postgresql"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/choptastic.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2013-09-17T21:47:32.000Z","updated_at":"2025-03-06T14:28:01.000Z","dependencies_parsed_at":"2025-04-22T12:12:14.602Z","dependency_job_id":"5cb41978-3fff-452c-ae60-891ed059e4f6","html_url":"https://github.com/choptastic/sql_bridge","commit_stats":{"total_commits":154,"total_committers":3,"mean_commits":"51.333333333333336","dds":0.2857142857142857,"last_synced_commit":"aa3859d585b7bde19c1c05a56d2bf0b6377474a7"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/choptastic/sql_bridge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choptastic%2Fsql_bridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choptastic%2Fsql_bridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choptastic%2Fsql_bridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choptastic%2Fsql_bridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/choptastic","download_url":"https://codeload.github.com/choptastic/sql_bridge/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choptastic%2Fsql_bridge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275033886,"owners_count":25393943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-13T02:00:10.085Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","mysql","postgresql"],"created_at":"2024-10-02T02:06:58.068Z","updated_at":"2025-10-26T09:31:56.022Z","avatar_url":"https://github.com/choptastic.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SQL_Bridge\n\n[![SQL Bridge Tests \u0026 Dialyzer](https://github.com/choptastic/sql_bridge/actions/workflows/tests-workflow.yml/badge.svg)](https://github.com/choptastic/sql_bridge/actions/workflows/tests-workflow.yml)\n\nAn Erlang SQL Abstraction layer for interfacing with SQL databases.\n\n### Supported Databases:\n\n\t* MySQL\n\t* PostgreSQL\n\n#### Planned for support\n\n\t* Microsoft SQL Server\n\t* SQLite\n\n## What it does\n\nIt provides a simple layer on top of existing SQL drivers, abstracting away the\npool management, and providing sensible default behaviors.  Further, it\nprovides a whole slew of methods for returning and updating data: lists,\ntuples, proplists, dicts, existence checks, single fields returning and\nsetting, updating data from proplists, and more.\n\n## Conventions\n\n   * A process tracks which database it's connecting to, grabbing any of the\n     available database pools. No need to identify a specific pool with\n     requests.\n   * Connections are made automatically when the first query is attempted\n   * A process determines which database to connect to via the `sql_bridge`\n     application variable `lookup`.\n   * This is used commonly to simplify the process of running a single codebase\n     which connects to different database for different users or for different\n     host headers.\n\n## Configuration\n\n### `module_alias`\n\nProbably the most unusual configuration to discuss is the `module_alias`.\n\nThis configuration option allows us to compile a module which exports all the\nfunctions of `sql_bridge`, but allows us to use a different module name.\n\nIn my own apps, I tend to use a module called `db` which serves as the alias to\n`sql_bridge`.  While it's easy enough to make your own module and do an\n`import`, this configuration parameter allows us to skip that step.\n\nThis alias module is generated and loaded during the `sql_bridge:start()`\nfunction.\n\n### `lookup`\n\nThe most important configuration variable is the `lookup` variable. This tells\nSQL_Bridge which database to use at any given time.\n\nIt can take two possible values:\n\n   * An atom of the database name. For single-database apps, this is the simple\n     solution: Whatever value you assign to `lookup` will be the database\n     SQL_Bridge uses.\n\n   * A {Module, Function} or {Module, Function, Args} tuple. This is for\n     multi-database apps.  The return value of Module:Function() or\n    `erlang:apply(Module, Function, Args)` will be ysed to determine which database\n    to connect to. This value will then be cached within the process dictionary so\n    that that (potentially expensive) function isn't repeatedly called within the\n    same process.\n\n### `adapter`\n\nAs 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)\n\n  + `sql_bridge_epgsql` - [epgsql](http://github.com/epgsql/epgsql) - The\n    Erlang PostgreSQL driver (also uses poolboy).\n  + `sql_bridge_mysql_otp` - [mysql-otp](http://github.com/mysql-otp/mysql-otp) -\n     A New MySQL driver (also uses poolboy).\n\n### Sample Config\n\nThere is a sample config file in\n[sample.config](https://github.com/choptastic/sigma_sql/blob/master/sample.config),\nbut here are the configuration settings currently available:\n\n```erlang\n[\n    {sql_bridge, [\n        %% module_alias creates a module with the specified name, which can be\n        %% used as an alias to the sigma_sql module.\n        {module_alias, db},\n\n        %% There are three adapters that can be used:\n        %%  + sql_bridge_epgsql: PostgreSQL adapter using epgsql\n        %%  + sql_bridge_mysql_otp: MySQL adapter using mysql-otp\n        {adapter, sql_bridge_mysql_otp},\n\n        %% connection parameters (self explanitory, I hope)\n        {host, \"127.0.0.1\"},\n        {port, 3306},\n        {user, \"user\"},\n        {pass, \"userpass\"},\n        %% all connection parameters can also be determined from \n        %% a module or set from environment variables\n        %%\n        %% e.g. by a module function repo:get_host()\n        %% {host, {mod, repo, get_host }}\n        %%\n        %% or e.g. by an environment variable MY_HOST \n        %% {host, {env, \"MY_HOST\"}}\n\n        %% There are two different ways to determine database\n        %%\n        %% 1) All requests go to a single database, called 'database_name':\n        {lookup, database_name}\n        %%\n        %% 2) Before a request is made, run the function\n        %% `lookup_module:lookup_function()`, for which the return value will\n        %% be the database name\n        {lookup, {lookup_module, lookup_function}},\n\n        %% Number of connections to establish per pool (which really means\n        %% number of connections per database).\n        {connections_per_pool, 10},\n\n        %% If a connection pool is saturated, this allows additional \"overflow\"\n        %% connections to be established up to the limit specified below.\n        {overflow_connections_per_pool, 10},\n\n        %% If you prefer your string types (varchars, texts, etc) to be returned as erlang lists rather than binaries, set the following to true:\n        {stringify_binaries, false}\n    ]}\n].\n```\n\nThe most complicated us the `lookup` application variable. Lookup can be one of\nthree different kinds of values:\n\n  * Atom: That's the database every request will use.\n  * {Module, Function}: Call Module:Function() to determine which database to\n    connect to.\n\n## API\n\n### Conventions\n\n#### Abbreviations\n\nDue to my obsession with brevity, most all function calls have hyper-terse\nversions which are acronyms of something.  Learning those conventions will save\nyou keystrokes, minimize the chance for typos, and shorten your code.  The\ndrawback is that it's not entirely obvious on a cursory glance what a function\nreturns (for example: `db:fffr` is not exactly obvious that it stands for\n\"(F)irst (F)ield of (F)irst (R)ecord\").\n\nBut you'll learn common shortcuts:\n\n  * `q` -\u003e query\n  * `t` -\u003e tuple\n  * `d` -\u003e dict\n  * `l` -\u003e list\n  * `pl` -\u003e proplist\n  * `ff` -\u003e first field\n  * `fr` -\u003e first record\n  * `i` -\u003e insert\n  * `u` -\u003e update\n\n\n**Conveniently, however,** There are also simpler, more semantic function\nnames, like `list`, `maps`, `proplist`, etc, which return exactly what the name\nimplies. All is documented below.\n\n#### Prepared Statements?\n\nSQL_Bridge currently does not offer prepared statements, but will do safe\nvariable replacement using a similar convention, either with MySQL's `?`\nplaceholder, or PostgreSQL's `$1, $2,...$X` placeholder.\n\n##### Replacement Placeholders\n\nWhich placeholder is used can be modified by the configuration variable\n`replacement_token_style`.  This value can be the atoms 'mysql' or 'postgres'\nor it could also be the shortened version with the atom '?' or '$'\nrespectively.\n\nSample MySQL Placeholders:\n\n```erlang\ndb:q(\"Select * from login where username=? or email=?\", [Loginid, Email])\n```\n\nSample PostgreSQL Placeholders:\n\n```erlang\ndb:q(\"Select * from login where username=$1 or email=$2\", [Loginid, Email])\n```\n\n#### Singular Table Names\n\nI know it's common for database designers to name their tables with the plural\nform of a noun to indicate that it's a collection of things (e.g. \"logins\"\ninstead of \"login\"), while still using the singular as the name of key fields\n(so a table of logins would be called \"logins\", but the key would be\n\"loginid\").\n\nWell, despite english being my native language, I find it to be a terribly,\nhorribly inconsistent language, and refuse to try to make code figure out if\nthe plural of \"child\" is \"children\" or \"childs\". As such, SQL_Bridge makes a lot\nof assumptions that your codebase will use a singular table name, and that the\nkey of that table is named `Tablename ++ \"id\"`.\n\nUsing the example above, my table of logins would be called \"login\" and the\nprimary key is \"loginid\".\n\n#### Insert or Update Determination\n\nThere are some helper functions in SQL_Bridge that will attempt to determine if\nwe're updating or inserting a new one.  The basic rule is this: If the key\nfield specified has a value of `0` or `undefined`, it will be an insert,\nassuming the database will do the auto increment for us. If it's anything else,\nit's an update.\n\n### Functions\n\n#### One more convention before showing each function:\n\nAlmost all query functions in SQL_Bridge take one or two parameters.\n\n  * 1 Argument: the query will be executed as-is. (e.g. `db:q(\"select * from\n    whatever\")`)\n  * 2 Arguments: Argument two should be a list of arguments that correspond to\n    and will replace question marks (`?`) within the query itself in order.\n    (e.g. `db:q(\"select * from whatever where field1=? or field1=?\", [SomeValue,\n    SomeOtherValue])`)\n\n#### Table Structure for our examples\n\nFor our example, we're going to have a table called `player`:\n\n```\n+----------+-----------------------------------+------+-----+---------+----------------+\n| Field    | Type                              | Null | Key | Default | Extra          |\n+----------+-----------------------------------+------+-----+---------+----------------+\n| playerid | int(10) unsigned                  | NO   | PRI | NULL    | auto_increment |\n| name     | varchar(40)                       | YES  |     | NULL    |                |\n| race     | enum('dwarf','orc','elf')         | YES  |     | NULL    |                |\n| class    | enum('wizard','archer','bruiser') | YES  |     | NULL    |                |\n| level    | int(10) unsigned                  | YES  |     | 1       |                |\n| alive    | tinyint(1)                        | NO   |     | 1       |                |\n+----------+-----------------------------------+------+-----+---------+----------------+\n```\n\n#### Select Queries\n\n##### Multi-record Queries\n\n  * `db:lists` or `db:q`: The most basic query. Will return a list of rows formatted as\n    simple lists.\n\n    ```erlang\n    \u003e db:q(\"select playerid, name from player where race=?\", [\"elf\"]).\n    [[2,\"Evan\"],\n     [3,\"Marc\"]]\n    ```\n\n  * `db:tuples` or `db:tq`: Like `db:q` except returns a list of rows formatted as tuples.\n    ```erlang\n\n    \u003e db:tq(\"select playerid, name from player where race=?\", [\"elf\"]).\n    [{2,\"Evan\"},\n     {3,\"Marc\"}]\n    ```\n\n  * `db:proplists` or `db:plq`: Like `db:q` except returns a list of proplists, with the keys of\n    which are atomized versions of the database field names:\n\n    ```erlang\n    \u003e db:plq(\"select name, race, level from player where alive=?\",[false]).\n    [[{name,\"Rusty\"},\n      {race,\"dwarf\"},\n      {level,35}],\n     [{name,\"Justin\"},\n      {race,\"orc\"},\n      {level,15}]]\n    ```\n\n  * `db:dicts` or `db:dq`: Like `db:plq`, except returns a list of Erlang `dicts` with the\n    keys again being atomized versions of the field names.\n\n  * `db:maps` or `db:mq`: Like `db:plq`, except returns a list of Erlang `maps`, with keys\n    atomized versions of field names.\n\n##### Single-record Queries\n\nSingle-record queries correspond directly to their multi-record queries, except\nthey only return a single row. They all start with `fr` for \"first record\"\n\n  * `db:list` or `db:fr`: Like `db:q` (Returns a list)\n  * `db:tuple` or `db:tfr`: Like `db:tq` (Returns a tuple)\n  * `db:proplist` or `db:plfr`: Like `db:plq` (Returns a proplist)\n  * `db:dict` or `db:dfr`: Like `db:dq` (Returns a dict)\n  * `db:map` or `db:mfr`: List `db:mq` (Returns a map)\n\n##### Other convenience queries\n\n  * `db:fffr`: (F)irst (F)ield of (F)irst (R)ecord. Returns the first field of\n    the first record returned.\n\n    ```erlang\n    \u003e db:fffr(\"select count(*) from player where class=?\",[wizard]).\n    2\n    ```\n\n  * `db:ffl`: (F)irst (F)ield (L)ist. Returns a list of the first field from\n    each row.\n\n    ```erlang\n    \u003e db:ffl(\"select playerid from player where alive=? or class=?\",[true,wizard]).\n    [1,2,3,5]\n    ```\n\n  * `db:qexists`: Returns `true` or `false` depending on whether or not the\n    query returns any records.\n\n    ```erlang\n    \u003e db:qexists(\"select playerid from player where playerid=?\",[999]).\n    false\n    ```\n\n  * `db:exists(Table, IDField, IDValue)`: Returns true or false depending on\n    whether or not a record in `Table` exists where the specified `IDField` has\n\tthe value `IDValue`.\n\n  * `db:exists(Table, IDValue)`: Shortcut for `db:exists(Table, Table ++ \"id\",\n    IDValue)`\n\n  * `db:field(Table, Field, IDValue)`: Returns the value of the field `Field`\n    from table `Table`, where the TableID value is `IDValue`.\n\n    ```erlang\n    \u003e db:field(player, race, 1).\n    \"dwarf\"\n    ```\n\tThe above is the equivilant to `db:fffr(\"select race from player where\n\tplayerid=1\")`\n\n  * `db:field(Table, Field, IDField, IDValue)`\n\n\tLike `db:field/3`, except you get to specify which field you're querying\n\tfor instead of assuming `Table ++ \"id\"` as the ID field.\n\n  * `db:fields(Table)`: Returns a list of the names of the fields of the named\n    `Table`\n\n    ```erlang\n    \u003e db:fields(player).\n    [playerid, name, race, class, level, alive]\n    ```\n\n  * `db:field_exists(Table, Field)` : Returns true if the specified `Table`\n    contains the field called `Field`\n\n#### Insert, Update, Delete Queries\n\n### Insert\n\n  * `db:qi` or `db:qinsert` Runs the specified query and returns the `insert_id`\n\n### Update\n\n  * `db:qu` or `db:qupdate`: Run the specified query and returns the number of affected rows.\n\n### Update or Delete from a Proplist, Map, or Record\n\n  * `db:save(Table, Keyfield, Data)`: Run an update or insert query on the\n    Table provided with the specified Data as the row data. `Data` can be\n\teither a proplist, a map, or a record (See *Workering with Records* below).\n    If the value in `Data` associated with the Key `Keyfield` is a\n    zero, or is undefined, then an insert query is performed. Otherwise, an update\n    query is performed.  Regardless of insert or update method, the return value is\n    the value of the `Keyfield` - if insert, then it returns the new `insert_id`,\n\tand if update, the value associated with the `Keyfield` from `Data`.\n\n  * `db:save(Table, Data)`: Like `db:save(Table, Keyfield, Data)` except\n    `Keyfield` is deduced with `list_to_atom(atom_to_list(Table) ++ \"id\")`\n\n  * `db:update(Table, KeyField, Data)`: Like `save/3` but will always use an\n    `update` even if the value for the keyfield is `0` or `undefined`.\n\n  * `db:update(Table, Data)`: Like `save/2`, but uses an `update` operation.\n\n  * `db:insert(Table, Data)`: Like `save/2` but uses an `insert` operation.\n\t**Special Note::** This does not strip out any Keyfields, it inserts the data\n\texactly as it is, so if the key field has a value of `0`, that's the value that\n\twill be inserted.\n\n#### Working with Records\n\nSQL_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:\n\n  * `db:save_record(Table, KeyField, Record, FieldList)`: In order to call this effectively, you must pass the return value of the built-in compile function `record_info(fields, RECORDNAME)` as the argument for `FieldList`. For example, if you have a record called `#foo` that is saved into the table `foo_tbl` you could save it like this:  `db:save_record(foo_tbl, fooid, FooRec, record_info(fields, foo))`\n\n  * `db:save_record(Table, Record, FieldList`: Like `db:save/2`, this will automatically determine the KeyField as `list_to_atom(atom_to_list(Table) ++ \"id\")`.\n\n#### Using a Record Handler\n\nSQL_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.\n\nTo 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.\n\nThe simplest example would be to make a module in your app like:\n\n```erlang\n-module(my_record_handler).\n-export([handle/1]).\n\nhandle(Foo = #foo{}) -\u003e\n\tsql_bridge_utils:record_to_proplist(Foo, record_info(fields, foo));\nhandle(Bar = #bar{}) -\u003e\n\tsql_bridge_utils:record_to_proplists(Bar, record_info(fields, bar)).\n```\n\nThen, in your config, set the `record_handler` value as follows:\n\n```erlang\n[{sql_bridge, [\n\t...\n\t{record_handler, {my_record_handler, handle}}\n]}].\n```\n\nOnce this is done, you can pass a record as the `Data` argument in `db:save/2-3`\n\n### Delete\n\n  * `db:delete(Table, ID)`: Delete records from a table.\n\n  * `db:delete(Table, Field, ID)`: Delete records from a table.\n\n## Transactions\n\nSQL_Bridge supports transactions through two mechanisms:\n\n  1. `db:start_trans()`, `db:commit()`, and `db:rollback()` - Manually initiate\n\t a transaction. Note, if you run something like `db:q(\"BEGIN\")`, SQL_Bridge\n    is not intelligent enough to determine that you're in a transaction. Please use\n    `db:start_trans()`.\n  2. `db:trans(Fun)` - Mnesia-style transactions where the contents of the\n\t function are run within a transaction.  Note that `Fun` is of arity 0\n    (that is, no arguments). If the function completes successfully, the queries\n    executed will be commited, and the return value of the `Fun()` will be the\n    return value of `db:trans(Fun)`.  If `Fun()` crashes, the transaction will be\n    automatically rolled back, and the return value will be `{error, Reason}`,\n    where `Reason` is information about the crash (including a stacktrace).\n\n## Misc Utilities\n\n  * `db:encode(Term)`: Safely escapes a data for database interaction on in a\n    SQL query, for the backend of your choice.\n \n  * `db:encode_list(List)`: Takes a list of terms and safely encodes them for\n    mysql interaction, separating them with commas. \n\n  * `db:encode64(Term)`\n\n  * `db:decode64(Term)`: Encodes and decodes any Erlang term to base64.\n\n  * `db:limit_clause(PerPage, Page)`: When you're doing a query that needs\n    pagination, sometimes you just don't want to deal with figuring out the\n    limit clause's offset and length.  In this case, you can build a simple limit\n    clause for MySQL by passing this function which page you want to show (start at\n    page 1), and how many items you per page you want to show. It will do the\n    offset calculation for you and return a limit clause that can be inserted into\n    the query.\n\n## Some Quirks\n\nThere are a number of quirks to get comfortable with when using sql_bridge:\n\n  * Dates, times, and timestamps are returned as a string (or binary), like\n\t\"2016-12-31\", \"23:15:46\", or \"2016-12-31 23:15:46\". This is largely for\n    backwards compatibility with the original implementation which returned\n    returned dates and times as strings, and since so much of my code depends\n    on this, it's just how it is.\n  * Numeric and Decimal types are returned as floats or integers. Again, this\n    is because of some code I have that depends on it.\n   \n## Change Log\n\nSee [CHANGELOG.md](https://github.com/choptastic/sql_bridge/blob/master/CHANGELOG.md)\n\n## TODO\n\n  * Maybe Experiment with [record-based querys](https://github.com/choptastic/sql_bridge/issues/1)\n  * Add SQLLite Support\n\n## About\n\nCopyright (c) 2013-2025 [Jesse Gumm](http://jessegumm.com)\n([@gumm.io](https://bsky.app/profile/gumm.io))\n\n[MIT License](https://github.com/choptastic/sql_bridge/blob/master/LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchoptastic%2Fsql_bridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchoptastic%2Fsql_bridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchoptastic%2Fsql_bridge/lists"}