uqa-engine 0.4.0

Engine: schema-aware table store, catalog restore, transactions
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

//! Role OIDs identify stored objects across catalog references and transaction undo.

use crate::{
    tests::relation_lock_support::{sessions, sql},
    Engine,
};
use uqa_core::Value;

fn oid(engine: &Engine, name: &str) -> Value {
    sql(engine, &format!("SELECT to_regrole('{name}')::oid AS id")).rows[0]["id"].clone()
}

pub(super) use crate::tests::relation_lock_support::reopen;

#[test]
fn role_oids_follow_create_undo_recreate_and_reopen_for_every_provider() {
    for provider in 0..3 {
        let (directory, first, second) = sessions(provider);
        sql(&first, "CREATE ROLE named");
        let original = oid(&first, "named");
        let incarnation = first.durable.roles.read()["named"].object_id;
        assert_ne!(incarnation, [0; 16]);
        assert!(
            matches!(original, Value::Int(value) if (16_384..=i64::from(u32::MAX)).contains(&value))
        );
        sql(&first, "ALTER ROLE named LOGIN");
        assert_eq!(oid(&first, "named"), original);
        assert_eq!(first.durable.roles.read()["named"].object_id, incarnation);
        sql(
            &first,
            "BEGIN; SAVEPOINT old_role; DROP ROLE named; CREATE ROLE named",
        );
        assert_ne!(oid(&first, "named"), original);
        assert_ne!(first.durable.roles.read()["named"].object_id, incarnation);
        assert_eq!(oid(&second, "named"), original);
        sql(&first, "ROLLBACK TO old_role; COMMIT");
        assert_eq!(oid(&first, "named"), original);
        assert_eq!(first.durable.roles.read()["named"].object_id, incarnation);
        sql(&first, "DROP ROLE named; CREATE ROLE named");
        let replacement = oid(&first, "named");
        let replacement_incarnation = first.durable.roles.read()["named"].object_id;
        assert_ne!(replacement_incarnation, incarnation);
        assert_ne!(replacement, original);
        assert_eq!(oid(&second, "named"), replacement);
        drop(second);
        drop(first);
        let reopened = reopen(provider, &directory.path().join("table-locks.db"));
        assert_eq!(oid(&reopened, "named"), replacement);
        assert_eq!(
            reopened.durable.roles.read()["named"].object_id,
            replacement_incarnation
        );
    }
}

#[test]
fn role_catalog_references_use_persisted_owner_and_membership_oids() {
    let engine = Engine::new();
    sql(&engine, "CREATE ROLE owner; CREATE ROLE member; GRANT owner TO member; CREATE SCHEMA owned AUTHORIZATION owner; SET ROLE owner; CREATE TABLE owned.items(id integer); CREATE INDEX items_id ON owned.items(id); CREATE VIEW owned.visible AS SELECT id FROM owned.items; CREATE MATERIALIZED VIEW owned.saved AS SELECT id FROM owned.items; CREATE SEQUENCE owned.counter; CREATE FUNCTION owned.answer() RETURNS integer LANGUAGE sql AS 'SELECT 1'; RESET ROLE; CREATE SERVER remote FOREIGN DATA WRAPPER memory_fdw; CREATE FOREIGN TABLE owned.remote(id integer) SERVER remote; ALTER FOREIGN TABLE owned.remote OWNER TO owner");
    let owner = oid(&engine, "owner");
    let member = oid(&engine, "member");
    let rows = sql(&engine, "SELECT relname, relowner FROM pg_class WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'owned')").rows;
    assert_eq!(rows.len(), 6);
    for row in rows {
        assert_eq!(row["relowner"], owner, "{:?}", row["relname"]);
    }
    assert_eq!(
        sql(
            &engine,
            "SELECT nspowner FROM pg_namespace WHERE nspname = 'owned'"
        )
        .rows[0]["nspowner"],
        owner
    );
    assert_eq!(
        sql(
            &engine,
            "SELECT proowner FROM pg_proc WHERE proname = 'answer'"
        )
        .rows[0]["proowner"],
        owner
    );
    let membership = &sql(
        &engine,
        "SELECT roleid, member, grantor FROM pg_auth_members",
    )
    .rows[0];
    assert_eq!(membership["roleid"], owner);
    assert_eq!(membership["member"], member);
    assert_eq!(membership["grantor"], Value::Int(10));
}