Skip to content

Commit fbb65c9

Browse files
authored
Feature #8869 - USING statement (#8870)
1 parent c3d5f12 commit fbb65c9

17 files changed

Lines changed: 544 additions & 99 deletions

doc/README.using_statement.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# The `USING` Statement
2+
3+
## Overview
4+
5+
The `USING` statement is a new DSQL extension designed to bridge the gap between standard DSQL statements and the
6+
powerful but verbose `EXECUTE BLOCK`.
7+
8+
When adapting a standard DSQL command to use `EXECUTE BLOCK` (for instance, to utilize sub-routines or reuse a single
9+
input parameter in multiple places), the developer is currently forced to explicitly declare all input parameters and,
10+
more tediously, all output fields.
11+
12+
The `USING` statement simplifies this workflow. It provides the ability to declare parameters and sub-routines while
13+
allowing the engine to infer outputs automatically from the contained SQL command.
14+
15+
## Syntax
16+
17+
```sql
18+
USING [ ( <input_parameter_list> ) ]
19+
[ <subroutines> ]
20+
DO <sql_command>
21+
```
22+
23+
**Note:** At least one of `<input_parameter_list>` or `<subroutines>` must be present. A `USING ... DO` statement
24+
without parameters and without subroutines is invalid.
25+
26+
### Components
27+
28+
* **`<input_parameter_list>`**: A strictly typed list of parameters. These can be bound to values using the `?`
29+
placeholder.
30+
* **`<subroutines>`**: Standard PSQL function or procedure declarations.
31+
* **`<sql_command>`**: The DSQL statement to execute. Supported statements include:
32+
* `SELECT`
33+
* `INSERT` (with or without `RETURNING`)
34+
* `UPDATE` (with or without `RETURNING`)
35+
* `UPDATE OR INSERT` (with or without `RETURNING`)
36+
* `DELETE` (with or without `RETURNING`)
37+
* `MERGE` (with or without `RETURNING`)
38+
* `CALL`
39+
* `EXECUTE PROCEDURE`
40+
41+
## Key Features
42+
43+
1. **Inferred Outputs**: Unlike `EXECUTE BLOCK`, you do not need to explicitly declare a `RETURNS (...)` clause. The
44+
output columns are automatically inferred from the `<sql_command>` in the `DO` clause.
45+
2. **Statement Type Transparency**: The API returns the statement type of the inner `<sql_command>` (e.g., if the
46+
inner command is a `SELECT`, the client sees a `SELECT` statement).
47+
3. **Parameter Reuse**: Input parameters declared in the `USING` clause can be used multiple times within the script
48+
using named references (e.g., `:p1`), while only requiring a single bind from the client application.
49+
4. **Mixed Parameter Binding**: You can mix declared parameters (bound via `?` in the declaration) and direct
50+
positional parameters (using `?` inside the `DO` command).
51+
52+
## Examples
53+
54+
### 1. Basic Parameter Reuse and Subroutines
55+
56+
This example demonstrates declaring typed parameters, defining local functions/procedures, and using them in a query.
57+
58+
```sql
59+
using (p1 integer = ?, p2 integer = ?)
60+
-- Declare a local function
61+
declare function subfunc (i1 integer) returns integer
62+
as
63+
begin
64+
return i1;
65+
end
66+
67+
-- Declare a local procedure
68+
declare procedure subproc (i1 integer) returns (o1 integer)
69+
as
70+
begin
71+
o1 = i1;
72+
suspend;
73+
end
74+
do
75+
-- The main query
76+
select subfunc(:p1) + o1
77+
from subproc(:p2 + ?)
78+
```
79+
80+
In this scenario:
81+
1. The client binds values to `p1` and `p2`.
82+
2. The client binds a third value to the `?` inside the `DO` clause.
83+
3. The result set structure is inferred from the `SELECT` statement.
84+
85+
### 2. Simplifying Parameter Reuse
86+
87+
Without `USING`, inserting the same bind value into multiple columns requires sending the data twice.
88+
89+
**Standard DSQL:**
90+
```sql
91+
insert into generic_table (col_a, col_b) values (?, ?);
92+
-- Client must bind: [100, 100]
93+
```
94+
95+
**With `USING`:**
96+
```sql
97+
using (val integer = ?)
98+
do insert into generic_table (col_a, col_b) values (:val, :val);
99+
-- Client binds: [100]
100+
```
101+
102+
### 3. EXECUTE STATEMENT with Named Parameters
103+
104+
```sql
105+
execute block returns (ri integer, rs varchar(20))
106+
as
107+
begin
108+
execute statement ('using(i int = ?, s varchar(20) = ?) do select :i, :s from rdb$database') (s := '2', i := 1) into ri, rs;
109+
suspend;
110+
end
111+
```
112+
113+
### 4. EXECUTE STATEMENT with Unnamed Parameters
114+
115+
```sql
116+
execute block returns (ri integer, rs varchar(20))
117+
as
118+
begin
119+
execute statement ('using(i int = ?, s varchar(20) = ?) do select :i, :s from rdb$database') (1, '2') into ri, rs;
120+
suspend;
121+
end
122+
```
123+
124+
## Comparison
125+
126+
| Feature | Standard DSQL | `EXECUTE BLOCK` | `USING` |
127+
| :---------------------- | :------------------------------ | :-------------------------------------------- | :-------------------------------------------- |
128+
| **Subroutines** | No | Yes | Yes |
129+
| **Input Declarations** | Implicit (Positional) | Explicit | Hybrid (implicit and explicit) |
130+
| **Output Declarations** | Inferred | Explicit (`RETURNS`) | Inferred |
131+
| **Verbosity** | Low | High | Medium |
132+
| **Use Case** | Simple queries | Complex logic, loops, no result set inference | Reusing params, subroutines, standard queries |

src/dsql/DdlNodes.epp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2577,7 +2577,10 @@ void CreateAlterFunctionNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch*
25772577
dsqlScratch->setPsql(true);
25782578

25792579
if (localDeclList)
2580+
{
2581+
localDeclList = localDeclList->dsqlPass(dsqlScratch);
25802582
localDeclList->genBlr(dsqlScratch);
2583+
}
25812584

25822585
dsqlScratch->loopLevel = 0;
25832586
dsqlScratch->cursorNumber = 0;
@@ -3549,7 +3552,10 @@ void CreateAlterProcedureNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch*
35493552
dsqlScratch->setPsql(true);
35503553

35513554
if (localDeclList)
3555+
{
3556+
localDeclList = localDeclList->dsqlPass(dsqlScratch);
35523557
localDeclList->genBlr(dsqlScratch);
3558+
}
35533559

35543560
dsqlScratch->loopLevel = 0;
35553561
dsqlScratch->cursorNumber = 0;
@@ -4111,7 +4117,10 @@ void CreateAlterTriggerNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* d
41114117
dsqlScratch->setPsql(true);
41124118

41134119
if (localDeclList)
4120+
{
4121+
localDeclList = localDeclList->dsqlPass(dsqlScratch);
41144122
localDeclList->genBlr(dsqlScratch);
4123+
}
41154124

41164125
dsqlScratch->loopLevel = 0;
41174126
dsqlScratch->cursorNumber = 0;

src/dsql/DsqlCompilerScratch.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class DsqlCompilerScratch : public BlrDebugWriter
7777
static const unsigned FLAG_VIEW_WITH_CHECK = 0x8000;
7878
static const unsigned FLAG_EXEC_BLOCK = 0x010000;
7979
static const unsigned FLAG_ALLOW_LTT_REFERENCES = 0x020000;
80+
static const unsigned FLAG_USING_STATEMENT = 0x040000;
8081

8182
static const unsigned MAX_NESTING = 512;
8283

src/dsql/ExprNodes.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14164,9 +14164,10 @@ void VariableNode::genBlr(DsqlCompilerScratch* dsqlScratch)
1416414164
{
1416514165
auto varScratch = outerDecl ? dsqlScratch->mainScratch : dsqlScratch;
1416614166

14167-
const bool execBlock = (varScratch->flags & DsqlCompilerScratch::FLAG_EXEC_BLOCK);
14167+
const bool execBlockOrUsing = (varScratch->flags &
14168+
(DsqlCompilerScratch::FLAG_EXEC_BLOCK | DsqlCompilerScratch::FLAG_USING_STATEMENT));
1416814169

14169-
if (dsqlVar->type == dsql_var::TYPE_INPUT && !execBlock)
14170+
if (dsqlVar->type == dsql_var::TYPE_INPUT && !execBlockOrUsing)
1417014171
{
1417114172
dsqlScratch->appendUChar(blr_parameter2);
1417214173

@@ -14184,7 +14185,7 @@ void VariableNode::genBlr(DsqlCompilerScratch* dsqlScratch)
1418414185
}
1418514186
else
1418614187
{
14187-
// If this is an EXECUTE BLOCK input parameter, use the internal variable.
14188+
// If this is an EXECUTE BLOCK or USING input parameter, use the internal variable.
1418814189
dsqlScratch->appendUChar(blr_variable);
1418914190

1419014191
if (outerDecl)

src/dsql/Nodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,7 @@ class StmtNode : public DmlNode
14951495
TYPE_SUSPEND,
14961496
TYPE_TRUNCATE_LOCAL_TABLE,
14971497
TYPE_UPDATE_OR_INSERT,
1498+
TYPE_USING,
14981499

14991500
TYPE_EXT_INIT_PARAMETERS,
15001501
TYPE_EXT_TRIGGER

0 commit comments

Comments
 (0)