..

PostgreSQL multidimensional arrays as parameters in Rust

I recently wanted to use a multidimensional array as a parameter to a query using rust-postgres and rust-postgres-array.

The “documentation” is lacking, so I went trawling Stack Overflow for an example of how the API’s supposed to work and didn’t find much––so I thought I’d throw this up in the hopes that I can help someone save a bit of time.

Without further ado, here’s the Cargo.toml

[package]
name = "scratch"
version = "0.1.0"
edition = "2021"

[dependencies]
postgres_array = "0.11.1"
postgres = "0.19"

Here’s the code for src/main.rs:

use postgres::{Client, NoTls};
use postgres_array::{Array, Dimension};

fn main() {
    let params = "host=localhost user=pg dbname=pg";
    let mut client = Client::connect(params, NoTls).unwrap();

    // Imagine you have a set of schema-qualified tables you want to check that the connected user
    // has `SELECT` privileges for.
    let tables = vec![["other", "t"], ["public", "t"]];

    let tables_len = tables.len();

    // `Array::from_parts` expects flattened structures, where the `dimensions` arguments describes
    // how to re-impose the dimensions on the values.
    let tables = Array::from_parts(
        tables.into_iter().map(|i| i.to_vec()).flatten().collect(),
        vec![
            // Outer dimension is the number of elements in our outer vector.
            Dimension {
                len: i32::try_from(tables_len).expect("fewer than i32::MAX tables"),
                // n.b. PG's indexing is 1-based, as is the argument in this struct.
                lower_bound: 1,
            },
            // Inner dimension is always 2. PG arrays should always be "rectilinear", i.e. they
            // cannot have interior "jagged" edges on any dimension; all lengths in each dimension
            // must be equal.
            Dimension {
                len: 2,
                // n.b. PG's indexing is 1-based, as is the argument in this struct.
                lower_bound: 1,
            },
        ],
    );

    let privileges_per_table = client
        .query(
            "
            WITH
                data AS (SELECT $1::text[] AS arr)
            SELECT
                t, has_table_privilege('pg', t, 'select') AS p
            FROM
                (
                    SELECT
                        -- This is by far the simplest way of returning
                        -- schema-qualified table names.
                        format('%I.%I', arr[i][1], arr[i][2]) AS t
                    FROM
                        data, ROWS FROM (generate_subscripts((SELECT arr FROM data), 1)) AS i
                )
                    AS o (t);",
            &[&tables],
        )
        .unwrap()
        .into_iter()
        .map(|row| (row.get("t"), row.get("p")))
        .collect::<Vec<(String, bool)>>();

    println!("Table privileges {:?}", privileges_per_table);
}