Expressions

The types of expressions in PL/CSQL are summarized by the following syntax:

<expression> ::=
      <literal>                                 # literal
    | <identifier>                              # identifier
    | SQL%ROWCOUNT                              # result size of Static SQL
    | <cursor_expression> <cursor_attribute>    # cursor attribute
    | <expression> <binary_op> <expression>     # binary operation
    | <unary_op> <expression>                   # unary operation
    | ( <expression> )                          # parentheses
    | <identifier>.<identifier>                 # record field reference
    | <identifier> <function_argument>          # function call
    | <case_expression>                         # CASE expression
    | SQLCODE                                   # exception code
    | SQLERRM                                   # exception message
    | <expression> IS [ NOT ] NULL              # IS NULL expression
    | <expression> [ NOT ] BETWEEN <expression> AND <expression>        # BETWEEN expression
    | <expression> [ NOT ] IN ( <expression> [ , <expression>, ... ] )  # IN expression
    | <expression> [ NOT ] LIKE <expression> [ ESCAPE <expression> ]    # LIKE expression

<literal> ::=
      DATE <quoted_string>
    | TIME <quoted_string>
    | DATETIME <quoted_string>
    | TIMESTAMP <quoted_string>
    | <numeric>
    | <quoted_string>
    | NULL
    | TRUE
    | FALSE

<numeric> ::= UNSIGNED_INTEGER | FLOATING_POINT_NUM

<cursor_attribute> ::= { %ISOPEN | %FOUND | %NOTFOUND | %ROWCOUNT }

<binary_op> ::=
      AND | XOR | OR
    | = | <=> | != | <> | <= | >= | < | >
    | * | / | + | -
    | >> | << | & | ^ | '|'
    | ||

<unary_op> ::= + | - | NOT | ~

<case_expression> ::=
      CASE <expression> <case_expression_when_part>... [ ELSE <expression> ] END
    | CASE <case_expression_when_part>... [ ELSE <expression> ] END

<case_expression_when_part> ::= WHEN <expression> THEN <expression>

Literals

Literals include date/time, numeric, string, NULL, TRUE, and FALSE values. Except that bit strings and collections are not supported, literal-related rules are the same as described in SQL Literals.

Identifiers

In PL/CSQL statements other than Static/Dynamic SQL, the following kinds of identifiers can be used:

  • Variables, constants, cursors, exceptions, and local procedures/functions declared in the declaration section

  • Parameters of procedures/functions

  • Implicitly declared iterators of FOR loops of type integer or record

Using an identifier without explicit or implicit declaration results in a compile-time error.

Result Count for Static SQL

SQL%ROWCOUNT is an expression that represents the result count immediately after executing a Static SQL statement.

  • For SELECT statements not associated with cursors, the INTO clause must be used, and the result must contain exactly one row. Therefore, when the SELECT executes successfully, SQL%ROWCOUNT will be 1. If the result has 0 or more than 1 row and causes a runtime error, the value of SQL%ROWCOUNT is undefined.

  • For INSERT, UPDATE, DELETE, MERGE, REPLACE, and TRUNCATE statements, it returns the number of affected rows.

  • For COMMIT and ROLLBACK, it returns 0.

Cursor Attributes

By appending %ISOPEN, %FOUND, %NOTFOUND, or %ROWCOUNT to a cursor_expression that evaluates to a cursor or SYS_REFCURSOR variable, you can check one of four cursor attributes.

  • %ISOPEN: Whether the cursor is open (BOOLEAN)

  • %FOUND: NULL before the first FETCH. Otherwise, whether the last FETCH returned 1 row (BOOLEAN). Raises INVALID_CURSOR Exception if the cursor is not open.

  • %NOTFOUND: NULL before the first FETCH. Otherwise, whether the last FETCH returned 0 rows (BOOLEAN). Raises INVALID_CURSOR Exception if the cursor is not open.

  • %ROWCOUNT: 0 before the first FETCH. Otherwise, the number of rows fetched so far (BIGINT). Raises INVALID_CURSOR Exception if the cursor is not open.

In the example below, the internal function iterate_cursor() iterates over records using cursor attributes and returns the total row count. If the passed cursor is not open (%ISOPEN is False), the function returns -1. Whether there are more records is determined using the %NOTFOUND attribute after FETCH. The %ROWCOUNT attribute increments with each FETCH and reflects the total number of rows fetched after the loop ends.

CREATE OR REPLACE PROCEDURE cursor_attributes AS
    ...

    FUNCTION iterate_cursor(rc SYS_REFCURSOR) RETURN INT
    AS
        v VARCHAR;
    BEGIN
        IF rc%ISOPEN THEN
            LOOP
                FETCH rc INTO v;
                EXIT WHEN rc%NOTFOUND;

                -- do something with v
                ...

            END LOOP;

            RETURN rc%ROWCOUNT;     -- number of records
        ELSE
            RETURN -1;              -- error
        END IF;
    END;
begin
    ...

end;

If the cursor_expression evaluates to NULL, an INVALID_CURSOR Exception is raised.

Binary Ops, Unary Ops, Parentheses

PL/CSQL uses the following precedence for operators:

Operator

Description

+, -, ~

Unary sign, bitwise NOT

*, /, DIV, MOD

Multiplication, division, integer division, modulus

+, -

Addition, subtraction

||

String concatenation

<<, >>

Bitwise shift

&

Bitwise AND

^

Bitwise XOR

|

Bitwise OR

IS NULL

NULL test

LIKE

String pattern test

BETWEEN

Range test

IN

Inclusion test

=, <=>, <, >, <=, >=, <>, !=

Comparison

NOT

Logical NOT

AND

Logical AND

XOR

Logical XOR

OR

Logical OR

  • In Non-static/dynamic SQL statements, the % operator cannot be used for modulo, so use MOD instead.

  • In Non-static/dynamic SQL statements, logical operators &&, ||, and ! cannot be used, so use AND, OR, and NOT instead.

  • In Non-static/dynamic SQL statements, string comparisons follow UTF8 encoding regardless of DB settings, using lexicographical order of Unicode values. In Static/Dynamic SQL statements, encoding and comparison rules follow the database and table settings.

Parentheses can be used to explicitly specify evaluation precedence.

Record Field Reference

PL/CSQL supports record variables in the following two cases:

  • Implicitly declared record variables used for iterating over SELECT results in a FOR loop

  • Record variables declared using %ROWTYPE

To reference a field in a record variable, append a dot and the field name to the record variable.

CREATE PROCEDURE athlete_history(p_name VARCHAR)
AS
    CURSOR my_cursor IS
    SELECT host_year, score
    FROM history
    WHERE athlete = p_name;
BEGIN
    FOR r IN my_cursor LOOP     -- r: implicitly declared
        DBMS_OUTPUT.put_line('host_year: ' || r.host_year || ' score: ' || r.score);    -- r.<column-name>
    END LOOP;
END;

Function Call

In a function call expression, the number and types of arguments must match the function’s declaration. Arguments passed to OUT parameters of the function must be assignable variables or other OUT parameters, as their values will be changed by the function call.

CREATE OR REPLACE FUNCTION callee(o OUT INT) RETURN INT
AS
BEGIN
    ...
END;

CREATE OR REPLACE PROCEDURE proc(i INT, o OUT INT)
AS
    v INT;
    c CONSTANT INT := 0;
BEGIN
    ... callee(i) ...   -- Error: IN argument
    ... callee(o) ...   -- OK: OUT argument
    ... callee(v) ...   -- OK: variable
    ... callee(c) ...   -- Error: constant
END;

Callable functions may be stored functions, local functions, or built-in functions. Among these, built-in functions refer to the CUBRID built-in functions listed in Operators and Functions. However, the built-in function IF cannot be used because it conflicts with PL/CSQL syntax.

If an error occurs while calling another stored function or built-in function, an SQL_ERROR Exception is raised.

CASE Expression

A CASE expression sequentially evaluates conditions and returns the value associated with the first condition that is satisfied.

Like the CASE statement, CASE expressions come in two forms:

  • The first form includes an expression immediately after the CASE keyword. That expression is evaluated once, then compared against each WHEN clause’s expression. When a match is found, the corresponding THEN expression is evaluated and becomes the result of the CASE expression.

  • The second form does not have an expression after the CASE keyword. In this case, each WHEN clause must be a BOOLEAN expression. The first one that evaluates to TRUE determines which THEN expression is returned as the CASE result.

Both forms may include an optional ELSE clause, whose expression becomes the CASE value if no conditions match. If no conditions match and no ELSE is present, the CASE expression evaluates to NULL.

The following is an example of the first form:

CREATE OR REPLACE PROCEDURE print_even_odd(i INTEGER)
AS
    s VARCHAR(5);
BEGIN
    s := CASE i MOD 2
        WHEN 0 THEN 'Even'
        WHEN 1 THEN 'Odd'
        ELSE 'NULL'
    END;

    DBMS_OUTPUT.put_line(s);
END;

The following shows the second form with equivalent behavior:

CREATE OR REPLACE PROCEDURE print_even_odd(i INTEGER)
AS
    s VARCHAR(5);
BEGIN
    s := CASE
        WHEN i MOD 2 = 0 THEN 'Even'
        WHEN i MOD 2 = 1 THEN 'Odd'
        ELSE 'NULL'
    END;

    DBMS_OUTPUT.put_line(s);
END;

SQLCODE, SQLERRM

Inside an exception handling block, SQLCODE and SQLERRM represent the error code (INTEGER) and error message (STRING) of the current Exception. Outside an exception block, SQLCODE is 0 and SQLERRM is ‘no error’.

CREATE OR REPLACE PROCEDURE test_sql_code_errm
AS
BEGIN
    ...
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.put_line('code=' || SQLCODE);
        DBMS_OUTPUT.put_line('error message' || SQLERRM);
END;