// Nested transactions: join/undo, fate of tables
// Textbook section: Volume III, chapter 18, "Nested transactions", §18.2/§18.3.
// What it demonstrates: a nested transaction (start_nested): abort rolls back its
//   changes (undo); commit folds them into the parent (join); a table created in a
//   nested transaction and then rolled back disappears.
// Build: g++ -std=c++17 -I<libmdbx-include> -I../common 25-nested-txn.c++ -lmdbx
// Run: ./25-nested-txn
// Expected output:
//   parent sees k1 after nested abort: v1
//   k2 rolled back by nested abort: absent
//   tmp table dropped with nested abort: absent
//   k3 joined by nested commit: v3
//   ok: nested transactions join/abort; table fate honored
// On failure — message to stderr and exit 1.
//
// SPDX-License-Identifier: Apache-2.0

#include <iostream>
#include <string>

#include "common.h++"
#include <mdbx.h++>

int main() {
  try {
    const std::string path = std::string(example::tmpdir()) + "/mdbx-25-nested-txn.mdbx";
    mdbx::env::remove(path);

    // Nested transactions are enabled by an environment option.
    mdbx::env::operate_parameters params;
    params.options.nested_transactions = true;
    mdbx::env_managed env = example::env_open(path, params);

    auto txn = env.start_write();
    auto table = txn.open_map(nullptr);
    txn.insert(table, mdbx::slice("k1"), mdbx::slice("v1"));

    // A nested transaction with a rollback (undo).
    {
      auto nested = txn.start_nested();
      auto ntable = nested.open_map(nullptr);
      nested.insert(ntable, mdbx::slice("k2"), mdbx::slice("v2"));
      // rollback of the nested transaction
      nested.abort();
    }
    std::cout << "parent sees k1 after nested abort: " << txn.get(table, mdbx::slice("k1")).as_string() << "\n";
    try {
      (void)txn.get(table, mdbx::slice("k2"));
      std::cerr << "FAIL: k2 survived nested abort\n";
      return EXIT_FAILURE;
    } catch (const mdbx::not_found &) {
      std::cout << "k2 rolled back by nested abort: absent\n";
    }

    // A table created in a nested transaction that was then rolled back disappears.
    {
      auto nested = txn.start_nested();
      auto tmp = nested.create_map("tmp-table", mdbx::key_mode::usual, mdbx::value_mode::single);
      nested.insert(tmp, mdbx::slice("x"), mdbx::slice("y"));
      nested.abort();
    }
    try {
      (void)txn.open_map("tmp-table");
      std::cerr << "FAIL: tmp table survived nested abort\n";
      return EXIT_FAILURE;
    } catch (const mdbx::not_found &) {
      std::cout << "tmp table dropped with nested abort: absent\n";
    }

    // A nested transaction with commit — the changes go into the parent (join).
    {
      auto nested = txn.start_nested();
      auto ntable = nested.open_map(nullptr);
      nested.insert(ntable, mdbx::slice("k3"), mdbx::slice("v3"));
      nested.commit();
    }
    std::cout << "k3 joined by nested commit: " << txn.get(table, mdbx::slice("k3")).as_string() << "\n";

    txn.commit();

    std::cout << "ok: nested transactions join/abort; table fate honored\n";
    return EXIT_SUCCESS;
  } catch (const std::exception &ex) {
    std::cerr << "error: " << ex.what() << "\n";
    return EXIT_FAILURE;
  }
}