-- bench/sql/bench.sql
-- pg_accumulator Benchmark Suite
--
-- Measures throughput of core API operations:
--   1. register_post() — single inserts
--   2. register_post() — batch 10 items
--   3. register_post() — batch 100 items
--   4. register_post() — batch 1000 items
--   5. balance read (balance_cache SELECT)
--   6. balance() generic function
--   7. register_post() — high_write mode (delta buffer)
--   8. register_unpost() — cancel movements
--
-- Usage: psql -f bench/sql/bench.sql
-- ================================================================

\echo ''
\echo '================================================================'
\echo '  pg_accumulator — Benchmark Suite'
\echo '================================================================'
\echo ''

-- ================================================================
-- Initialise
-- ================================================================
CREATE EXTENSION IF NOT EXISTS pg_accumulator;

-- Results collection table
CREATE TEMP TABLE bench_results (
    ord         serial,
    scenario    text,
    iterations  integer,
    total_ms    numeric(12,2),
    avg_ms      numeric(10,4),
    ops_per_sec numeric(12,1)
);

-- Convenience helper used by every DO block
CREATE OR REPLACE FUNCTION pg_temp.bench_record(
    p_scenario  text,
    p_n         integer,
    p_start     timestamptz,
    p_end       timestamptz
) RETURNS void LANGUAGE sql AS $$
    INSERT INTO bench_results(scenario, iterations, total_ms, avg_ms, ops_per_sec)
    SELECT
        p_scenario,
        p_n,
        round(extract(epoch from (p_end - p_start)) * 1000,  2),
        round(extract(epoch from (p_end - p_start)) * 1000 / p_n, 4),
        round(p_n / extract(epoch from (p_end - p_start)), 1);
$$;

-- ================================================================
-- Test registers
-- ================================================================
\echo 'Setting up benchmark registers...'

SELECT accum.register_create(
    name       := 'b_std',
    dimensions := '{"warehouse": "int", "product": "int"}',
    resources  := '{"quantity": "numeric(18,4)", "amount": "numeric(18,2)"}',
    kind       := 'balance'
);

SELECT accum.register_create(
    name       := 'b_hw',
    dimensions := '{"warehouse": "int", "product": "int"}',
    resources  := '{"quantity": "numeric(18,4)", "amount": "numeric(18,2)"}',
    kind       := 'balance',
    high_write := true
);

\echo ''

-- ================================================================
-- SCENARIO 1: register_post() — single inserts
-- ================================================================
\echo 'Scenario 1/8  register_post() single inserts (5,000 ops)...'

DO $$
DECLARE
    t_start timestamptz;
    t_end   timestamptz;
    N       constant int := 5000;
    i       int;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N LOOP
        PERFORM accum.register_post('b_std', jsonb_build_object(
            'recorder', 'bench:s1:' || i,
            'period',   '2026-01-15',
            'warehouse', (i % 10) + 1,
            'product',   (i % 50) + 1,
            'quantity',  1,
            'amount',    10.00
        ));
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record('1. register_post() single insert', N, t_start, t_end);
END;
$$;

-- ================================================================
-- SCENARIO 2: register_post() — batch 10
-- ================================================================
\echo 'Scenario 2/8  register_post() batch 10 (500 × 10 = 5,000 items)...'

DO $$
DECLARE
    t_start    timestamptz;
    t_end      timestamptz;
    N_batches  constant int := 500;
    BATCH_SZ   constant int := 10;
    i          int;
    batch      jsonb;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N_batches LOOP
        SELECT jsonb_agg(jsonb_build_object(
            'recorder',  'bench:s2:' || i || ':' || j,
            'period',    '2026-02-01',
            'warehouse', (j % 10) + 1,
            'product',   (j % 50) + 1,
            'quantity',  1,
            'amount',    5.00
        )) INTO batch
        FROM generate_series(1, BATCH_SZ) AS j;

        PERFORM accum.register_post('b_std', batch);
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record(
        '2. register_post() batch 10  (500 batches)',
        N_batches * BATCH_SZ, t_start, t_end
    );
END;
$$;

-- ================================================================
-- SCENARIO 3: register_post() — batch 100
-- ================================================================
\echo 'Scenario 3/8  register_post() batch 100 (100 × 100 = 10,000 items)...'

DO $$
DECLARE
    t_start   timestamptz;
    t_end     timestamptz;
    N_batches constant int := 100;
    BATCH_SZ  constant int := 100;
    i         int;
    batch     jsonb;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N_batches LOOP
        SELECT jsonb_agg(jsonb_build_object(
            'recorder',  'bench:s3:' || i || ':' || j,
            'period',    '2026-03-01',
            'warehouse', (j % 10) + 1,
            'product',   (j % 50) + 1,
            'quantity',  1,
            'amount',    5.00
        )) INTO batch
        FROM generate_series(1, BATCH_SZ) AS j;

        PERFORM accum.register_post('b_std', batch);
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record(
        '3. register_post() batch 100 (100 batches)',
        N_batches * BATCH_SZ, t_start, t_end
    );
END;
$$;

-- ================================================================
-- SCENARIO 4: register_post() — batch 1000
-- ================================================================
\echo 'Scenario 4/8  register_post() batch 1000 (10 × 1000 = 10,000 items)...'

DO $$
DECLARE
    t_start   timestamptz;
    t_end     timestamptz;
    N_batches  constant int := 10;
    BATCH_SZ  constant int := 1000;
    i         int;
    batch     jsonb;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N_batches LOOP
        SELECT jsonb_agg(jsonb_build_object(
            'recorder',  'bench:s4:' || i || ':' || j,
            'period',    '2026-04-01',
            'warehouse', (j % 10) + 1,
            'product',   (j % 50) + 1,
            'quantity',  1,
            'amount',    5.00
        )) INTO batch
        FROM generate_series(1, BATCH_SZ) AS j;

        PERFORM accum.register_post('b_std', batch);
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record(
        '4. register_post() batch 1000 (10 batches)',
        N_batches * BATCH_SZ, t_start, t_end
    );
END;
$$;

-- ================================================================
-- SCENARIO 5: balance_cache direct read (point lookup)
-- ================================================================
\echo 'Scenario 5/8  balance_cache direct read (2,000 point lookups)...'

DO $$
DECLARE
    t_start timestamptz;
    t_end   timestamptz;
    N       constant int := 2000;
    i       int;
    dummy   numeric;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N LOOP
        SELECT quantity INTO dummy
        FROM accum.b_std_balance_cache
        WHERE warehouse = (i % 10) + 1
          AND product   = (i % 50) + 1
        LIMIT 1;
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record('5. balance_cache direct SELECT', N, t_start, t_end);
END;
$$;

-- ================================================================
-- SCENARIO 6: b_std_balance() per-register function
-- ================================================================
\echo 'Scenario 6/8  b_std_balance() per-register function (1,000 calls)...'

DO $$
DECLARE
    t_start timestamptz;
    t_end   timestamptz;
    N       constant int := 1000;
    i       int;
    dummy   jsonb;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N LOOP
        SELECT accum.b_std_balance(
            dimensions := jsonb_build_object(
                'warehouse', (i % 10) + 1,
                'product',   (i % 50) + 1
            )
        ) INTO dummy;
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record('6. b_std_balance() per-register fn', N, t_start, t_end);
END;
$$;

-- ================================================================
-- SCENARIO 7: register_post() high_write mode (delta buffer)
-- ================================================================
\echo 'Scenario 7/8  register_post() high_write delta buffer (5,000 ops)...'

DO $$
DECLARE
    t_start timestamptz;
    t_end   timestamptz;
    N       constant int := 5000;
    i       int;
BEGIN
    t_start := clock_timestamp();
    FOR i IN 1..N LOOP
        PERFORM accum.register_post('b_hw', jsonb_build_object(
            'recorder', 'bench:s7:' || i,
            'period',   '2026-01-15',
            'warehouse', (i % 10) + 1,
            'product',   (i % 50) + 1,
            'quantity',  1,
            'amount',    10.00
        ));
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record('7. register_post() high_write mode', N, t_start, t_end);
END;
$$;

-- ================================================================
-- SCENARIO 8: register_unpost() — cancel movements
-- ================================================================
\echo 'Scenario 8/8  register_unpost() (1,000 cancellations)...'

DO $$
DECLARE
    t_start   timestamptz;
    t_end     timestamptz;
    N       constant int := 1000;
    i         int;
    rec_id    text;
BEGIN
    -- Pre-seed 1 000 movements dedicated to unpost testing
    FOR i IN 1..N LOOP
        PERFORM accum.register_post('b_std', jsonb_build_object(
            'recorder', 'bench:s8:' || i,
            'period',   '2026-05-01',
            'warehouse', (i % 10) + 1,
            'product',   (i % 50) + 1,
            'quantity',  1,
            'amount',    1.00
        ));
    END LOOP;

    -- Now time the unpost
    t_start := clock_timestamp();
    FOR i IN 1..N LOOP
        PERFORM accum.register_unpost('b_std', 'bench:s8:' || i);
    END LOOP;
    t_end := clock_timestamp();
    PERFORM pg_temp.bench_record('8. register_unpost()', N, t_start, t_end);
END;
$$;

-- ================================================================
-- RESULTS
-- ================================================================
\echo ''
\echo '================================================================'
\echo '  RESULTS'
\echo '================================================================'

\pset border 2
\pset format aligned

SELECT
    scenario                                   AS "Scenario",
    to_char(iterations, '999G999G999')         AS "Iterations",
    to_char(total_ms,   'FM99999990.00') || ' ms'  AS "Total",
    to_char(avg_ms,     'FM9990.0000')   || ' ms'  AS "Avg/op",
    to_char(ops_per_sec,'FM999G999G990.0')          AS "ops/sec"
FROM bench_results
ORDER BY ord;

\echo ''
\echo '================================================================'
\echo '  DATA VOLUME'
\echo '================================================================'

SELECT
    relname                                      AS "Table",
    to_char(n_live_tup, '999G999G999')           AS "Live rows",
    pg_size_pretty(pg_total_relation_size(
        'accum.' || relname))                    AS "Total size"
FROM pg_stat_user_tables
WHERE schemaname = 'accum'
  AND relname LIKE 'b_%'
ORDER BY relname;

\echo ''
