cartel-pg 0.1.0

Async PostgreSQL driver for the dope runtime
use crate::wire::{Fe, Sink};

fn reserve_len<S: Sink>(out: &mut S) -> usize {
    let pos = out.len();
    out.extend_from_slice(&[0u8; 4]);
    pos
}

fn finish_len<S: Sink>(out: &mut S, pos: usize) {
    let len = out.len();
    if pos + 4 > len {
        return;
    }
    let total = (len - pos) as u32;
    let bytes = total.to_be_bytes();
    out.as_mut_slice()[pos..pos + 4].copy_from_slice(&bytes);
}

fn write_cstr<S: Sink>(out: &mut S, s: &str) {
    out.extend_from_slice(s.as_bytes());
    out.push(0);
}

pub(super) fn startup<S: Sink>(
    out: &mut S,
    user: &str,
    database: &str,
    application_name: &str,
    options: &str,
) {
    let pos = reserve_len(out);
    out.extend_from_slice(&Fe::STARTUP_PROTOCOL.to_be_bytes());

    write_cstr(out, "user");
    write_cstr(out, user);
    write_cstr(out, "database");
    write_cstr(out, database);
    write_cstr(out, "application_name");
    write_cstr(out, application_name);
    write_cstr(out, "client_encoding");
    write_cstr(out, "UTF8");
    if !options.is_empty() {
        write_cstr(out, "options");
        write_cstr(out, options);
    }
    out.push(0);

    finish_len(out, pos);
}

pub(super) fn sync<S: Sink>(out: &mut S) {
    out.push(Fe::SYNC);
    out.extend_from_slice(&4u32.to_be_bytes());
}

pub(super) fn copy_data<S: Sink>(out: &mut S, data: &[u8]) {
    out.push(Fe::COPY_DATA);
    let total_len = (4 + data.len()) as u32;
    out.extend_from_slice(&total_len.to_be_bytes());
    out.extend_from_slice(data);
}

pub(super) fn copy_done<S: Sink>(out: &mut S) {
    out.push(Fe::COPY_DONE);
    out.extend_from_slice(&4u32.to_be_bytes());
}

pub(super) fn sasl_initial_response<S: Sink>(out: &mut S, mechanism: &str, initial: &[u8]) {
    out.push(Fe::PASSWORD);
    let pos = reserve_len(out);
    write_cstr(out, mechanism);
    out.extend_from_slice(&(initial.len() as i32).to_be_bytes());
    out.extend_from_slice(initial);
    finish_len(out, pos);
}

pub(super) fn sasl_response<S: Sink>(out: &mut S, msg: &[u8]) {
    out.push(Fe::PASSWORD);
    let pos = reserve_len(out);
    out.extend_from_slice(msg);
    finish_len(out, pos);
}

pub(super) fn parse<S: Sink>(out: &mut S, stmt_name: &str, sql: &str, param_oids: &[u32]) {
    out.push(Fe::PARSE);
    let pos = reserve_len(out);
    write_cstr(out, stmt_name);
    write_cstr(out, sql);
    out.extend_from_slice(&(param_oids.len() as u16).to_be_bytes());
    for oid in param_oids {
        out.extend_from_slice(&oid.to_be_bytes());
    }
    finish_len(out, pos);
}

pub(super) fn bind_header<S: Sink>(
    out: &mut S,
    portal: &str,
    stmt_name: &str,
    param_formats: &[u16],
    n_params: u16,
) -> usize {
    out.push(Fe::BIND);
    let pos = reserve_len(out);
    write_cstr(out, portal);
    write_cstr(out, stmt_name);

    out.extend_from_slice(&(param_formats.len() as u16).to_be_bytes());
    for f in param_formats {
        out.extend_from_slice(&f.to_be_bytes());
    }

    out.extend_from_slice(&n_params.to_be_bytes());
    pos
}

pub(super) fn bind_trailer<S: Sink>(out: &mut S, pos: usize, result_formats: &[u16]) {
    out.extend_from_slice(&(result_formats.len() as u16).to_be_bytes());
    for f in result_formats {
        out.extend_from_slice(&f.to_be_bytes());
    }
    finish_len(out, pos);
}

pub(super) fn execute<S: Sink>(out: &mut S) {
    const MSG: [u8; 10] = [Fe::EXECUTE, 0, 0, 0, 9, 0, 0, 0, 0, 0];
    out.extend_from_slice(&MSG);
}