Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions doc/sql.extensions/README.using_statement.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,29 @@ input parameter in multiple places), the developer is currently forced to explic
more tediously, all output fields.

The `USING` statement simplifies this workflow. It provides the ability to declare parameters, sub-routines and
variables while allowing the engine to infer outputs automatically from the contained SQL command.
variables, optionally execute the `DO` command in an autonomous transaction, and still allow the engine to infer
outputs automatically from the contained SQL command.

## Syntax

```sql
USING [ ( <input_parameter_list> ) ]
[ <local_declarations> ]
[ IN AUTONOMOUS TRANSACTION ]
DO <sql_command>
```

**Note:** At least one of `<input_parameter_list>` or `<local_declarations>` must be present. A `USING DO ...` statement
without parameters and without local declarations is invalid.
**Note:** At least one of `<input_parameter_list>`, `<local_declarations>` or `IN AUTONOMOUS TRANSACTION` must be
present. A `USING DO ...` statement without parameters, without local declarations and without `IN AUTONOMOUS
TRANSACTION` is invalid.

### Components

* **`<input_parameter_list>`**: A strictly typed list of parameters. These can be bound to values using the `?`
placeholder.
* **`<local_declarations>`**: Standard PSQL local declarations (variables, sub-functions and sub-procedures).
* **`IN AUTONOMOUS TRANSACTION`**: Executes the `DO` command in a separate autonomous transaction, reusing the same
semantics already supported by PSQL.
* **`<sql_command>`**: The DSQL statement to execute. Supported statements include:
* `SELECT`
* `INSERT` (with or without `RETURNING`)
Expand Down Expand Up @@ -121,12 +126,21 @@ begin
end
```

### 5. Autonomous Transaction

```sql
using in autonomous transaction
do insert into audit_log (log_time, message)
values (current_timestamp, 'Entry written in autonomous transaction');
```

## Comparison

| Feature | Standard DSQL | `EXECUTE BLOCK` | `USING` |
| :---------------------- | :------------------------------ | :-------------------------------------------- | :-------------------------------------------------------- |
| **Local Declarations** | No | Yes | Yes |
| **Input Declarations** | Implicit (Positional) | Explicit | Hybrid (implicit and explicit) |
| **Output Declarations** | Inferred | Explicit (`RETURNS`) | Inferred |
| **Verbosity** | Low | High | Medium |
| **Use Case** | Simple queries | Complex logic, loops, no result set inference | Reusing params, variables, sub-routines, standard queries |
| Feature | Standard DSQL | `EXECUTE BLOCK` | `USING` |
| :------------------------- | :------------------------------ | :-------------------------------------------- | :-------------------------------------------------------- |
| **Local Declarations** | No | Yes | Yes |
| **Input Declarations** | Implicit (Positional) | Explicit | Hybrid (implicit and explicit) |
| **Output Declarations** | Inferred | Explicit (`RETURNS`) | Inferred |
| **Verbosity** | Low | High | Medium |
| **Use Case** | Simple queries | Complex logic, loops, no result set inference | Reusing params, variables, sub-routines, standard queries |
| **Autonomous transaction** | No | Yes (without `SUSPEND`) | Yes |
137 changes: 124 additions & 13 deletions src/dsql/StmtNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2410,6 +2410,9 @@ void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
GEN_port(dsqlScratch, dsqlScratch->recordKeyMessage);

std::optional<USHORT> tableNumber;
const bool useInternalAutoTrans =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK) &&
dsqlReturning && !dsqlScratch->isPsql() && dsqlCursorName.isEmpty();

const bool skipLocked = dsqlRse && dsqlRse->hasSkipLocked();

Expand All @@ -2430,6 +2433,13 @@ void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
}
}

if (useInternalAutoTrans)
{
dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0);
dsqlScratch->appendUChar(blr_begin);
}

if (dsqlRse)
{
dsqlScratch->appendUChar(blr_for);
Expand Down Expand Up @@ -2460,6 +2470,9 @@ void EraseNode::genBlr(DsqlCompilerScratch* dsqlScratch)

if (!dsqlScratch->isPsql() && dsqlCursorName.isEmpty())
{
if (useInternalAutoTrans)
dsqlScratch->appendUChar(blr_end);

dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, tableNumber.value());

if (!skipLocked)
Expand Down Expand Up @@ -4713,16 +4726,12 @@ DmlNode* InAutonomousTransactionNode::parse(thread_db* tdbb, MemoryPool& pool, C

InAutonomousTransactionNode* InAutonomousTransactionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
const bool autoTrans = dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK;
dsqlScratch->flags |= DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK;
AutoSetRestoreFlag autoTrans(&dsqlScratch->flags, DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK, true);

InAutonomousTransactionNode* node = FB_NEW_POOL(dsqlScratch->getPool()) InAutonomousTransactionNode(
dsqlScratch->getPool());
node->action = action->dsqlPass(dsqlScratch);

if (!autoTrans)
dsqlScratch->flags &= ~DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK;

return node;
}

Expand All @@ -4737,9 +4746,33 @@ string InAutonomousTransactionNode::internalPrint(NodePrinter& printer) const

void InAutonomousTransactionNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0); // to extend syntax in the future
action->genBlr(dsqlScratch);
bool useInternalAutoTrans = false;

if (dsqlScratch && !dsqlScratch->isPsql())
{
if (const auto modifyNode = nodeAs<ModifyNode>(action))
useInternalAutoTrans = modifyNode->dsqlReturning && modifyNode->dsqlCursorName.isEmpty();
else if (const auto eraseNode = nodeAs<EraseNode>(action))
useInternalAutoTrans = eraseNode->dsqlReturning && eraseNode->dsqlCursorName.isEmpty();
else if (const auto storeNode = nodeAs<StoreNode>(action))
useInternalAutoTrans = storeNode->dsqlReturning != nullptr;
else if (const auto updateOrInsertNode = nodeAs<UpdateOrInsertNode>(action))
useInternalAutoTrans = updateOrInsertNode->returning != nullptr;
else if (const auto mergeNode = nodeAs<MergeNode>(action))
useInternalAutoTrans = mergeNode->returning != nullptr;
}

if (useInternalAutoTrans)
{
AutoSetRestoreFlag autoTrans(&dsqlScratch->flags, DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK, true);
action->genBlr(dsqlScratch);
}
else
{
dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0); // to extend syntax in the future
action->genBlr(dsqlScratch);
}
}

InAutonomousTransactionNode* InAutonomousTransactionNode::pass1(thread_db* tdbb, CompilerScratch* csb)
Expand Down Expand Up @@ -7246,6 +7279,9 @@ string MergeNode::internalPrint(NodePrinter& printer) const
void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
std::optional<USHORT> tableNumber;
const bool useInternalAutoTrans =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK) &&
returning && !dsqlScratch->isPsql();

if (returning && !dsqlScratch->isPsql())
{
Expand All @@ -7255,6 +7291,13 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlGenReturningLocalTableDecl(dsqlScratch, tableNumber.value());
}

if (useInternalAutoTrans)
{
dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0);
dsqlScratch->appendUChar(blr_begin);
}

// Put src info for blr_for.
if (hasLineColumn)
dsqlScratch->putDebugSrcInfo(line, column);
Expand Down Expand Up @@ -7493,6 +7536,9 @@ void MergeNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUChar(blr_end);
}

if (useInternalAutoTrans)
dsqlScratch->appendUChar(blr_end);

if (returning && !dsqlScratch->isPsql())
{
dsqlGenReturningLocalTableCursor(dsqlScratch, returning, tableNumber.value());
Expand Down Expand Up @@ -7949,6 +7995,15 @@ string ModifyNode::internalPrint(NodePrinter& printer) const

void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
const bool useInternalAutoTrans =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK) &&
dsqlReturning && !dsqlScratch->isPsql() && dsqlCursorName.isEmpty() &&
dsqlReturningLocalTableNumber.has_value() &&
!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT);
const bool deferUpdateOrInsertLocalTableDecl =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT) &&
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK);

if (dsqlScratch->recordKeyMessage)
{
GEN_port(dsqlScratch, dsqlScratch->recordKeyMessage);
Expand All @@ -7957,14 +8012,24 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
if (dsqlReturning && !dsqlScratch->isPsql())
{
if (dsqlCursorName.isEmpty())
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturningLocalTableNumber.value());
{
if (!deferUpdateOrInsertLocalTableDecl)
dsqlGenReturningLocalTableDecl(dsqlScratch, dsqlReturningLocalTableNumber.value());
}
else
{
dsqlScratch->appendUChar(blr_send);
dsqlScratch->appendUChar(dsqlScratch->getDsqlStatement()->getReceiveMsg()->msg_number);
}
}

if (useInternalAutoTrans)
{
dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0);
dsqlScratch->appendUChar(blr_begin);
}

if (dsqlRse)
{
dsqlScratch->appendUChar(blr_for);
Expand Down Expand Up @@ -8002,6 +8067,9 @@ void ModifyNode::genBlr(DsqlCompilerScratch* dsqlScratch)
!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT) &&
dsqlCursorName.isEmpty())
{
if (useInternalAutoTrans)
dsqlScratch->appendUChar(blr_end);

dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value());
}
}
Expand Down Expand Up @@ -9002,6 +9070,11 @@ string StoreNode::internalPrint(NodePrinter& printer) const

void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
const bool useInternalAutoTrans =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK) &&
dsqlReturning && !dsqlScratch->isPsql() && dsqlReturningLocalTableNumber.has_value() &&
!(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT);

if (dsqlReturning && !dsqlScratch->isPsql())
{
if (dsqlRse)
Expand All @@ -9013,6 +9086,13 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)
}
}

if (useInternalAutoTrans)
{
dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0);
dsqlScratch->appendUChar(blr_begin);
}

if (dsqlRse)
{
dsqlScratch->appendUChar(blr_for);
Expand All @@ -9035,10 +9115,18 @@ void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)

if (dsqlReturningLocalTableNumber.has_value())
{
const bool deferUpdateOrInsertCursor =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT) &&
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK);

if (dsqlScratch->flags & DsqlCompilerScratch::FLAG_UPDATE_OR_INSERT)
dsqlScratch->appendUChar(blr_end); // close blr_if (blr_eql, blr_internal_info)

dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value());
if (useInternalAutoTrans)
dsqlScratch->appendUChar(blr_end);

if (!deferUpdateOrInsertCursor)
dsqlGenReturningLocalTableCursor(dsqlScratch, dsqlReturning, dsqlReturningLocalTableNumber.value());
}
}
else if (overrideClause.has_value())
Expand Down Expand Up @@ -10745,6 +10833,19 @@ string UpdateOrInsertNode::internalPrint(NodePrinter& printer) const

void UpdateOrInsertNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
const bool useInternalAutoTrans =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK) &&
storeNode->dsqlReturningLocalTableNumber.has_value() && !dsqlScratch->isPsql();

if (useInternalAutoTrans)
{
dsqlGenReturningLocalTableDecl(dsqlScratch, storeNode->dsqlReturningLocalTableNumber.value());

dsqlScratch->appendUChar(blr_auto_trans);
dsqlScratch->appendUChar(0);
dsqlScratch->appendUChar(blr_begin);
}

dsqlScratch->appendUChar(blr_begin);

for (auto& varAssign : varAssignments)
Expand Down Expand Up @@ -10777,6 +10878,13 @@ void UpdateOrInsertNode::genBlr(DsqlCompilerScratch* dsqlScratch)
dsqlScratch->appendUChar(blr_end); // blr_if

dsqlScratch->appendUChar(blr_end);

if (useInternalAutoTrans)
{
dsqlScratch->appendUChar(blr_end);
dsqlGenReturningLocalTableCursor(dsqlScratch, storeNode->dsqlReturning,
storeNode->dsqlReturningLocalTableNumber.value());
}
}


Expand Down Expand Up @@ -10959,21 +11067,23 @@ void UserSavepointNode::execute(thread_db* tdbb, DsqlRequest* request, jrd_tra**

StmtNode* UsingNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
// USING without parameters and subroutines is useless
if (parameters.isEmpty() && !localDeclList)
// USING without parameters, subroutines or autonomous transaction is useless
if (parameters.isEmpty() && !localDeclList && !inAutonomousTransaction)
{
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
Arg::Gds(isc_dsql_using_requires_params_subroutines));
Arg::Gds(isc_dsql_using_statement_must_contain_clause));
}

dsqlScratch->flags |= DsqlCompilerScratch::FLAG_USING_STATEMENT;
dsqlScratch->reserveInitialVarNumbers(parameters.getCount());

const auto statement = dsqlScratch->getDsqlStatement();
unsigned index = 0;

const auto node = FB_NEW_POOL(dsqlScratch->getPool()) UsingNode(dsqlScratch->getPool());

node->parameters = parameters;
node->inAutonomousTransaction = inAutonomousTransaction;

for (auto newParam : node->parameters)
{
Expand Down Expand Up @@ -11036,6 +11146,7 @@ string UsingNode::internalPrint(NodePrinter& printer) const
NODE_PRINT(printer, parameters);
NODE_PRINT(printer, localDeclList);
NODE_PRINT(printer, body);
NODE_PRINT(printer, inAutonomousTransaction);

return "UsingNode";
}
Expand Down
17 changes: 10 additions & 7 deletions src/dsql/StmtNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,14 @@ class SavepointEncloseNode final : public TypedNode<StmtNode, StmtNode::TYPE_SAV

static StmtNode* make(MemoryPool& pool, DsqlCompilerScratch* dsqlScratch, StmtNode* node, bool force = false);

private:
explicit SavepointEncloseNode(MemoryPool& pool, StmtNode* stmt)
: TypedNode<StmtNode, StmtNode::TYPE_SAVEPOINT>(pool),
statement(stmt)
{
}

public:
Firebird::string internalPrint(NodePrinter& printer) const override;
SavepointEncloseNode* dsqlPass(DsqlCompilerScratch* dsqlScratch) override;
void genBlr(DsqlCompilerScratch* dsqlScratch) override;
Expand All @@ -1553,13 +1561,7 @@ class SavepointEncloseNode final : public TypedNode<StmtNode, StmtNode::TYPE_SAV

const StmtNode* execute(thread_db* tdbb, Request* request, ExeState* exeState) const override;

private:
explicit SavepointEncloseNode(MemoryPool& pool, StmtNode* stmt)
: TypedNode<StmtNode, StmtNode::TYPE_SAVEPOINT>(pool),
statement(stmt)
{
}

public:
NestConst<StmtNode> statement;
};

Expand Down Expand Up @@ -2116,6 +2118,7 @@ class UsingNode final : public TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_USING>
Firebird::Array<NestConst<ParameterClause>> parameters;
NestConst<LocalDeclarationsNode> localDeclList;
NestConst<StmtNode> body;
bool inAutonomousTransaction = false;
};


Expand Down
Loading
Loading