HugSql + conman for the Rust world

Overview

Hug SQLx - embrace SQL

HugSQLx is a derive macro turning SQL queries into plain Rust functions. This is an attempt to decouple queries from source code, embrace IDE's ability to propertly format and syntax-highlight SQLs, and based on underlaying LSP - get auto-completing and docstrings for free.

Installation

HugSQLx stands on a shoulders of 2 other crates: async_trait and SQLx:

[dependencies]
async-trait = "0.1.58"
sqlx = { version = "0.6.2", features = ["sqlite"] }
hugsqlx = { version = "0.1.2", features = ["sqlite"] }

Both HugSQLx and SQLx itself should have the same database mentioned in features (sqlite, postgres or mysql).

Deep dive into named queries

The idea here is to distinguish 3 types of queries:

  • typed ones - queries which return a result of concrete type, like <User>. This is what SQLx returns with query_as!.
  • untyped ones - queries which return a "raw" database result wrapped into database-specific type (PgRow, SqliteRow or MysqlRow)
  • mapped ones - queries where result is transformed by a mapper function rather than coerced with type given upfront. This is what SQLx does by calling query(..).map(|f| ...)

Each of these queries (with some exception mentioned below) might return different kind and number of results: one result, many results, optional result or stream of results. In all cases result might be typed or it might be just a DB row. One exception to this classification a low-level "execute" query which is always untyped and returns low-level DB-specific result (PgQueryResult, SqliteQueryResult or MysqlQueryResult).

Query definition

Queries are described by a simple structure:

-- :name fetch_users
-- :doc Returns all the users from DB
SELECT user_id, email, name, picture FROM users

Crucial part here are 2 lines of comments: one with :name identifier, the other one with :doc docstring. Note that name needs to be a valid identifier - it's used to generate a function name after all. Use it wisely, no whitechars, hyphens or any other weird characters if you don't want to be surprised by a panic :)

:doc on the other hand gives more freedom. Place here anything you'd normally add as a function docstring. In case you'd need multiline docstring, go as following:

-- :name set_picture
-- :doc Sets user's picture.
-- Picture is expected to be a valid URL.
UPDATE users
   -- expected URL to the picture
   SET picture = ?
 WHERE user_id = ?

This example also shows that it's perfectly valid to use SQL comments inside the query, as long as comment lines do not start with -- :name or -- :doc, obviously.

Query type definition

Going along with typed / untyped / mapped classification, here is how to add a type hint to query definition:

-- :name untyped_query
-- :name untyped_query :untyped
-- :name typed_query   :typed
-- :name mapped_query  :mapped

Queries are untyped by default. Nothing's needed to instruct HugSqlx to generated ones (though you may still use :untyped hint). The other type however needs a clear hint - either :typed one (aliased by :<>) for typed query, or :mapped (aliased by :||) one for mapped query.

Query result

Again a hint is required to let code generator know what kind of result we expect:

-- :name execute
-- :name one_result        :1
-- :name optional_result   :?
-- :name many_results      :*
-- :name stream_of_results :^

Analogically to query types, execute query is default one. No need for hint here. The other kind of result requires hinting - :1 when query is expected to return exactly one result, :? if optional result is expected, :* for many results (vector) and :^ for a stream of results.

Both query- and result types can be mixed:

-- :name delete_user
-- :name fetch_user     :<> :?
-- :name fetch_users    :<> :*
-- :name fetch_profile  :mapped :1

Back to code

When using Hugsqlx, you need to decide first what database (postgres, sqlite or mysql) the code should be generated for:

hugsqlx = {version = "0.1.2", features = ["sqlite"]}

Having dependency added, time to add a struct:

use hugsqlx::{params, HugSqlx};

#[derive(HugSqlx)]
#[queries = "resources/db/queries/users.sql"]
struct Users {}

queries attribute needs to be a project (Cargo.toml) relative path and may point to either a single file (query definitions will be taken from this file only) or a directory. The later forces macro to traverse a path and generate corresponding functions upon found files.

Example:

Assuming following query in "resources/db/queries/users.sql":

-- :name fetch_users :mapped :*
-- :doc Returns all the users from DB
SELECT user_id, email, name, picture FROM users WHERE role=?

HugSqlx generates a trait function fetch_users, which might be shaped differently depending on provided query hints. Independently of hints however, all the generated queries require at least 2 arguments - an Executor (Pool, PoolConnection or Connection) and query parameters. Mapped query, as expected, requires one more parameter - a mapper function transforming DB row into a data of concrete type. Let's call the generated function for above query:

let users = Users::fetch_users(&pool, params!("guest"), |row| { ... }).await?;

Parameters need to be passed with =params!= macro due to Rust mechanism which forbids creating a vector of elements of different types.

Tips & tricks (with Emacs)

How to get better syntax highlighting on comments with :name and :doc?

(font-lock-add-keywords
 'sql-mode `(("\".+?\"" 0 'font-lock-string-face t)
             (":[a-zA-Z0-9+-><?!\\*\\|]?+" 0 'font-lock-constant-face t)
             (":name \\([[:graph:]]+\\)" 1 'font-lock-function-name-face t)))

How to get get ctags working with named queries?

--kinddef-sql=q,query,Queries
--regex-sql=/\-\-[ \t]+(:name[\ \t]+)([[:alnum:]_-]+)/\2/q/

Limitations

Query definition both with :name and :doc expects to have :name comment first. HugSqlx does not complain otherwise, but result might be surprising.

No subfolders are recursively traversed to read query definitions.

Also, because of SQLx limitation, no named parameters have been implemented yet.

You might also like...
Rust library to create a Good Game Easily

ggez What is this? ggez is a Rust library to create a Good Game Easily. The current version is 0.6.0-rc0. This is a RELEASE CANDIDATE version, which m

RTS game/engine in Rust and WebGPU
RTS game/engine in Rust and WebGPU

What is this? A real time strategy game/engine written with Rust and WebGPU. Eventually it will be able to run in a web browser thanks to WebGPU. This

unrust - A pure rust based (webgl 2.0 / native) game engine

unrust A pure rust based (webgl 2.0 / native) game engine Current Version : 0.1.1 This project is under heavily development, all api are very unstable

Rust bindings for GDNative

GDNative bindings for Rust Rust bindings to the Godot game engine. Website | User Guide | API Documentation Stability The bindings cover most of the e

SDL bindings for Rust

Rust-SDL Bindings for SDL in Rust Overview Rust-SDL is a library for talking to SDL from Rust. Low-level C components are wrapped in Rust code to make

SDL2 bindings for Rust

Rust-SDL2 Bindings for SDL2 in Rust Changelog for 0.34.2 Overview Rust-SDL2 is a library for talking to the new SDL2.0 libraries from Rust. Low-level

SFML bindings for Rust

rust-sfml Rust bindings for SFML, the Simple and Fast Multimedia Library. Requirements Linux, Windows, or OS X Rust 1.42 or later SFML 2.5 CSFML 2.5 D

Rust bindings for libtcod 1.6.3 (the Doryen library/roguelike toolkit)

Warning: Not Maintained This project is no longer actively developed or maintained. Please accept our apologies. Open pull requests may still get merg

Victorem - easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust.

Victorem Easy UDP game server and client framework for creating simple 2D and 3D online game prototype in Rust. Example Cargo.toml [dependencies] vict

Comments
  • Mention dependence on async-trait

    Mention dependence on async-trait

    Without async-trait as a dependency of my project it was failing to compile with the below message. I saw the example depended on it even though it was never explicitly included so I decided to try adding it, and it fixed the compiler error.

    Possibly hugsqlx should provide it, or make it explicit the user must bring it into scope in files that use the HugSqlx trait :)

    error[E0706]: functions in traits cannot be declared `async`
     --> src/fun.rs:3:10
      |
    3 | #[derive(HugSqlx)]
      |          ^^^^^^^ `async` because of this
      |
      = note: `async` trait functions are not currently supported
      = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
      = note: this error originates in the derive macro `HugSqlx` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    error[E0433]: failed to resolve: use of undeclared crate or module `async_trait`
     --> src/fun.rs:3:10
      |
    3 | #[derive(HugSqlx)]
      |          ^^^^^^^ use of undeclared crate or module `async_trait`
      |
      = note: this error originates in the derive macro `HugSqlx` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    opened by tbillington 1
  • Mention about required dependencies in documentation

    Mention about required dependencies in documentation

    Resolves #1

    Mention SQLx and async-trait as dependencies of HugSQLx. I'm not sure yet what the general strategy should be (including dependencies by crate itself or delegating this to consumer), so let start with proper documentation first.

    opened by mbuczko 0
Owner
Michał Buczko
Polyglot programmer notoriously learning, obsessively reading. Passioned clojurian by day, rustacean when the night comes.
Michał Buczko
Using the powers of Rust, Go and Dragonfly to make a vanilla-like world generation.

df-rs-gen — Dragonfly Rust Generator Using the powers of Rust, Go and Dragonfly to make a vanilla-like world generation. How to use Clone the repo. gi

Sculas 1 Apr 13, 2022
A block world game engine written in Rust.

About This is the Leafwing Studios' template repo, providing a quick, opinionated base for high-quality Bevy game projects (and libraries). We've shav

null 0 Feb 3, 2022
Dark Forest, the world's first decentralized real-time strategy game.

darkforest-rs Dark Forest, the world's first decentralized real-time strategy game.

null 44 Oct 9, 2022
Rust-raytracer - 🔭 A simple ray tracer in Rust 🦀

rust-raytracer An implementation of a very simple raytracer based on Ray Tracing in One Weekend by Peter Shirley in Rust. I used this project to learn

David Singleton 159 Nov 28, 2022
Rust-and-opengl-lessons - Collection of example code for learning OpenGL in Rust

rust-and-opengl-lessons Project requires Rust 1.31 Collection of example code for learning OpenGL in Rust 00 - Setup 01 - Window 02 - OpenGL Context 0

Nerijus Arlauskas 348 Dec 11, 2022
Simple retro game made using Rust bracket-lib by following "Herbert Wolverson's Hands on Rust" book.

Flappy Dragon Code from This program is a result of a tutorial i followed from Herbert Wolverson's Hands-on Rust Effective Learning through 2D Game De

Praneeth Chinthaka Ranasinghe 1 Feb 7, 2022
A rust chess implementation using a neural network scoring function built on huggingface/candle + rust + wasm

Rusty Chess What is it? Rusty Chess aims to be a high quality embeddable chess engine that runs entirely locally in the browser (no backend required).

Gareth 3 Nov 3, 2023
A Rust wrapper and bindings of Allegro 5 game programming library

RustAllegro A thin Rust wrapper of Allegro 5. Game loop example extern crate allegro; extern crate allegro_font; use allegro::*; use allegro_font::*;

null 80 Dec 31, 2022
High performance Rust ECS library

Legion aims to be a feature rich high performance Entity component system (ECS) library for Rust game projects with minimal boilerplate. Getting Start

Amethyst Engine 1.4k Jan 5, 2023
A refreshingly simple data-driven game engine built in Rust

What is Bevy? Bevy is a refreshingly simple data-driven game engine built in Rust. It is free and open-source forever! WARNING Bevy is still in the ve

Bevy Engine 21.1k Jan 4, 2023