--
-- Explicit subtransactions (ported from PL/Tcl): subtransaction(callable, ...).
--

CREATE TABLE sx (id int);

-- A successful subtransaction commits its work.
CREATE FUNCTION sx_ok() RETURNS int LANGUAGE plphp AS $$
    subtransaction(function() { spi_exec("insert into sx values (1)"); });
    return 1;
$$;
SELECT sx_ok();
SELECT count(*) AS after_ok FROM sx;

-- A PHP exception thrown in the body rolls the subtransaction back and is
-- catchable by the caller.
CREATE FUNCTION sx_catch() RETURNS text LANGUAGE plphp AS $$
    try {
        subtransaction(function() {
            spi_exec("insert into sx values (2)");
            throw new Exception("rollback me");
        });
    } catch (\Throwable $e) {
        return "caught: " . $e->getMessage();
    }
    return "no exception";
$$;
SELECT sx_catch();
-- The insert of 2 was rolled back, so the count is unchanged.
SELECT count(*) AS after_catch FROM sx;

-- Arguments after the callable are passed through to it, and the callable's
-- return value is returned.
CREATE FUNCTION sx_ret(int) RETURNS int LANGUAGE plphp AS $$
    return subtransaction(function($x) { return $x * 2; }, $args[0]);
$$;
SELECT sx_ret(21);

-- Nested subtransactions: the inner one rolls back (its exception is caught)
-- while the outer one commits.
CREATE FUNCTION sx_nested() RETURNS void LANGUAGE plphp AS $$
    subtransaction(function() {
        spi_exec("insert into sx values (10)");
        try {
            subtransaction(function() {
                spi_exec("insert into sx values (11)");
                throw new Exception("inner fails");
            });
        } catch (\Throwable $e) {
            pg_raise('notice', 'inner rolled back: ' . $e->getMessage());
        }
    });
$$;
SELECT sx_nested();
-- 1 was already present; 10 committed, 11 rolled back.
SELECT id FROM sx ORDER BY id;

-- A database error inside a subtransaction rolls it back and surfaces as a
-- catchable PgError.
CREATE FUNCTION sx_dberr() RETURNS void LANGUAGE plphp AS $$
    try {
        subtransaction(function() { spi_exec("insert into sx values (1/0)"); });
    } catch (PgError $e) {
        pg_raise('notice', 'caught ' . $e->getSQLState() . ': ' . $e->getMessage());
    }
$$;
SELECT sx_dberr();
SELECT count(*) AS after_dberr FROM sx;

DROP TABLE sx;
