7.6. Writing the Body Code

    Colon Marker (‘:’)

    The colon marker prefix (‘:’) is used in PSQL to mark a reference to a variable in a DML statement. The colon marker is not required before variable names in other code and it should never be applied to context variables.

    Used for

    Assigning a value to a variable

    Available in

    PSQL

    Syntax

    PSQL uses the equivalence symbol (‘=’) as its assignment operator. The assignment statement assigns an SQL expression value on the right to the variable on the left of the operator. The expression can be any valid SQL expression: it may contain literals, internal variable names, arithmetic, logical and string operations, calls to internal functions or to external functions (UDFs).

    Example using assignment statements

    1. CREATE PROCEDURE MYPROC (
    2. a INTEGER,
    3. b INTEGER,
    4. name VARCHAR (30)
    5. )
    6. RETURNS (
    7. c INTEGER,
    8. str VARCHAR(100))
    9. AS
    10. BEGIN
    11. -- assigning a constant
    12. c = 0;
    13. str = '';
    14. SUSPEND;
    15. -- assigning expression values
    16. c = a + b;
    17. str = name || CAST(b AS VARCHAR(10));
    18. SUSPEND;
    19. -- assigning expression value
    20. -- built by a query
    21. c = (SELECT 1 FROM rdb$database);
    22. -- assigning a value from a context variable
    23. str = CURRENT_USER;
    24. SUSPEND;
    25. END

    See also

    DECLARE VARIABLE

    7.6.2. DECLARE CURSOR

    Used for

    Declaring a named cursor

    Available in

    PSQL

    Syntax

    1. DECLARE [VARIABLE] cursorname CURSOR FOR (<select>) [FOR UPDATE]
    Table 82. DECLARE CURSOR Statement Parameters
    ArgumentDescription

    cursorname

    Cursor name

    select

    SELECT statement

    The DECLARE CURSOR …​ FOR statement binds a named cursor to the result set obtained in the SELECT statement specified in the FOR clause. In the body code, the cursor can be opened, used to walk row-by-row through the result set and closed. While the cursor is open, the code can perform positioned updates and deletes using the WHERE CURRENT OF in the UPDATE or DELETE statement.

    • The optional FOR UPDATE clause can be included in the SELECT statement but its absence does not prevent successful execution of a positioned update or delete

    • Care should be taken to ensure that the names of declared cursors do not conflict with any names used subsequently in statements for AS CURSOR clauses

    • If the cursor is needed only to walk the result set, it is nearly always easier and less error-prone to use a FOR SELECT statement with the AS CURSOR clause. Declared cursors must be explicitly opened, used to fetch data and closed. The context variable ROW_COUNT has to be checked after each fetch and, if its value is zero, the loop has to be terminated. A FOR SELECT statement checks it automatically.

      Nevertheless, declared cursors provide a high level of control over sequential events and allow several cursors to be managed in parallel.

    • The SELECT statement may contain parameters. For instance:

      1. SELECT NAME || :SFX FROM NAMES WHERE NUMBER = :NUM

      Each parameter has to have been declared beforehand as a PSQL variable, even if they originate as input and output parameters. When the cursor is opened, the parameter is assigned the current value of the variable.

    Attention!

    If the value of a PSQL variable used in the SELECT statement changes during the loop, its new value may (but not always) be used for the remaining rows. It is better to avoid having such situations arise unintentionally. If you really need this behaviour, you should test your code carefully to be certain that you know exactly how changes in the variable affect the result.

    Note particularly that the behaviour may depend on the query plan, specifically on the indexes being used. No strict rules are in place for situations like this currently, but that could change in future versions of Firebird.

    Examples Using Named Cursors

    1. Declaring a named cursor in the trigger.

      1. CREATE OR ALTER TRIGGER TBU_STOCK
      2. BEFORE UPDATE ON STOCK
      3. AS
      4. DECLARE C_COUNTRY CURSOR FOR (
      5. SELECT
      6. COUNTRY,
      7. CAPITAL
      8. FROM COUNTRY
      9. );
      10. BEGIN
      11. /* PSQL statements */
      12. END
    2. A collection of scripts for creating views with a PSQL block using named cursors.

      1. EXECUTE BLOCK
      2. RETURNS (
      3. SCRIPT BLOB SUB_TYPE TEXT)
      4. AS
      5. DECLARE VARIABLE FIELDS VARCHAR(8191);
      6. DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME;
      7. DECLARE VARIABLE RELATION RDB$RELATION_NAME;
      8. DECLARE VARIABLE SOURCE TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE;
      9. DECLARE VARIABLE CUR_R CURSOR FOR (
      10. SELECT
      11. RDB$RELATION_NAME,
      12. RDB$VIEW_SOURCE
      13. FROM
      14. RDB$RELATIONS
      15. WHERE
      16. RDB$VIEW_SOURCE IS NOT NULL);
      17. -- Declaring a named cursor where
      18. -- a local variable is used
      19. DECLARE CUR_F CURSOR FOR (
      20. SELECT
      21. RDB$FIELD_NAME
      22. FROM
      23. RDB$RELATION_FIELDS
      24. WHERE
      25. -- It is important that the variable must be declared earlier
      26. RDB$RELATION_NAME = :RELATION);
      27. BEGIN
      28. OPEN CUR_R;
      29. WHILE (1 = 1) DO
      30. BEGIN
      31. FETCH CUR_R
      32. INTO :RELATION, :SOURCE;
      33. IF (ROW_COUNT = 0) THEN
      34. LEAVE;
      35. FIELDS = NULL;
      36. -- The CUR_F cursor will use the value
      37. -- of the RELATION variable initiated above
      38. OPEN CUR_F;
      39. WHILE (1 = 1) DO
      40. BEGIN
      41. FETCH CUR_F
      42. INTO :FIELD_NAME;
      43. IF (ROW_COUNT = 0) THEN
      44. LEAVE;
      45. IF (FIELDS IS NULL) THEN
      46. FIELDS = TRIM(FIELD_NAME);
      47. ELSE
      48. FIELDS = FIELDS || ', ' || TRIM(FIELD_NAME);
      49. END
      50. CLOSE CUR_F;
      51. SCRIPT = 'CREATE VIEW ' || RELATION;
      52. IF (FIELDS IS NOT NULL) THEN
      53. SCRIPT = SCRIPT || ' (' || FIELDS || ')';
      54. SCRIPT = SCRIPT || ' AS ' || ASCII_CHAR(13);
      55. SCRIPT = SCRIPT || SOURCE;
      56. SUSPEND;
      57. END
      58. CLOSE CUR_R;
      59. END

    See also

    , FETCH,

    7.6.3. DECLARE VARIABLE

    Used for

    Declaring a local variable

    Available in

    PSQL

    Syntax

    1. DECLARE [VARIABLE] varname
    2. {<datatype> | domain | TYPE OF {domain | COLUMN rel.col}
    3. [NOT NULL] [CHARACTER SET charset] [COLLATE collation]
    4. [{DEFAULT | = } <initvalue>];
    5. <datatype> ::=
    6. {SMALLINT | INTEGER | BIGINT}
    7. | {FLOAT | DOUBLE PRECISION}
    8. | {DATE | TIME | TIMESTAMP}
    9. | {DECIMAL | NUMERIC} [(precision [, scale])]
    10. | {CHAR | CHARACTER [VARYING] | VARCHAR} [(size)]
    11. [CHARACTER SET charset]
    12. | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING]
    13. [(size)]
    14. | BLOB [SUB_TYPE {subtype_num | subtype_name}]
    15. [SEGMENT SIZE seglen] [CHARACTER SET charset]
    16. | BLOB [(seglen [, subtype_num])]
    17. <initvalue> ::= <literal> | <context_var>
    Table 83. DECLARE VARIABLE Statement Parameters
    ArgumentDescription

    varname

    Name of the local variable

    datatype

    An SQL data type

    domain

    The name of an existing domain in this database

    rel.col

    Relation name (table or view) in this database and the name of a column in that relation

    precision

    Precision. From 1 to 18

    scale

    Scale. From 0 to 18, it must be less than or equal to precision

    size

    The maximum size of a string in characters

    subtype_num

    BLOB subtype number

    subtype_name

    BLOB subtype mnemonic name

    seglen

    Segment size, not greater than 65,535

    initvalue

    Initial value for this variable

    literal

    Literal of a type compatible with the type of the local variable

    context_var

    Any context variable whose type is compatible with the type of the local variable

    charset

    Character set

    collation

    Collation sequence

    The statement DECLARE [VARIABLE] is used for declaring a local variable. The keyword VARIABLE can be omitted. One DECLARE [VARIABLE] statement is required for each local variable. Any number of DECLARE [VARIABLE] statements can be included and in any order. The name of a local variable must be unique among the names of local variables and input and output parameters declared for the module.

    Data Type for Variables

    A local variable can be of any SQL type other than an array.

    • A domain name can be specified as the type and the variable will inherit all of its attributes.

    • If the TYPE OF *domain* clause is used instead, the variable will inherit only the domain’s data type, and, if applicable, its character set and collation attributes. Any default value or constraints such as NOT NULL or CHECK constraints are not inherited.

    • If the TYPE OF COLUMN *relation*.*column*> option is used to “borrow” from a column in a table or view, the variable will inherit only the column’s data type, and, if applicable, its character set and collation attributes. Any other attributes are ignored.

    NOT NULL Constraint

    The variable can be constrained NOT NULL if required. If a domain has been specified as the data type and already carries the NOT NULL constraint, it will not be necessary. With the other forms, including use of a domain that is nullable, the NOT NULL attribute should be included if needed.

    CHARACTER SET and COLLATE clauses

    Unless specified, the character set and collation sequence of a string variable will be the database defaults. A CHARACTER SET clause can be included, if required, to handle string data that is going to be in a different character set. A valid collation sequence (COLLATE clause) can also be included, with or without the character set clause.

    Local variables are NULL when execution of the module begins. They can be initialized so that a starting or default value is available when they are first referenced. The DEFAULT <initvalue> form can be used, or just the assignment operator, ‘=’: = <initvalue>. The value can be any type-compatible literal or context variable.

    Be sure to use this clause for any variables that are constrained to be NOT NULL and do not otherwise have a default value available.

    Examples of various ways to declare local variables

    1. CREATE OR ALTER PROCEDURE SOME_PROC
    2. AS
    3. -- Declaring a variable of the INT type
    4. DECLARE I INT;
    5. -- Declaring a variable of the INT type that does not allow NULL
    6. DECLARE VARIABLE J INT NOT NULL;
    7. -- Declaring a variable of the INT type with the default value of 0
    8. DECLARE VARIABLE K INT DEFAULT 0;
    9. -- Declaring a variable of the INT type with the default value of 1
    10. DECLARE VARIABLE L INT = 1;
    11. -- Declaring a variable based on the COUNTRYNAME domain
    12. DECLARE FARM_COUNTRY COUNTRYNAME;
    13. -- Declaring a variable of the type equal to the COUNTRYNAME domain
    14. DECLARE FROM_COUNTRY TYPE OF COUNTRYNAME;
    15. -- Declaring a variable with the type of the CAPITAL column in the COUNTRY table
    16. DECLARE CAPITAL TYPE OF COLUMN COUNTRY.CAPITAL;
    17. BEGIN
    18. /* PSQL statements */
    19. END

    See also

    , Custom Data Types — Domains,

    7.6.4. BEGIN …​ END

    Used for

    Delineating a block of statements

    Available in

    PSQL

    Syntax

    1. <block> ::=
    2. BEGIN
    3. [<compound_statement>
    4. …]
    5. END
    6. <compound_statement> ::= {<block> | <statement>;}

    The BEGIN …​ END construct is a two-part statement that wraps a block of statements that are executed as one unit of code. Each block starts with the half-statement BEGIN and ends with the other half-statement END. Blocks can be nested to unlimited depth. They may be empty, allowing them to act as stubs, without the need to write dummy statements.

    The BEGIN and END statements have no line terminators. However, when defining or altering a PSQL module in the isql utility, that application requires that the last END statement be followed by its own terminator character, that was previously switched, using SET TERM, to some string other than a semicolon. That terminator is not part of the PSQL syntax.

    The final, or outermost, END statement in a trigger terminates the trigger. What the final END statement does in a stored procedure depends on the type of procedure:

    • In a selectable procedure, the final END statement returns control to the caller, returning SQLCODE 100, indicating that there are no more rows to retrieve

    • In an executable procedure, the final END statement returns control to the caller, along with the current values of any output parameters defined.

    Example

    A sample procedure from the employee.fdb database, showing simple usage of BEGIN…​END blocks:

    1. SET TERM ^;
    2. CREATE OR ALTER PROCEDURE DEPT_BUDGET (
    3. DNO CHAR(3))
    4. RETURNS (
    5. TOT DECIMAL(12,2))
    6. AS
    7. DECLARE VARIABLE SUMB DECIMAL(12,2);
    8. DECLARE VARIABLE RDNO CHAR(3);
    9. DECLARE VARIABLE CNT INTEGER;
    10. BEGIN
    11. TOT = 0;
    12. SELECT
    13. BUDGET
    14. FROM
    15. DEPARTMENT
    16. WHERE DEPT_NO = :DNO
    17. INTO :TOT;
    18. SELECT
    19. COUNT(BUDGET)
    20. FROM
    21. DEPARTMENT
    22. WHERE HEAD_DEPT = :DNO
    23. INTO :CNT;
    24. IF (CNT = 0) THEN
    25. SUSPEND;
    26. FOR
    27. SELECT
    28. DEPT_NO
    29. FROM
    30. DEPARTMENT
    31. WHERE HEAD_DEPT = :DNO
    32. INTO :RDNO
    33. DO
    34. BEGIN
    35. EXECUTE PROCEDURE DEPT_BUDGET(:RDNO)
    36. RETURNING_VALUES :SUMB;
    37. TOT = TOT + SUMB;
    38. END
    39. SUSPEND;
    40. SET TERM ;^

    See also

    , LEAVE,

    7.6.5. IF …​ THEN …​ ELSE

    Used for

    Conditional jumps

    Available in

    PSQL

    Syntax

    1. IF (<condition>)
    2. THEN <compound_statement>
    3. [ELSE <compound_statement>]
    Table 84. IF …​ THEN …​ ELSE Parameters
    ArgumentDescription

    condition

    A logical condition returning TRUE, FALSE or UNKNOWN

    single_statement

    A single statement terminated with a semicolon

    compound_statement

    Two or more statements wrapped in BEGIN …​ END

    The conditional jump statement IF …​ THEN is used to branch the execution process in a PSQL module. The condition is always enclosed in parentheses. If it returns the value TRUE, execution branches to the statement or the block of statements after the keyword THEN. If an ELSE is present and the condition returns FALSE or UNKNOWN, execution branches to the statement or the block of statements after it.

    Multi-branch Jumps

    PSQL does not provide multi-branch jumps, such as CASE or SWITCH. Nevertheless, the CASE search statement from DSQL is available in PSQL and is able to satisfy at least some use cases in the manner of a switch:

    1. CASE <test_expr>
    2. WHEN <expr> THEN <result>
    3. [WHEN <expr> THEN <result> ...]
    4. [ELSE <defaultresult>]
    5. END
    6. CASE
    7. WHEN <bool_expr> THEN <result>
    8. [WHEN <bool_expr> THEN <result> ...]
    9. [ELSE <defaultresult>]
    10. END

    Example in PSQL

    1. ...
    2. C = CASE
    3. WHEN A=1 THEN 3
    4. ELSE 0
    5. END;
    6. ...

    Example

    An example using the IF statement. Assume that the FIRST, LINE2 and LAST variables were declared earlier.

    1. ...
    2. IF (FIRST IS NOT NULL) THEN
    3. LINE2 = FIRST || ' ' || LAST;
    4. ELSE
    5. LINE2 = LAST;
    6. ...

    See also

    , CASE

    Used for

    Looping constructs

    PSQL

    Syntax

    1. WHILE <condition> DO
    2. <compound_statement>

    A WHILE statement implements the looping construct in PSQL. The statement or the block of statements will be executed until the condition returns TRUE. Loops can be nested to any depth.

    Example

    A procedure calculating the sum of numbers from 1 to I shows how the looping construct is used.

    Executing the procedure in isql:

    1. EXECUTE PROCEDURE SUM_INT(4);

    the result is:

    1. S
    2. ==========
    3. 10

    See also

    , LEAVE, , FOR SELECT,

    7.6.7. LEAVE

    Used for

    Terminating a loop

    Available in

    PSQL

    Syntax

    1. [label:]
    2. <loop_stmt>
    3. BEGIN
    4. ...
    5. LEAVE [label];
    6. ...
    7. END
    8. <loop_stmt> ::=
    9. FOR <select_stmt> INTO <var_list> DO
    10. | FOR EXECUTE STATEMENT ... INTO <var_list> DO
    11. | WHILE (<condition>)} DO
    Table 86. LEAVE Statement Parameters
    ArgumentDescription

    label

    Label

    select_stmt

    SELECT statement

    condition

    A logical condition returning TRUE, FALSE or UNKNOWN

    A LEAVE statement immediately terminates the inner loop of a WHILE or FOR looping statement. The label parameter is optional.

    LEAVE can cause an exit from outer loops as well. Code continues to be executed from the first statement after the termination of the outer loop block.

    Examples

    1. Leaving a loop if an error occurs on an insert into the NUMBERS table. The code continues to be executed from the line C = 0.

      1. ...
      2. WHILE (B < 10) DO
      3. BEGIN
      4. INSERT INTO NUMBERS(B)
      5. VALUES (:B);
      6. B = B + 1;
      7. WHEN ANY DO
      8. BEGIN
      9. EXECUTE PROCEDURE LOG_ERROR (
      10. CURRENT_TIMESTAMP,
      11. 'ERROR IN B LOOP');
      12. LEAVE;
      13. END
      14. END
      15. C = 0;
      16. ...
    2. An example using labels in the LEAVE statement. LEAVE LOOPA terminates the outer loop and LEAVE LOOPB terminates the inner loop. Note that the plain LEAVE statement would be enough to terminate the inner loop.

      1. ...
      2. STMT1 = 'SELECT NAME FROM FARMS';
      3. LOOPA:
      4. FOR EXECUTE STATEMENT :STMT1
      5. INTO :FARM DO
      6. BEGIN
      7. STMT2 = 'SELECT NAME ' || 'FROM ANIMALS WHERE FARM = ''';
      8. LOOPB:
      9. FOR EXECUTE STATEMENT :STMT2 || :FARM || ''''
      10. INTO :ANIMAL DO
      11. BEGIN
      12. IF (ANIMAL = 'FLUFFY') THEN
      13. LEAVE LOOPB;
      14. ELSE IF (ANIMAL = FARM) THEN
      15. LEAVE LOOPA;
      16. ELSE
      17. SUSPEND;
      18. END
      19. END
      20. ...

    See also

    7.6.8. EXIT

    Used for

    Terminating module execution

    Available in

    PSQL

    Syntax

    1. EXIT

    The EXIT statement causes execution of the procedure or trigger to jump to the final END statement from any point in the code, thus terminating the program.

    Example

    Using the EXIT statement in a selectable procedure:

    1. CREATE PROCEDURE GEN_100
    2. RETURNS (
    3. I INTEGER
    4. )
    5. AS
    6. BEGIN
    7. I = 1;
    8. WHILE (1=1) DO
    9. BEGIN
    10. SUSPEND;
    11. IF (I=100) THEN
    12. EXIT;
    13. I = I + 1;
    14. END
    15. END

    See also

    , SUSPEND

    7.6.9. SUSPEND

    Used for

    Passing output to the buffer and suspending execution while waiting for caller to fetch it

    Available in

    PSQL

    Syntax

    1. SUSPEND

    The SUSPEND statement is used in a selectable stored procedure to pass the values of output parameters to a buffer and suspend execution. Execution remains suspended until the calling application fetches the contents of the buffer. Execution resumes from the statement directly after the SUSPEND statement. In practice, this is likely to be a new iteration of a looping process.

    Important Notes
    1. Applications using interfaces that wrap the API perform the fetches from selectable procedures transparently.

    2. When a SUSPEND statement is executed in an executable stored procedure, it is the same as executing the EXIT statement, resulting in immediate termination of the procedure.

    3. SUSPEND“breaks” the atomicity of the block in which it is located. If an error occurs in a selectable procedure, statements executed after the final SUSPEND statement will be rolled back. Statements that executed before the final SUSPEND statement will not be rolled back unless the transaction is rolled back.

    Example

    Using the SUSPEND statement in a selectable procedure:

    1. CREATE PROCEDURE GEN_100
    2. RETURNS (
    3. I INTEGER
    4. )
    5. AS
    6. BEGIN
    7. I = 1;
    8. WHILE (1=1) DO
    9. BEGIN
    10. SUSPEND;
    11. IF (I=100) THEN
    12. EXIT;
    13. I = I + 1;
    14. END
    15. END

    See also

    EXIT

    7.6.10. EXECUTE STATEMENT

    Used for

    Executing dynamically created SQL statements

    Available in

    PSQL

    Syntax

    1. <execute_statement> ::= EXECUTE STATEMENT <argument>
    2. [<option> …]
    3. [INTO <variables>]
    4. <argument> ::= <paramless_stmt>
    5. | (<paramless_stmt>)
    6. | (<stmt_with_params>) (<param_values>)
    7. <param_values> ::= <named_values> | <positional_values>
    8. <named_values> ::= paramname := <value_expr>
    9. [, paramname := <value_expr> ...]
    10. <positional_values> ::= <value_expr> [, <value_expr> ...]
    11. <option> ::= WITH {AUTONOMOUS | COMMON} TRANSACTION
    12. | WITH CALLER PRIVILEGES
    13. | AS USER user
    14. | PASSWORD password
    15. | ROLE role
    16. | ON EXTERNAL [DATA SOURCE] <connect_string>
    17. <connect_string> ::= [<hostspec>] {filepath | db_alias}
    18. <hostspec> ::= <tcpip_hostspec> | <NamedPipes_hostspec>
    19. <tcpip_hostspec> ::= hostname[/port]:
    20. <NamePipes_hostspec> ::= \\hostname\
    21. <variables> ::= [:]varname [, [:]varname ...]
    Table 87. EXECUTE STATEMENT Statement Parameters
    ArgumentDescription

    paramless_stmt

    Literal string or variable containing a non-parameterized SQL query

    stmt_with_params

    Literal string or variable containing a parameterized SQL query

    paramname

    SQL query parameter name

    value_expr

    SQL expression resolving to a value

    user

    User name. It can be a string, CURRENT_USER or a string variable

    password

    Password. It can be a string or a string variable

    role

    Role. It can be a string, CURRENT_ROLE or a string variable

    connection_string

    Connection string. It can be a string or a string variable

    filepath

    Path to the primary database file

    db_alias

    Database alias

    hostname

    Computer name or IP address

    varname

    Variable

    The statement EXECUTE STATEMENT takes a string parameter and executes it as if it were a DSQL statement. If the statement returns data, it can be passed to local variables by way of an INTO clause.

    Parameterized Statements

    You can use parameters — either named or positional — in the DSQL statement string. Each parameter must be assigned a value.

    Special Rules for Parameterized Statements

    1. Named and positional parameters cannot be mixed in one query

    2. If the statement has parameters, they must be enclosed in parentheses when EXECUTE STATEMENT is called, regardless of whether they come directly as strings, as variable names or as expressions

    3. Each named parameter must be prefixed by a colon (‘:’) in the statement string itself, but not when the parameter is assigned a value

    4. Positional parameters must be assigned their values in the same order as they appear in the query text

    5. The assignment operator for parameters is the special operator “:=”, similar to the assignment operator in Pascal

    6. Each named parameter can be used in the statement more than once, but its value must be assigned only once

    7. With positional parameters, the number of assigned values must match the number of parameter placeholders (question marks) in the statement exactly

    Examples of EXECUTE STATEMENT with parameters

    With named parameters:

    1. ...
    2. DECLARE license_num VARCHAR(15);
    3. DECLARE connect_string VARCHAR (100);
    4. DECLARE stmt VARCHAR (100) =
    5. 'SELECT license
    6. FROM cars
    7. WHERE driver = :driver AND location = :loc';
    8. BEGIN
    9. ...
    10. SELECT connstr
    11. FROM databases
    12. WHERE cust_id = :id
    13. INTO connect_string;
    14. ...
    15. FOR
    16. SELECT id
    17. FROM drivers
    18. INTO current_driver
    19. DO
    20. BEGIN
    21. FOR
    22. SELECT location
    23. FROM driver_locations
    24. WHERE driver_id = :current_driver
    25. INTO current_location
    26. DO
    27. BEGIN
    28. ...
    29. EXECUTE STATEMENT (stmt)
    30. (driver := current_driver,
    31. loc := current_location)
    32. ON EXTERNAL connect_string
    33. INTO license_num;
    34. ...

    The same code with positional parameters:

    1. DECLARE license_num VARCHAR (15);
    2. DECLARE connect_string VARCHAR (100);
    3. DECLARE stmt VARCHAR (100) =
    4. 'SELECT license
    5. FROM cars
    6. WHERE driver = ? AND location = ?';
    7. BEGIN
    8. ...
    9. SELECT connstr
    10. FROM databases
    11. WHERE cust_id = :id
    12. into connect_string;
    13. ...
    14. FOR
    15. SELECT id
    16. FROM drivers
    17. INTO current_driver
    18. DO
    19. BEGIN
    20. FOR
    21. SELECT location
    22. FROM driver_locations
    23. WHERE driver_id = :current_driver
    24. INTO current_location
    25. DO
    26. BEGIN
    27. ...
    28. EXECUTE STATEMENT (stmt)
    29. (current_driver, current_location)
    30. ON EXTERNAL connect_string
    31. INTO license_num;
    32. ...

    WITH {AUTONOMOUS | COMMON} TRANSACTION

    Traditionally, the executed SQL statement always ran within the current transaction, and this is still the default. WITH AUTONOMOUS TRANSACTION causes a separate transaction to be started, with the same parameters as the current transaction. It will be committed if the statement runs to completion without errors and rolled back otherwise. WITH COMMON TRANSACTION uses the current transaction if possible.

    If the statement must run in a separate connection, an already started transaction within that connection is used, if available. Otherwise, a new transaction is started with the same parameters as the current transaction. Any new transactions started under the “COMMON” regime are committed or rolled back with the current transaction.

    WITH CALLER PRIVILEGES

    By default, the SQL statement is executed with the privileges of the current user. Specifying WITH CALLER PRIVILEGES adds to this the privileges of the calling procedure or trigger, just as if the statement were executed directly by the routine. WITH CALLER PRIVILEGES has no effect if the ON EXTERNAL clause is also present.

    With ON EXTERNAL [DATA SOURCE], the SQL statement is executed in a separate connection to the same or another database, possibly even on another server. If the connect string is NULL or “''” (empty string), the entire ON EXTERNAL [DATA SOURCE] clause is considered absent and the statement is executed against the current database.

    Connection Pooling

    • External connections made by statements WITH COMMON TRANSACTION (the default) will remain open until the current transaction ends. They can be reused by subsequent calls to EXECUTE STATEMENT, but only if the connect string is exactly the same, including case

    • External connections made by statements WITH AUTONOMOUS TRANSACTION are closed as soon as the statement has been executed

    • Notice that statements WITH AUTONOMOUS TRANSACTION can and will re-use connections that were opened earlier by statements WITH COMMON TRANSACTION. If this happens, the reused connection will be left open after the statement has been executed. (It must be, because it has at least one un-committed transaction!)

    Transaction Pooling

    • If WITH COMMON TRANSACTION is in effect, transactions will be reused as much as possible. They will be committed or rolled back together with the current transaction

    • If WITH AUTONOMOUS TRANSACTION is specified, a fresh transaction will always be started for the statement. This transaction will be committed or rolled back immediately after the statement’s execution

    Exception Handling

    When ON EXTERNAL is used, the extra connection is always made via a so-called external provider, even if the connection is to the current database. One of the consequences is that exceptions cannot be caught in the usual way. Every exception caused by the statement is wrapped in either an eds_connection or an eds_statement error. In order to catch them in your PSQL code, you have to use WHEN GDSCODE eds_connection, WHEN GDSCODE eds_statement or WHEN ANY.

    Without ON EXTERNAL, exceptions are caught in the usual way, even if an extra connection is made to the current database.

    Miscellaneous Notes

    • The character set used for the external connection is the same as that for the current connection

    • Two-phase commits are not supported

    AS USER, PASSWORD and ROLE

    The optional AS USER, PASSWORD and ROLE clauses allow specificaton of which user will execute the SQL statement and with which role. The method of user log-in and whether a separate connection is open depend on the presence and values of the ON EXTERNAL [DATA SOURCE], AS USER, PASSWORD and ROLE clauses:

    • If ON EXTERNAL is present, a new connection is always opened, and:

      • If at least one of AS USER, PASSWORD and ROLE is present, native authentication is attempted with the given parameter values (locally or remotely, depending on the connect string). No defaults are used for missing parameters

      • If all three are absent and the connect string contains no hostname, then the new connection is established on the local host with the same user and role as the current connection. The term ‘local’ means “on the same machine as the server” here. This is not necessarily the location of the client

      • If all three are absent and the connect string contains a hostname, then trusted authentication is attempted on the remote host (again, ‘remote’ from the perspective of the server). If this succeeds, the remote operating system will provide the user name (usually the operating system account under which the Firebird process runs)

    • If ON EXTERNAL is absent:

      • If at least one of AS USER, PASSWORD and ROLE is present, a new connection to the current database is opened with the suppled parameter values. No defaults are used for missing parameters

      • If all three are absent, the statement is executed within the current connection

    Notice

    If a parameter value is NULL or “‘’” (empty string), the entire parameter is considered absent. Additionally, AS USER is considered absent if its value is equal to CURRENT_USER, and ROLE if it is the same as CURRENT_ROLE.

    Caveats with EXECUTE STATEMENT

    1. There are no dependency checks to discover whether tables or columns have been dropped

    2. Even though the performance in loops has been significantly improved in Firebird 2.5, execution is still considerably slower than when the same statements are launched directly

    3. Return values are strictly checked for data type in order to avoid unpredictable type-casting exceptions. For example, the string '1234' would convert to an integer, 1234, but 'abc' would give a conversion error

    All in all, this feature is meant to be used very cautiously and you should always take the caveats into account. If you can achieve the same result with PSQL and/or DSQL, it will almost always be preferable.

    See also

    Used for

    Looping row-by-row through a selected result set

    Available in

    PSQL

    Syntax

    1. DO <compound_statement>

    A FOR SELECT statement

    • retrieves each row sequentially from the result set and executes the statement or block of statements on the row. In each iteration of the loop, the field values of the current row are copied into pre-declared variables.

      Including the AS CURSOR clause enables positioned deletes and updates to be performed — see notes below

    • can embed other statements

    • can carry named parameters that must be previously declared in the DECLARE VARIABLE statement or exist as input or output parameters of the procedure

    • requires an INTO clause that is located at the end of the SELECT …​ FROM …​ specification. In each iteration of the loop, the field values in the current row are copied to the list of variables specified in the INTO clause. The loop repeats until all rows are retrieved, after which it terminates

    • can be terminated before all rows are retrieved by using a LEAVE statement

    The Undeclared Cursor

    The optional AS CURSOR clause surfaces the set in the FOR SELECT structure as an undeclared, named cursor that can be operated on using the WHERE CURRENT OF clause inside the statement or block following the DO command, in order to delete or update the current row before execution moves to the next iteration.

    Other points to take into account regarding undeclared cursors:

    1. the OPEN, FETCH and CLOSE statements cannot be applied to a cursor surfaced by the AS CURSOR clause

    2. the cursor name argument associated with an AS CURSOR clause must not clash with any names created by DECLARE VARIABLE or DECLARE CURSOR statements at the top of the body code, nor with any other cursors surfaced by an AS CURSOR clause

    3. The optional FOR UPDATE clause in the SELECT statement is not required for a positioned update

    Examples using FOR SELECT

    1. A simple loop through query results:

      1. CREATE PROCEDURE SHOWNUMS
      2. RETURNS (
      3. AA INTEGER,
      4. BB INTEGER,
      5. SM INTEGER,
      6. DF INTEGER)
      7. AS
      8. BEGIN
      9. FOR SELECT DISTINCT A, B
      10. FROM NUMBERS
      11. ORDER BY A, B
      12. INTO AA, BB
      13. DO
      14. BEGIN
      15. SM = AA + BB;
      16. DF = AA - BB;
      17. SUSPEND;
      18. END
      19. END
    2. Nested FOR SELECT loop:

    3. Using the AS CURSOR clause to surface a cursor for the positioned delete of a record:

      1. CREATE PROCEDURE DELTOWN (
      2. TOWNTODELETE VARCHAR(24))
      3. RETURNS (
      4. TOWN VARCHAR(24),
      5. POP INTEGER)
      6. AS
      7. BEGIN
      8. FOR SELECT TOWN, POP
      9. FROM TOWNS
      10. INTO :TOWN, :POP AS CURSOR TCUR
      11. DO
      12. BEGIN
      13. IF (:TOWN = :TOWNTODELETE) THEN
      14. -- Positional delete
      15. DELETE FROM TOWNS
      16. WHERE CURRENT OF TCUR;
      17. ELSE
      18. SUSPEND;
      19. END
      20. END

    See also

    DECLARE CURSOR, , SELECT, , DELETE

    7.6.12. FOR EXECUTE STATEMENT

    Used for

    Executing dynamically created SQL statements that return a row set

    Available in

    PSQL

    Syntax

    1. FOR <execute_statement> DO <compound_statement>
    Table 89. FOR EXECUTE STATEMENT Statement Parameters
    ArgumentDescription

    execute_stmt

    An EXECUTE STATEMENT statement

    single_statement

    A single statement, terminated with a colon, that performs all the processing for this FOR loop

    compound_statement

    A block of statements wrapped in BEGIN…​END, that performs all the processing for this FOR loop

    The statement FOR EXECUTE STATEMENT is used, in a manner analogous to FOR SELECT, to loop through the result set of a dynamically executed query that returns multiple rows.

    Example

    Executing a dynamically constructed SELECT query that returns a data set:

    1. CREATE PROCEDURE DynamicSampleThree (
    2. Q_FIELD_NAME VARCHAR(100),
    3. Q_TABLE_NAME VARCHAR(100)
    4. ) RETURNS(
    5. LINE VARCHAR(32000)
    6. )
    7. AS
    8. DECLARE VARIABLE P_ONE_LINE VARCHAR(100);
    9. BEGIN
    10. LINE = '';
    11. FOR
    12. EXECUTE STATEMENT
    13. 'SELECT T1.' || :Q_FIELD_NAME ||
    14. ' FROM ' || :Q_TABLE_NAME || ' T1 '
    15. INTO :P_ONE_LINE
    16. DO
    17. IF (:P_ONE_LINE IS NOT NULL) THEN
    18. LINE = :LINE || :P_ONE_LINE || ' ';
    19. SUSPEND;
    20. END

    See also

    EXECUTE STATEMENT

    7.6.13. OPEN

    Used for

    Opening a declared cursor

    Available in

    PSQL

    Syntax

    1. OPEN cursorname
    Table 90. OPEN Statement Parameter
    ArgumentDescription

    cursorname

    Cursor name. A cursor with this name must be previously declared with a DECLARE CURSOR statement

    An OPEN statement opens a previously declared cursor, executes the SELECT statement declared for it and makes the first record of the result data set ready to fetch. OPEN can be applied only to cursors previously declared in a DECLARE VARIABLE statement.

    If the SELECT statement declared for the cursor has parameters, they must be declared as local variables or exist as input or output parameters before the cursor is declared. When the cursor is opened, the parameter is assigned the current value of the variable.

    Examples

    1. Using the OPEN statement:

      1. SET TERM ^;
      2. CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMES
      3. RETURNS (
      4. RNAME CHAR(31)
      5. )
      6. AS
      7. DECLARE C CURSOR FOR (
      8. SELECT RDB$RELATION_NAME
      9. FROM RDB$RELATIONS);
      10. BEGIN
      11. OPEN C;
      12. WHILE (1 = 1) DO
      13. BEGIN
      14. FETCH C INTO :RNAME;
      15. IF (ROW_COUNT = 0) THEN
      16. LEAVE;
      17. SUSPEND;
      18. END
      19. CLOSE C;
      20. END^
      21. SET TERM ;^
    2. A collection of scripts for creating views using a PSQL block with named cursors:

      1. EXECUTE BLOCK
      2. RETURNS (
      3. SCRIPT BLOB SUB_TYPE TEXT)
      4. AS
      5. DECLARE VARIABLE FIELDS VARCHAR(8191);
      6. DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME;
      7. DECLARE VARIABLE RELATION RDB$RELATION_NAME;
      8. DECLARE VARIABLE SOURCE TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE;
      9. -- named cursor
      10. DECLARE VARIABLE CUR_R CURSOR FOR (
      11. SELECT
      12. RDB$RELATION_NAME,
      13. RDB$VIEW_SOURCE
      14. FROM
      15. RDB$RELATIONS
      16. WHERE
      17. RDB$VIEW_SOURCE IS NOT NULL);
      18. -- named cursor with local variable
      19. DECLARE CUR_F CURSOR FOR (
      20. SELECT
      21. RDB$FIELD_NAME
      22. FROM
      23. RDB$RELATION_FIELDS
      24. WHERE
      25. -- Important! The variable shall be declared earlier
      26. RDB$RELATION_NAME = :RELATION);
      27. BEGIN
      28. OPEN CUR_R;
      29. WHILE (1 = 1) DO
      30. BEGIN
      31. FETCH CUR_R
      32. INTO :RELATION, :SOURCE;
      33. IF (ROW_COUNT = 0) THEN
      34. LEAVE;
      35. FIELDS = NULL;
      36. -- The CUR_F cursor will use
      37. -- variable value of RELATION initialized above
      38. OPEN CUR_F;
      39. WHILE (1 = 1) DO
      40. BEGIN
      41. FETCH CUR_F
      42. INTO :FIELD_NAME;
      43. IF (ROW_COUNT = 0) THEN
      44. LEAVE;
      45. IF (FIELDS IS NULL) THEN
      46. FIELDS = TRIM(FIELD_NAME);
      47. ELSE
      48. FIELDS = FIELDS || ', ' || TRIM(FIELD_NAME);
      49. END
      50. CLOSE CUR_F;
      51. SCRIPT = 'CREATE VIEW ' || RELATION;
      52. IF (FIELDS IS NOT NULL) THEN
      53. SCRIPT = SCRIPT || ' (' || FIELDS || ')';
      54. SCRIPT = SCRIPT || ' AS ' || ASCII_CHAR(13);
      55. SCRIPT = SCRIPT || SOURCE;
      56. SUSPEND;
      57. END
      58. CLOSE CUR_R;
      59. END

    See also

    DECLARE CURSOR, , CLOSE

    7.6.14. FETCH

    Used for

    Fetching successive records from a data set retrieved by a cursor

    Available in

    PSQL

    Syntax

    1. FETCH cursorname INTO [:]varname [, [:]varname ...]
    Table 91. FETCH Statement Parameters
    ArgumentDescription

    cursorname

    Cursor name. A cursor with this name must be previously declared with a DECLARE CURSOR statement and opened by an OPEN statement.

    varname

    Variable name

    A FETCH statement fetches the first and successive rows from the result set of the cursor and assigns the column values to PSQL variables. The FETCH statement can be used only with a cursor declared with the DECLARE CURSOR statement.

    The INTO clause gets data from the current row of the cursor and loads them into PSQL variables.

    For checking whether all of the the data set rows have been fetched, the context variable ROW_COUNT returns the number of rows fetched by the statement. It is positive until all rows have been checked. A ROW_COUNT of 1 indicates that the next fetch will be the last.

    Example

    Using the FETCH statement:

    1. SET TERM ^;
    2. CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMES
    3. RETURNS (
    4. RNAME CHAR(31)
    5. )
    6. AS
    7. DECLARE C CURSOR FOR (
    8. SELECT RDB$RELATION_NAME
    9. FROM RDB$RELATIONS);
    10. BEGIN
    11. OPEN C;
    12. WHILE (1 = 1) DO
    13. BEGIN
    14. FETCH C INTO :RNAME;
    15. IF (ROW_COUNT = 0) THEN
    16. LEAVE;
    17. SUSPEND;
    18. END
    19. CLOSE C;
    20. END^
    21. SET TERM ;^

    See also

    DECLARE CURSOR, , CLOSE

    7.6.15. CLOSE

    Used for

    Closing a declared cursor

    Available in

    PSQL

    Syntax

    1. CLOSE cursorname
    Table 92. CLOSE Statement Parameter
    ArgumentDescription

    cursorname

    Cursor name. A cursor with this name must be previously declared with a DECLARE CURSOR statement and opened by an OPEN statement

    A CLOSE statement closes an open cursor. Any cursors that are still open will be automatically closed after the module code completes execution. Only a cursor that was declared with DECLARE CURSOR can be closed with a CLOSE statement.

    Example

    Using the CLOSE statement:

    1. SET TERM ^;
    2. CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMES
    3. RETURNS (
    4. RNAME CHAR(31)
    5. )
    6. AS
    7. DECLARE C CURSOR FOR (
    8. SELECT RDB$RELATION_NAME
    9. FROM RDB$RELATIONS);
    10. BEGIN
    11. OPEN C;
    12. WHILE (1 = 1) DO
    13. BEGIN
    14. FETCH C INTO :RNAME;
    15. IF (ROW_COUNT = 0) THEN
    16. LEAVE;
    17. SUSPEND;
    18. END
    19. CLOSE C;
    20. END^

    See also

    DECLARE CURSOR, , FETCH

    Used for

    Executing a statement or a block of statements in an autonomous transaction

    Available in

    PSQL

    Syntax

    1. IN AUTONOMOUS TRANSACTION DO <compound_statement>

    An IN AUTONOMOUS TRANSACTION statement enables execution of a statement or a block of statements in an autonomous transaction. Code running in an autonomous transaction will be committed right after its successful execution, regardless of the status of its parent transaction. It might be needed when certain operations must not be rolled back, even if an error occurs in the parent transaction.

    An autonomous transaction has the same isolation level as its parent transaction. Any exception that is thrown in the block of the autonomous transaction code will result in the autonomous transaction being rolled back and all made changes being cancelled. If the code executes successfully, the autonomous transaction will be committed.

    Example

    Using an autonomous transaction in a trigger for the database ON CONNECT event, in order to log all connection attempts, including those that failed:

    1. CREATE TRIGGER TR_CONNECT ON CONNECT
    2. AS
    3. BEGIN
    4. -- Logging all attempts to connect to the database
    5. IN AUTONOMOUS TRANSACTION DO
    6. INSERT INTO LOG(MSG)
    7. VALUES ('USER ' || CURRENT_USER || ' CONNECTS.');
    8. IF (CURRENT_USER IN (SELECT
    9. USERNAME
    10. FROM
    11. BLOCKED_USERS)) THEN
    12. BEGIN
    13. -- Logging that the attempt to connect
    14. -- to the database failed and sending
    15. -- a message about the event
    16. IN AUTONOMOUS TRANSACTION DO
    17. BEGIN
    18. INSERT INTO LOG(MSG)
    19. VALUES ('USER ' || CURRENT_USER || ' REFUSED.');
    20. POST_EVENT 'CONNECTION ATTEMPT' || ' BY BLOCKED USER!';
    21. END
    22. -- now calling an exception
    23. EXCEPTION EX_BADUSER;
    24. END
    25. END

    See also

    7.6.17. POST_EVENT

    Used for

    Notifying listening clients about database events in a module

    Available in

    PSQL

    Syntax

    1. POST_EVENT event_name
    Table 94. POST_EVENT Statement Parameter
    ArgumentDescription

    event_name

    Event name (message) limited to 127 bytes

    The POST_EVENT statement notifies the event manager about the event, which saves it to an event table. When the transaction is committed, the event manager notifies applications that are signalling their interest in the event.

    The event name can be some sort of code or a short message: the choice is open as it is just a string up to 127 bytes.

    The content of the string can be a string literal, a variable or any valid SQL expression that resolves to a string.

    Example

    1. SET TERM ^;
    2. CREATE TRIGGER POST_NEW_ORDER FOR SALES
    3. ACTIVE AFTER INSERT POSITION 0
    4. AS
    5. BEGIN
    6. POST_EVENT 'new_order';