fortunes.nika

// The TechEmpower "fortunes" benchmark: read rows from a database, add one,
// sort them and render them as an HTML table.
//
// This does not compile yet: it needs database access, which is not built yet,
// and the `http` package has no `Server` with routes.
//
// Shows: an SQL query and an HTML template embedded in the same handler.

use http
use std::db::postgres
use std::html

// ---------------------------------------------------------------------------
// 1. Data
// ---------------------------------------------------------------------------

pub struct Fortune {
    id: i32,
    message: String,
}

// ---------------------------------------------------------------------------
// 2. The template, as a DSL
// ---------------------------------------------------------------------------

// The template binds immediately: `:rows` is taken from this function's scope
// where the template is written, unlike a SQL placeholder.
//
// Every `{…}` hole is escaped, and nothing at the hole turns that off. The only
// way to insert markup as it is is a value of type `html::Raw`. A hole where
// the template cannot escape, such as inside `<script>` or in a URL, is a
// compile error.
fn render(rows: Vec[Fortune]) -> String {
    return dsl html {
        <!DOCTYPE html>
        <html>
        <head><title>Fortunes</title></head>
        <body>
          <table>
            <tr><th>id</th><th>message</th></tr>
            <for row in :rows>
              <tr><td>{row.id}</td><td>{row.message}</td></tr>
            </for>
          </table>
        </body>
        </html>
    } eod
}

// ---------------------------------------------------------------------------
// 3. The handler
// ---------------------------------------------------------------------------

fn fortunes(db: Shared[postgres::Connection]) -> String throws {

    // The query binds late: its parameters are filled when it runs, although
    // this one has none. It is parsed at compile time, and its columns are
    // checked against the schema. As a plain string, both checks would wait
    // for the first request in production.
    let all = dsl postgres::Sql {
        SELECT id, message FROM fortune
    } eod

    // A network round-trip, written as a plain call: no `async` on the
    // signature and no `await` here. While this request waits, the event loop
    // serves other connections, which is what the benchmark measures.
    //
    // Nothing marks the failure either. `fetch` can fail, and because this
    // function is `throws`, the error goes to the caller.
    let mut rows = all.fetch(db)

    // The benchmark requires one extra row added in memory, not in the query.
    rows.push(Fortune { id: 0, message: "Additional fortune added at request time." })

    // `sort_by` runs the comparison right away, so it can borrow `rows`
    // instead of taking it. No copy is needed.
    rows.sort_by fn(left, right) { left.message.cmp(right.message) }

    return render(rows)
}

fn main() throws {
    // One database connection, shared by every request.
    let db = postgres::connect(env::var("DATABASE_URL") ?? "postgres://localhost/hello_world")

    // A handler that does not mention the request takes no argument. This one
    // does not need it.
    http::Server::new()
        .route("/fortunes") fn { fortunes(db) }
        .listen(":8080")
}

The file itself · all the examples

This site is open source. Improve this page.