/*------------------------------------------------------------------------
   File        : TestQueryKindReader
   Purpose     : Theme 8 reader.  Settles #11723 note #16: which KINDS of query
                 prefetch, measured side by side in one run so the comparison is
                 not across runs.
   Notes       : Pairs with TestQueryKindWriter on harness port 8898.

                 Note #16 corrects an assumption in my earlier report: a STATIC
                 DEFINE QUERY is forward-only by default, while a DYNAMIC query
                 handle is SCROLLING by default.  Both queries in the earlier
                 test were static, so that A/B measured the SCROLLING keyword on
                 static queries only - it never measured a dynamic query cleanly
                 (theme 4's dynamic scenario also repositioned, which perturbs the
                 window).

                 So five query kinds are measured here in one run, same fixture,
                 same pause, writer doing the identical thing:

                   FE01  FOR EACH                       (the known-prefetching baseline)
                   SQ01  static DEFINE QUERY            (forward-only by default)
                   SQ02  static DEFINE QUERY SCROLLING
                   DQ01  dynamic CREATE QUERY           (scrolling by default)
                   DQ02  dynamic CREATE QUERY, FORWARD-ONLY = TRUE

                 If prefetch tracks scrollability, SQ02 and DQ01 prefetch while
                 SQ01 and DQ02 do not.  If it tracks static-vs-dynamic, the split
                 falls the other way.  FE01 anchors both readings.

                 IR01 answers whether INDEXED-REPOSITION re-establishes a window;
                 QK01 replaces my loose phrase "kills the query" with attributes.
 ----------------------------------------------------------------------*/

using Progress.Lang.*.
using OpenEdge.Core.Assert.
using support.harness.msgbuf.* from propath.

block-level on error undo, throw.

class support.harness.msgbuf.TestQueryKindReader:

    define variable sync    as MsgBufSync    no-undo.
    define variable fixture as MsgBufFixture no-undo.
    define variable log     as MsgBufLog     no-undo.
    define variable tgt     as MsgBufTargets no-undo.

    define query qStatic    for Book.
    define query qScrolling for Book scrolling.

    define buffer bProbe for Book.

    define variable rids as rowid extent 120 no-undo.

    @Before.
    method public void BeforeAll ():
        if valid-object(sync) then
            return.

        session:suppress-warnings = false no-error.

        fixture = new MsgBufFixture().
        fixture:AssertClientServer().
        tgt = new MsgBufTargets().

        log = new MsgBufLog("querykind").
        log:StartRun(fixture:ConnectionInfo()).

        sync = new MsgBufSync().
        sync:Connect(8898, "TestQueryKindReader").
    end method.

    @After.
    method public void AfterAll ():
        if valid-object(log) then
            log:Dump("support/harness/msgbuf/results").
    end method.

    /* ---------------- the five query kinds ---------------- */

    @Test.
    method public void FE01ForEach ():
        define variable iter as integer no-undo.

        Begin("FE01", "FOR EACH Book NO-LOCK").

        for each Book no-lock:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("FE01").
        end.

        Finish("FE01").
    end method.

    @Test.
    method public void SQ01StaticDefault ():
        define variable iter as integer no-undo.

        Begin("SQ01", "static DEFINE QUERY (forward-only by default) + GET NEXT").

        open query qStatic for each Book no-lock.
        get next qStatic.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("SQ01").
            get next qStatic.
        end.
        close query qStatic.

        Finish("SQ01").
    end method.

    @Test.
    method public void SQ02StaticScrolling ():
        define variable iter as integer no-undo.

        Begin("SQ02", "static DEFINE QUERY SCROLLING + GET NEXT").

        open query qScrolling for each Book no-lock.
        get next qScrolling.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("SQ02").
            get next qScrolling.
        end.
        close query qScrolling.

        Finish("SQ02").
    end method.

    @Test.
    method public void DQ01DynamicDefault ():
        define variable hQry as handle  no-undo.
        define variable hBuf as handle  no-undo.
        define variable iter as integer no-undo.

        Begin("DQ01", "dynamic CREATE QUERY (scrolling by default) + GET-NEXT").

        create buffer hBuf for table "Book".
        create query hQry.
        hQry:set-buffers(hBuf).
        hQry:query-prepare("for each Book no-lock").
        log:Note(substitute("before open: FORWARD-ONLY=&1", hQry:forward-only)).
        hQry:query-open().

        hQry:get-next().
        do while hBuf:available:
            iter = iter + 1.
            log:Row(iter, integer(hBuf:buffer-field("book-id"):buffer-value),
                    string(hBuf:buffer-field("new_dt"):buffer-value)).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("DQ01").
            hQry:get-next().
        end.
        hQry:query-close().

        Finish("DQ01").

        finally:
            delete object hQry no-error.
            delete object hBuf no-error.
        end finally.
    end method.

    @Test.
    method public void DQ02DynamicForwardOnly ():
        define variable hQry as handle  no-undo.
        define variable hBuf as handle  no-undo.
        define variable iter as integer no-undo.

        Begin("DQ02", "dynamic CREATE QUERY with FORWARD-ONLY = TRUE + GET-NEXT").

        create buffer hBuf for table "Book".
        create query hQry.
        hQry:set-buffers(hBuf).
        hQry:forward-only = true.
        hQry:query-prepare("for each Book no-lock").
        log:Note(substitute("before open: FORWARD-ONLY=&1", hQry:forward-only)).
        hQry:query-open().

        hQry:get-next().
        do while hBuf:available:
            iter = iter + 1.
            log:Row(iter, integer(hBuf:buffer-field("book-id"):buffer-value),
                    string(hBuf:buffer-field("new_dt"):buffer-value)).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("DQ02").
            hQry:get-next().
        end.
        hQry:query-close().

        Finish("DQ02").

        finally:
            delete object hQry no-error.
            delete object hBuf no-error.
        end finally.
    end method.

    /* ---------------- INDEXED-REPOSITION ---------------- */

    @Test.
    method public void IR01IndexedReposition ():
        define variable iter as integer no-undo.
        define variable done as logical no-undo.

        /* Two-phase: phase 1 marks V2 while the reader sits in its first window;
           the reader then repositions well past it and stops again; phase 2 marks
           V3.  A run of V2 after the second pause is a window that formed AFTER
           the reposition - which plain REPOSITION did not produce. */
        Begin2("IR01", "OPEN QUERY ... INDEXED-REPOSITION, reposition to position 60, second phase").
        CaptureRowids().

        open query qScrolling for each Book no-lock indexed-reposition.
        get next qScrolling.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).

            if iter eq tgt:PauseAt then
                sync:ReaderPause("IR01").
            else if iter eq tgt:PauseAt + 1 and not done then
            do:
                done = true.
                reposition qScrolling to rowid rids[60] no-error.
                log:Note(substitute("after REPOSITION (INDEXED-REPOSITION): available=&1 error=&2",
                                    available Book, error-status:error)).
                get next qScrolling.
                if available Book then
                    log:Note(substitute("landed on id &1 mark=&2", Book.book-id, Book.new_dt)).
                sync:ReaderPauseSecond("IR01").
            end.

            get next qScrolling.
        end.
        close query qScrolling.

        Finish("IR01").
    end method.

    /* ---------------- what "kills the query" actually means ---------------- */

    @Test.
    method public void QK01RepositionOffResultSet ():
        define variable hQry as handle  no-undo.
        define variable hBuf as handle  no-undo.
        define variable iter as integer no-undo.
        define variable rid  as rowid   no-undo.
        define variable done as logical no-undo.

        /* My earlier wording - "kills the query" - was loose.  Report the
           attributes instead: after repositioning onto a rowid that is not in the
           result set, is the query still open, is it off-end, and can GET-FIRST
           recover it? */
        Begin("QK01", "dynamic query, REPOSITION-TO-ROWID onto a row inserted after OPEN").

        create buffer hBuf for table "Book".
        create query hQry.
        hQry:set-buffers(hBuf).
        hQry:query-prepare("for each Book no-lock").
        hQry:query-open().

        hQry:get-next().
        do while hBuf:available:
            iter = iter + 1.
            log:Row(iter, integer(hBuf:buffer-field("book-id"):buffer-value),
                    string(hBuf:buffer-field("new_dt"):buffer-value)).

            if iter eq tgt:PauseAt then
                sync:ReaderPause("QK01").
            else if iter eq tgt:PauseAt + 1 and not done then
            do:
                done = true.

                find bProbe where bProbe.book-id eq tgt:KeyCreateInside no-lock no-error.
                if available bProbe then
                do:
                    rid = rowid(bProbe).
                    release bProbe.

                    hQry:reposition-to-rowid(rid) no-error.
                    log:Note(substitute("after REPOSITION-TO-ROWID(off-result-set): error=&1 msg=&2",
                                        error-status:error,
                                        (if error-status:num-messages > 0
                                         then error-status:get-message(1) else "none"))).
                    log:Note(substitute("query state: IS-OPEN=&1 QUERY-OFF-END=&2 buffer AVAILABLE=&3",
                                        hQry:is-open, hQry:query-off-end, hBuf:available)).

                    hQry:get-next() no-error.
                    log:Note(substitute("after GET-NEXT: available=&1 off-end=&2",
                                        hBuf:available, hQry:query-off-end)).

                    hQry:get-first() no-error.
                    log:Note(substitute("after GET-FIRST (recovery attempt): available=&1 id=&2 is-open=&3",
                                        hBuf:available,
                                        (if hBuf:available
                                         then string(hBuf:buffer-field("book-id"):buffer-value)
                                         else "n/a"),
                                        hQry:is-open)).
                    leave.
                end.
            end.

            hQry:get-next().
        end.
        hQry:query-close() no-error.

        Finish("QK01").

        finally:
            delete object hQry no-error.
            delete object hBuf no-error.
        end finally.
    end method.

    /* ---------------- shared ---------------- */

    method private void Begin (scn as character, queryText as character):
        fixture:Build(fixture:PROFILE_SLIM).
        log:StartScenario(scn, fixture:Profile, fixture:SampleRecordLength(),
                          tgt:PauseAt, queryText, "blanket update Book -> V2").
        sync:ReaderReady(scn).
    end method.

    method private void Begin2 (scn as character, queryText as character):
        fixture:Build(fixture:PROFILE_SLIM).
        log:StartScenario(scn, fixture:Profile, fixture:SampleRecordLength(),
                          tgt:PauseAt, queryText,
                          "phase 1 all -> V2; phase 2 all -> V3").
        sync:ReaderReady(scn).
    end method.

    method private void CaptureRowids ():
        define variable i as integer no-undo.

        for each Book no-lock by Book.book-id:
            i = i + 1.
            if i <= extent(rids) then
                rids[i] = rowid(Book).
        end.

        release Book.
    end method.

    method private void Finish (scn as character):
        sync:ReaderDone(scn).
        log:EndScenario().
        Assert:IsTrue(log:ScenarioRows > 0).
    end method.

end class.
