Thread

Commits

  1. Merge coding of return/exit/continue cases in plpgsql's loop statements.

  2. Improve regression tests' code coverage for plpgsql control structures.

  1. Better testing coverage and unified coding for plpgsql loops

    Tom Lane <tgl@sss.pgh.pa.us> — 2017-12-30T21:46:41Z

    While I've been fooling around with plpgsql, I've been paying close
    attention to code coverage reports to make sure that the regression tests
    exercise all that new code.  It started to bug me that there were some
    serious gaps in the test coverage for existing code in pl_exec.c.
    One thing I noticed in particular was that coverage for the
    PLPGSQL_RC_EXIT/PLPGSQL_RC_RETURN/PLPGSQL_RC_CONTINUE management code
    in the various looping statements was nearly nonexistent, and coverage
    for integer FOR loops was pretty awful too (eg, not a single BY clause
    in the whole test corpus :-().  So I said to myself "let's fix that",
    and wrote up a new regression test file plpgsql_control.sql with a
    charter to test plpgsql's control structures.  I moved a few existing
    tests that seemed to fall into that charter into the new file, and
    added tests until I was happier with the state of the coverage report.
    The result is attached as plpgsql-better-code-coverage.patch.
    
    However, while I was doing that, it seemed like the tests I was adding
    were mighty repetitive, as many of them were just exactly the same thing
    adjusted for a different kind of loop statement.  And so I began to wonder
    why it was that we had five copies of the RC_FOO management logic, no two
    quite alike.  If we only had *one* copy then it would not seem necessary
    to have such duplicative test cases for it.  A bit of hacking later, and
    I had the management logic expressed as a macro, with only one copy for
    all five kinds of loop.  I verified it still passes the previous set of
    tests and then removed the ones that seemed redundant, yielding
    plpgsql-unify-loop-rc-code.patch below.  So what I propose actually
    committing is the combination of these two patches.
    
    Anyone feel like reviewing this, or should I just push it?  It seems
    pretty noncontroversial to me, but maybe I'm wrong about that.
    
    			regards, tom lane
    
    
  2. Re: Better testing coverage and unified coding for plpgsql loops

    Pavel Stehule <pavel.stehule@gmail.com> — 2017-12-31T08:05:59Z

    2017-12-30 22:46 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:
    
    > While I've been fooling around with plpgsql, I've been paying close
    > attention to code coverage reports to make sure that the regression tests
    > exercise all that new code.  It started to bug me that there were some
    > serious gaps in the test coverage for existing code in pl_exec.c.
    > One thing I noticed in particular was that coverage for the
    > PLPGSQL_RC_EXIT/PLPGSQL_RC_RETURN/PLPGSQL_RC_CONTINUE management code
    > in the various looping statements was nearly nonexistent, and coverage
    > for integer FOR loops was pretty awful too (eg, not a single BY clause
    > in the whole test corpus :-().  So I said to myself "let's fix that",
    > and wrote up a new regression test file plpgsql_control.sql with a
    > charter to test plpgsql's control structures.  I moved a few existing
    > tests that seemed to fall into that charter into the new file, and
    > added tests until I was happier with the state of the coverage report.
    > The result is attached as plpgsql-better-code-coverage.patch.
    >
    > However, while I was doing that, it seemed like the tests I was adding
    > were mighty repetitive, as many of them were just exactly the same thing
    > adjusted for a different kind of loop statement.  And so I began to wonder
    > why it was that we had five copies of the RC_FOO management logic, no two
    > quite alike.  If we only had *one* copy then it would not seem necessary
    > to have such duplicative test cases for it.  A bit of hacking later, and
    > I had the management logic expressed as a macro, with only one copy for
    > all five kinds of loop.  I verified it still passes the previous set of
    > tests and then removed the ones that seemed redundant, yielding
    > plpgsql-unify-loop-rc-code.patch below.  So what I propose actually
    > committing is the combination of these two patches.
    >
    > Anyone feel like reviewing this, or should I just push it?  It seems
    > pretty noncontroversial to me, but maybe I'm wrong about that.
    >
    
    I don't think any issue there. This macro is little bit massive, but
    similar is used elsewhere.
    
    +1
    
    Pavel
    
    
    
    >                         regards, tom lane
    >
    >
    
  3. Re: Better testing coverage and unified coding for plpgsql loops

    Alvaro Herrera <alvherre@alvh.no-ip.org> — 2018-01-02T12:20:05Z

    Tom Lane wrote:
    
    > However, while I was doing that, it seemed like the tests I was adding
    > were mighty repetitive, as many of them were just exactly the same thing
    > adjusted for a different kind of loop statement.  And so I began to wonder
    > why it was that we had five copies of the RC_FOO management logic, no two
    > quite alike.  If we only had *one* copy then it would not seem necessary
    > to have such duplicative test cases for it.  A bit of hacking later, and
    > I had the management logic expressed as a macro, with only one copy for
    > all five kinds of loop.
    
    I'm not entirely sure about this rationale.  Improving coverage is of
    course an important goal, but it's not the only one: each documented
    construct and behavior should be tested also explicitly, to avoid any
    inadvertant breakage.  You're probably the most careful committer in the
    project, but what that means is that some other less careful committer
    (present or future) will need to hack this code again in the future and
    break some of the cases that you've made to work, because the test cases
    only exercise the generic behavior through some specific kind of loop,
    and not every individual kind of loop specifically.
    
    In other words, I think testing (the basic behavior of) every construct
    separately is worthwhile even if it tests the same code several times.
    
    -- 
    Álvaro Herrera                https://www.2ndQuadrant.com/
    PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
    
    
    
  4. Re: Better testing coverage and unified coding for plpgsql loops

    Darafei Komяpa Praliaskouski <me@komzpa.net> — 2018-01-02T12:54:24Z

    Hello!
    
    > However, while I was doing that, it seemed like the tests I was adding
    > were mighty repetitive, as many of them were just exactly the same thing
    > adjusted for a different kind of loop statement.  And so I began to wonder
    > why it was that we had five copies of the RC_FOO management logic, no two
    > quite alike.  If we only had *one* copy then it would not seem necessary
    > to have such duplicative test cases for it.  A bit of hacking later, and
    > I had the management logic expressed as a macro, with only one copy for
    > all five kinds of loop.  I verified it still passes the previous set of
    > tests and then removed the ones that seemed redundant, yielding
    > plpgsql-unify-loop-rc-code.patch below.  So what I propose actually
    > committing is the combination of these two patches.
    >
    
    I have looked into plpgsql-unify-loop-rc-code.patch.
    I have two questions:
    
     - how do currently existing coverage tools display coverage for such a
    large macro?
    
    I expect DEFINE's to be treated as comments.
    
    I've looked into https://coverage.postgresql.org/src/port/qsort.c.gcov.html and
    on line 70 I see a similar multi line define that is yellow in coverage,
    not counted at all. I think that "higher coverage" effect you are seeing is
    mostly due to code being hidden from coverage counter, not actually better
    testing. Another thing I see is that most define's are in .h files, and
    they're also not in coverage report mostly.
    
     - can this macro become a function?
    
  5. Re: Better testing coverage and unified coding for plpgsql loops

    Alvaro Herrera <alvherre@alvh.no-ip.org> — 2018-01-02T13:46:23Z

    Darafei "Komяpa" Praliaskouski wrote:
    
    >  - how do currently existing coverage tools display coverage for such a
    > large macro?
    > 
    > I expect DEFINE's to be treated as comments.
    
    It is, but then it is counted in the callsite where each branch is
    displayed separately.  So in
    https://coverage.postgresql.org/src/pl/plpgsql/src/pl_exec.c.gcov.html
    line 2028 you can see a bunch of "+" and three "-".
    
    >  - can this macro become a function?
    
    The "exit_action" argument makes it tough.  It can probably be done --
    it seems to require contorting the one callsite that uses "goto" though.
    
    -- 
    Álvaro Herrera                https://www.2ndQuadrant.com/
    PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
    
    
    
  6. Re: Better testing coverage and unified coding for plpgsql loops

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-02T15:08:10Z

    Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
    > Darafei "Komяpa" Praliaskouski wrote:
    >> - can this macro become a function?
    
    > The "exit_action" argument makes it tough.  It can probably be done --
    > it seems to require contorting the one callsite that uses "goto" though.
    
    It could be converted into a function returning bool, a la
    
    	if (!loop_rc_processing(...))
    		break;
    
    but then the burden is on you to show there's negligible performance
    impact, a question that doesn't arise when just macro-izing existing
    code.  I suppose the function could be made inline, but then we're
    right back to the question of how well lcov will display the actual
    code coverage.
    
    			regards, tom lane
    
    
    
  7. Re: Better testing coverage and unified coding for plpgsql loops

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-02T16:16:31Z

    Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
    > Tom Lane wrote:
    >> However, while I was doing that, it seemed like the tests I was adding
    >> were mighty repetitive, as many of them were just exactly the same thing
    >> adjusted for a different kind of loop statement.  And so I began to wonder
    >> why it was that we had five copies of the RC_FOO management logic, no two
    >> quite alike.  If we only had *one* copy then it would not seem necessary
    >> to have such duplicative test cases for it.  A bit of hacking later, and
    >> I had the management logic expressed as a macro, with only one copy for
    >> all five kinds of loop.
    
    > I'm not entirely sure about this rationale.  Improving coverage is of
    > course an important goal, but it's not the only one: each documented
    > construct and behavior should be tested also explicitly, to avoid any
    > inadvertant breakage.  You're probably the most careful committer in the
    > project, but what that means is that some other less careful committer
    > (present or future) will need to hack this code again in the future and
    > break some of the cases that you've made to work, because the test cases
    > only exercise the generic behavior through some specific kind of loop,
    > and not every individual kind of loop specifically.
    
    I don't especially buy this argument, at least not in this case.
    I can certainly believe that somebody would add another looping construct
    to plpgsql in future, but with the new setup they'd just copy-and-paste
    a macro invocation, and there's basically nothing to break.  Fooling
    with the RC logic itself is so rare as to be negligible --- looking at
    the git history, most of it sprang full grown from Jan Wieck's forehead
    in 1998, and the only meaningful change since then was when Neil Conway
    added CONTINUE in 2005.  So I think you're proposing to add regression
    testing overhead that will burden every developer, every day, for the
    foreseeable future, for a really negligible return.  That path leads to
    regression tests that nobody runs because they take two hours.
    
    			regards, tom lane
    
    
    
  8. Re: Better testing coverage and unified coding for plpgsql loops

    Robert Haas <robertmhaas@gmail.com> — 2018-01-03T18:36:02Z

    On Tue, Jan 2, 2018 at 10:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
    > Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
    >> Darafei "Komяpa" Praliaskouski wrote:
    >>> - can this macro become a function?
    >
    >> The "exit_action" argument makes it tough.  It can probably be done --
    >> it seems to require contorting the one callsite that uses "goto" though.
    >
    > It could be converted into a function returning bool, a la
    >
    >         if (!loop_rc_processing(...))
    >                 break;
    >
    > but then the burden is on you to show there's negligible performance
    > impact, a question that doesn't arise when just macro-izing existing
    > code.  I suppose the function could be made inline, but then we're
    > right back to the question of how well lcov will display the actual
    > code coverage.
    
    I prefer writing this sort of thing using a function call and
    dispatching on the return value, as you suggest in the text quoted
    here. Long macros that involve a zillion continuation lines are hard
    to edit, and often hard to step through properly in a debugger.  It
    may be that in this case it doesn't matter much because, as you said
    in the other email, this code may have no bugs and never get modified;
    if so, we don't care whether it's hard to edit and debug the code.  Of
    course, then we also don't really need tests for it, either.
    
    I disagree that Alvaro or Darafei must carry the burden of proving
    that using a function rather than a macro doesn't slow anything down.
    In general, it's not like performance concerns unilaterally trump
    readability.  But in this case, I suspect it would be hard to prove
    anything at all.  It seems unlikely to be in the first place that any
    change would be anything more than noise, and there's the sort of
    broader issues that duplicating the code makes the binary bigger,
    which carries a distributed cost of its own.  I'm not sure how you'd
    even design a fair test for something like this.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
    
  9. Re: Better testing coverage and unified coding for plpgsql loops

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-03T18:53:22Z

    Robert Haas <robertmhaas@gmail.com> writes:
    > On Tue, Jan 2, 2018 at 10:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
    >> It could be converted into a function returning bool, a la
    >> 	if (!loop_rc_processing(...))
    >> 		break;
    
    > I prefer writing this sort of thing using a function call and
    > dispatching on the return value, as you suggest in the text quoted
    > here. Long macros that involve a zillion continuation lines are hard
    > to edit, and often hard to step through properly in a debugger.
    
    I thought about this a bit harder and realized that if we make it
    a function, we will have to pass "rc" by reference since the function
    needs to change it in some cases.  That might have no impact if the
    compiler is smart enough, but I expect on at least some compilers
    it would end up forcing rc into memory with an attendant speed hit.
    
    I really think we should stick with the macro implementation, unless
    somebody wants to do some actual investigation to prove that a
    function implementation imposes negligible cost.  I'm not prepared
    to just assume that, especially not after the work I just did on
    plpgsql record processing --- I initially thought that an extra
    function call or three wouldn't matter in those code paths either,
    but I found out differently.
    
    			regards, tom lane
    
    
    
  10. Re: Better testing coverage and unified coding for plpgsql loops

    Robert Haas <robertmhaas@gmail.com> — 2018-01-03T18:56:23Z

    On Wed, Jan 3, 2018 at 1:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
    > I thought about this a bit harder and realized that if we make it
    > a function, we will have to pass "rc" by reference since the function
    > needs to change it in some cases.  That might have no impact if the
    > compiler is smart enough, but I expect on at least some compilers
    > it would end up forcing rc into memory with an attendant speed hit.
    >
    > I really think we should stick with the macro implementation, unless
    > somebody wants to do some actual investigation to prove that a
    > function implementation imposes negligible cost.  I'm not prepared
    > to just assume that, especially not after the work I just did on
    > plpgsql record processing --- I initially thought that an extra
    > function call or three wouldn't matter in those code paths either,
    > but I found out differently.
    
    OK.  I'm not really exercised about it, so I'll leave it to others to
    decide whether they want to spend time on it.
    
    -- 
    Robert Haas
    EnterpriseDB: http://www.enterprisedb.com
    The Enterprise PostgreSQL Company
    
    
    
  11. Re: Better testing coverage and unified coding for plpgsql loops

    Alvaro Herrera <alvherre@alvh.no-ip.org> — 2018-01-03T19:31:44Z

    Tom Lane wrote:
    
    > I really think we should stick with the macro implementation, unless
    > somebody wants to do some actual investigation to prove that a
    > function implementation imposes negligible cost.  I'm not prepared
    > to just assume that, especially not after the work I just did on
    > plpgsql record processing --- I initially thought that an extra
    > function call or three wouldn't matter in those code paths either,
    > but I found out differently.
    
    I don't really care too much about the macro-or-function side of this,
    but if you wanted to improve debuggability avoiding the performance cost
    of a function call, you could use a static inline function, which is
    supposed (AFAIK) to have performance characteristics equivalent to those
    of a macro.  But again I'm not voting either way and I'm not in a
    position to do the legwork either.
    
    -- 
    Álvaro Herrera                https://www.2ndQuadrant.com/
    PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
    
    
    
  12. Re: Better testing coverage and unified coding for plpgsql loops

    Tom Lane <tgl@sss.pgh.pa.us> — 2018-01-03T19:37:02Z

    Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
    > Tom Lane wrote:
    >> I really think we should stick with the macro implementation, unless
    >> somebody wants to do some actual investigation to prove that a
    >> function implementation imposes negligible cost.
    
    > I don't really care too much about the macro-or-function side of this,
    > but if you wanted to improve debuggability avoiding the performance cost
    > of a function call, you could use a static inline function, which is
    > supposed (AFAIK) to have performance characteristics equivalent to those
    > of a macro.
    
    I'm not sure whether inlining the function can be relied on to get rid
    of the side effects of taking rc's address.  It wouldn't take all that
    much work to establish the point, probably, but it's work I don't care
    to put into it.
    
    			regards, tom lane