<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>Control Should be Data, Too</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Denis Hirn</string-name>
          <email>denis.hirn@uni-tuebingen.de</email>
        </contrib>
        <contrib contrib-type="editor">
          <string-name>University of Tübingen, Germany</string-name>
        </contrib>
      </contrib-group>
      <abstract>
        <p>PL/SQL functions sufer from poor runtime performance due to the frequent context switches that occur between the PL/SQL interpreter and the SQL executor. This switching causes friction that can slow down UDF execution significantly. Table-valued UDFs incur the additional challenge of the eficient treatment of the sizable results they generate. In this paper, we generalize our PL/SQL UDF compilation strategy to also handle such table-valued UDFs. The generated SQL code carefully separates control flow from data flow at runtime. Compiled UDFs eficiently stream their table-valued results (as opposed to UDF variants that need to hold and copy intermediate states in array variables) and thus impose significantly less memory pressure.</p>
      </abstract>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>
        PL/SQL is a high-level procedural programming language
that allows developers to write custom user-defined
functions (UDFs), operators, and algorithms that are not
tem. PL/SQL enables a style of imperative programming—
which is quite diferent from the declarative set-oriented
SQL paradigm—but still provides immediate access to
database-resident tables. The distinctive PL/SQL features
are (
        <xref ref-type="bibr" rid="ref1">1</xref>
        ) destructive variable assignments, (
        <xref ref-type="bibr" rid="ref2">2</xref>
        ) statement
sequences, (
        <xref ref-type="bibr" rid="ref3">3</xref>
        ) arbitrary control flow (e.g. in terms of
IF...ELSE, WHILE, EXIT), and a (
        <xref ref-type="bibr" rid="ref4">4</xref>
        ) seamless integration of
      </p>
      <sec id="sec-1-1">
        <title>SQL queries and expressions.</title>
        <p>PL/SQL SQL
Q</p>
        <p>WHILE
Q1 in march</p>
      </sec>
      <sec id="sec-1-2">
        <title>Q1 overhead</title>
        <p>time</p>
        <p>Typically, PL/SQL is
implemented as an interpreted
language on top of SQL host
engines.</p>
        <p>The imperative,
non-SQL statements are
interpreted by the PL/SQL
subsystem, while all embedded
SQL queries are sent to the
SQL executor. Because both
execution environments are
completely disparate, each
switch from one context to
the other (and back) takes
time and therefore causes</p>
      </sec>
      <sec id="sec-1-3">
        <title>The situation is particularly</title>
        <p>PL/SQL (FOR or WHILE) loops. In that case, switching back
and forth between PL/SQL and the SQL executor occurs
very frequently, which multiplies the overhead, and
ultimately slows down execution. Figure 1 visualizes this.</p>
        <p>It has thus become common developer lore that PL/SQL
nonetheless been used for decades to implement complex
database-driven applications [2]. Research has since
recognized this as an important issue, resulting in several
publications addressing this pressing challenge [1, 3–5].</p>
        <p>The scope of the PhD project. The overarching goal
of the PhD project is to allow developers to use
imperative programming, e.g., in the form of PL/SQL UDFs, while
maintaining the high performance of plan-based SQL
execution. To accomplish this, we develop new ways to
compile imperative PL/SQL UDFs into SQL queries. This
compilation can handle arbitrary nesting of the PL/SQL
features mentioned on the left. This includes looping
control flow. As a side efect of this efort, database systems
without PL/SQL support—but with support for a
contemporary SQL dialect—will be able to run imperative PL/SQL
UDFs after compilation, since no PL/SQL interpreter is
required. In the present paper, we focus on the
compilation of imperative (typically: iterative) table-valued UDF
code into recursive yet plain SQL queries.
1.1. From Scalar Values to Tables
scalar PL/SQL UDFs to a single recursive SQL CTE
(WITH RECURSIVE). While keeping the basic idea and
compilation chain as is, the present work separates the
management of control flow and data flow to make the
compilation suitable for table-valued UDFs. To this end, we
introduce the concept of control rows and data rows.
Previously, the compiler used only control rows and could</p>
        <p>Let us look at an example. UDF march of Figure 2a is a
ing as UDF march executes.
dire when these embedded queries are placed in tight
VLDB 2023 PhD Workshop, co-located with the 49th International
0000-0001-7040-1780 (D. Hirn)
1 CREATE FUNCTION march-arr(start vec2) RETURNS vec2[] AS $$
2 DECLARE
3 goal vec2 := start;
4 cur vec2 := start;
5 dir vec2;
6 result vec2[] := ARRAY[] :: vec2[];
7 BEGIN
8 WHILE true LOOP
109 dir := (SFERLOEMCT ddi.dreicrtions AS d, squares AS sQ1[⋅]
11 WHERE s.xy = cur
12 AND (s.ll, s.lr, s.ul, s.ur)
13 = (d.ll, d.lr, d.ul, d.ur));
14 result := result || cur;
15 cur := (cur.x + dir.x, cur.y + dir.y) :: vec2;
16 EXIT WHEN cur = goal OR dir IS NULL;
17 END LOOP;
18 RETURN result;
19 END;
20 $$ LANGUAGE PLPGSQL STRICT;
(a) Table-Valued version of PL/SQL UDF march.</p>
        <p>
          (b) Marching Squares as an array-based PL/SQL UDF.
table-valued function and implements the popular
computer graphics algorithm Marching Squares [6] in Post- ff SSA ANF ANF SQL QQff
greSQL’s PL/pgSQL. Whenever such a table-valued UDF
encounters a RETURN NEXT, the PostgreSQL interpreter iterative goto recursive trampolined style WITH RECURSIVE
adds a new result to the function’s result set before the Figure 3: Compilation stages and intermediate UDF forms.
UDF resumes execution. This, potentially sizable, return See [5] for a detailed description of this compilation.
set is materialized during execution and returned as a
whole when the function exits. start:
(seAenmaarlctehr-naartrivine Fimigpulreem2ebn)tiatetiroantivaeslay sbcuailladrs tPhLe/SrQeLsuUltDaFs cdguiorra00l0←← ← sNtUsaLtrLat;r;t; ReExTiUtR:N;
an array of type vec2[]. Our previous compilation strat- GOTO while;
egy does handle march-arr, but the resulting SQL query
will exhibit disappointing performance: the compilation wchuirl0e:← o|(start:cur0,while:cur1);
creates a recursive CTE whose iteration expresses the dir1 ← (Q1[cur0]);
iteration of the original UDF. This CTE maintains the emit cur0;
local state of all UDF variables in a single row of the cur1 ← (cur0.x + dir1.x, cur0.y + dir1.y);
CTE’s working table. For UDF march-arr, maintaining IpF1 p←1(TcHuErN1 = goal0 OR dir1 IS NULL);
the array result iteratively results in significant runtime GOTO exit;
overhead because the array has to be copied (and ex- ELSGEOTO while;
tended) in each iteration. For  iterations, this amounts
to a total of  × (1 + 2 + ... + ( − 1 )) ≡ 12  2 × ( − 1) copy
operations. In consequence, the CTE’s working table Figure 4: CFG for UDF march with code blocks in SSA form.
grows to 16 MB during the execution of the compiled
UDF march-arr. Note that this copy overhead is not spe- termediate results, and (
          <xref ref-type="bibr" rid="ref3">3</xref>
          ) still avoid PL/SQL↔SQL switch
cific to PostgreSQL. It is a consequence of (semi-)naive overhead (which adds to 20% in the case of UDF march).
evaluation of recursive CTEs. This evaluation semantics
requires all data structures to be purely functional,
meaning that they preserve previous versions of themselves 2. Trampolined Style in SQL
unchanged. It may be possible to apply ideas from purely
functional data structures [7] to database engines to
improve the performance of recursive CTEs. However, as
far as we know, no system currently does this.
        </p>
        <p>
          In what follows, we sketch how to adapt and
generalize the CTE-based PL/SQL UDF compilation to also cover
table-valued UDFs like march of Figure 2a. We aim to
(
          <xref ref-type="bibr" rid="ref1">1</xref>
          ) support the idiomatic RETURN NEXT style of UDF
authoring, (
          <xref ref-type="bibr" rid="ref2">2</xref>
          ) avoid the materialization and copying of
inComplex UDFs that express (potentially deeply nested)
looping computation and the simple iterative semantics
of SQL’s WITH RECURSIVE appear to be at odds. Yet, that
gap can be bridged.
        </p>
        <p>A program in trampolined style is organized as a single
“scheduler” loop, the so called trampoline, which manages
all control flow. Execution of such programs proceed in
discrete steps. After each step, control is returned to
the trampoline, which then proceeds to transfer control
EXIT
From PL/SQL to SQL. The compiler performs a series
of transformations (see Figure 3) to get from PL/SQL to
a plain recursive SQL query. The first step is to lower
the UDF to static single assignment (SSA) form. Any
iterative control flow is mapped to an equivalent program
with labeled basic blocks that end with either GOTO  to
pass control to the block labeled  , or RETURN. From the
resulting CFG (see Figure 4) we derive the recursive SQL
CTE run of Figure 5.
again [8]. This cycle continues until the program execu- line” and how it realizes the control flow for UDF march.
tion is finished. Using trampolined style, the program The SQL query reads the call column
is efectively transformed into a state machine. We ex- to select one block ∈ {while, exit} for
ploit the property, that only a single loop is required to evaluation. All blocks must return a start
express arbitrary control flow. This restricted form of new control row with columns "rec?"
control flow perfectly matches the semantics of SQL’s and call, so the dispatcher knows trampoline
WITH RECURSIVE construct. how to proceed in the next iteration
(see Lines 13, 16, and 21 of Figure 5). exit
This process continues until a block
returns a control row with column
"rec?"=false (see Line 21 of Figure 5).</p>
        <p>The working table in the next iteration Figure 6:
Tramwill be empty, and WITH RECURSIVE polined style.
evaluation stops.</p>
        <p>New: Data Flow Management. While scalar UDFs
return a single value in the last trampoline iteration,
tableControl Flow Management. After compilation, each valued UDFs can return any number of values during
excall to UDF march is encoded as a control row in the ecution (see emit cur0 in Figure 4). The CTE of Figure 5
working table of run. This row determines the state of encodes these returned values in dedicated rows marked
the machine, and thus which part of the computation in Line 8 of Figure 5. Two columns manage this data
to perform next. In Figure 4, each CFG construct that flow:
yields a control row is marked . It is initially created data? ∈ {true, false}: Column data? indicates if this
in the non-recursive part of run (see Line 2 of Figure 5). row has a valid return value in column res.
In the recursive part of run, the row is read, because res: Contains this return value.
two columns determine the transfer of control during We call rows with column data?=true data rows. When a
execution: UDF uses either RETURN NEXT or RETURN QUERY, such data
rec? ∈ {true, false}: If column rec? is false, the tram- rows are created in addition to control rows.</p>
        <p>
          poline will stop calculating and return. Given the UDF of Figure 2a and assuming a call
call ∈ {while, exit}: Otherwise, column call specifies march((
          <xref ref-type="bibr" rid="ref7 ref8">8,7</xref>
          )), overall the recursive CTE computes
tathe block to jump to. When the block is finished, it ble run as shown on the next page. After the
initialreturns a control row to the trampoline with new rec? ization, marked , each iteration (separated by )
genand call values. erates two rows, a data row and a control row. (In
We call these rows control rows. The recursive part of general, any number of data rows can be created in
run in Lines 4 to 23 of Figure 5 implements a dispatcher. each iteration.) Note how the last iteration indicates
Figure 6 depicts the central role of the dispatcher “trampo- the end of execution via (rec?,data?)=(false,false).
¬ rec?
control
while
        </p>
        <p>data
emit cur</p>
        <p>Return
Type</p>
        <p>Overhead</p>
        <p>Runtime</p>
        <p>Memory
march-arr vec2[]</p>
        <p>march SETOF vec2</p>
      </sec>
    </sec>
    <sec id="sec-2">
      <title>4. Wrap-Up</title>
      <p>
        run Recall that the
rec? data? call res cur original PL/SQL UDF
true false while NULL (
        <xref ref-type="bibr" rid="ref7 ref8">8,7</xref>
        ) has to materialize
false true NULL (
        <xref ref-type="bibr" rid="ref7 ref8">8,7</xref>
        ) NULL its table-valued
true false while NULL (
        <xref ref-type="bibr" rid="ref7">9,7</xref>
        ) result during
exefalse true NULL (
        <xref ref-type="bibr" rid="ref7">9,7</xref>
        ) NULL cution, and returns
false true NULL (
        <xref ref-type="bibr" rid="ref8 ref8">8,8</xref>
        ) NULL all of it as a whole.
ftarlusee ffaallssee eNxUiLtL NNUULLLL (N8U,L8L) This
materializa
      </p>
      <p>
        tion prevents the
surrounding execution plan from terminating
prematurely, for example, when a LIMIT clause is used:
SELECT * FROM march((
        <xref ref-type="bibr" rid="ref7 ref8">8,7</xref>
        )) LIMIT 5. After compilation,
however, these results are immediately returned to
the parent operator in terms of data rows, without
materialization of the entire result. This saves memory
and reduces the runtime. In addition, metrics such
as CPU cost and cardinalities can be estimated more
accurately, making planning of the translation more
efective: While PL/SQL UDFs are efectively a black box
for the planner, the translation is a regular SQL query
that the planner is designed to handle.
      </p>
      <p>Separating the concepts of data rows and control rows is
essential for translating table-valued functions. We save
working table space and are rewarded with a significant
runtime advantage over array-centric UDF alternatives.</p>
      <p>But it does not stop there. In the future, we plan to
generalize this concept to add support for recursion in
UDFs. Data rows could be used to model call stack
entries, which—in one form or another—are required for
functions that are not tail-recursive.</p>
      <p>A further generalization would be to remove the
restriction that one control row always yields exactly one
new control row. This property causes trampolined style
to model single-threaded computation. If the SQL
back3. Data Rows in Trampolined Style end supports parallel plan execution, the creation of
multiple control rows in a single iteration efectively spawns
Both UDFs, march and march-arr, indeed exhibit the in- independent threads. UDF evaluation would benefit from
famous context switching overhead that gives PL/SQL its parallelization just like regular SQL queries.
bad reputation. We have measured that the back and
forth between PL/SQL and SQL accounts for 20% of the
overall evaluation time for both variants (see Table 1). References
The compilation to recursive SQL CTEs described in [5]
avoids this particular overhead for the two UDFs.</p>
      <p>However, the naive treatment of the iterative result
array construction and copying in the CTE for the
scalar UDF march-arr quickly eats up all the gains: the
quadratic array maintenance cost mentioned in the
introduction add up to about 50% of the overall CTE runtime.</p>
      <p>If we double the size of UDF input, the working table of
the CTE for march-arr grows by a factor of four (from
16 MB to 64 MB) and the array maintenance overhead
increases to 56%. Ultimately, this leads to a slowdown of
march-arr after compilation.</p>
      <p>In stark contrast, the control- and data-flow-aware
compilation strategy sketched in Section 2, translates the
table-valued UDF into the recursive SQL CTE of Figure 5.</p>
      <p>Array construction and copying is avoided altogether
and working table size remains small: doubling the UDF
input size—and thus the number of iterations performed—
linearly grows the working table’s size from 110 kB to a
mere 220 kB. Overall, compilation of UDF march leads to
runtime reduction of 62% (i.e., post-compilation the UDF
runs about 2.6 times faster). In addition, materialization
is entirely avoided: the CTE of Figure 5 can stream the
rows of the resulting table to the downstream plan.</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>K.</given-names>
            <surname>Ramachandra</surname>
          </string-name>
          ,
          <string-name>
            <given-names>K.</given-names>
            <surname>Park</surname>
          </string-name>
          ,
          <string-name>
            <given-names>K.</given-names>
            <surname>Emani</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Halverson</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Galindo-Legaria</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Cunningham</surname>
          </string-name>
          ,
          <article-title>Froid: Optimization of Imperative Programs in a Relational Database</article-title>
          ,
          <source>Proc. VLDB</source>
          <volume>11</volume>
          (
          <year>2018</year>
          ).
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>J.</given-names>
            <surname>Harris</surname>
          </string-name>
          ,
          <string-name>
            <surname>A (Not So) Brief But (Very) Accurate History of</surname>
            <given-names>PL</given-names>
          </string-name>
          /SQL,
          <year>2020</year>
          . http://oracle-internals .com/blog/2020/04/29/a
          <article-title>-not-so-brief-but -very-accurate-history-of-pl-sql/.</article-title>
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>V.</given-names>
            <surname>Simhadri</surname>
          </string-name>
          ,
          <string-name>
            <given-names>K.</given-names>
            <surname>Ramachandra</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Chaitanya</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R.</given-names>
            <surname>Guravannavar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Sudarshan</surname>
          </string-name>
          ,
          <article-title>Decorrelation of user defined function invocations in queries</article-title>
          ,
          <source>in: 2014 IEEE 30th ICDE</source>
          ,
          <year>2014</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [4]
          <string-name>
            <given-names>S.</given-names>
            <surname>Gupta</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Purandare</surname>
          </string-name>
          ,
          <string-name>
            <given-names>K.</given-names>
            <surname>Ramachandra</surname>
          </string-name>
          ,
          <article-title>Aggify: Lifting the Curse of Cursor Loops using Custom Aggregates</article-title>
          ,
          <source>in: Proc. SIGMOD</source>
          ,
          <year>2020</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>D.</given-names>
            <surname>Hirn</surname>
          </string-name>
          , T. Grust,
          <article-title>One WITH RECURSIVE is Worth Many GOTOs</article-title>
          ,
          <source>in: Proc. SIGMOD</source>
          ,
          <year>2021</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>C.</given-names>
            <surname>Maple</surname>
          </string-name>
          ,
          <article-title>Geometric design and space planning using the marching squares and marching cube algorithms</article-title>
          ,
          <source>in: Proc. GMAG</source>
          <year>2003</year>
          ,
          <year>2003</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>C.</given-names>
            <surname>Okasaki</surname>
          </string-name>
          ,
          <source>Purely Functional Data Structures</source>
          , Cambridge University Press, USA,
          <year>1998</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>S. E.</given-names>
            <surname>Ganz</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D. P.</given-names>
            <surname>Friedman</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Wand</surname>
          </string-name>
          ,
          <article-title>Trampolined style</article-title>
          ,
          <source>in: Proceedings of the fourth ACM SIGPLAN ICFP</source>
          ,
          <year>1999</year>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>