Skip to content
Merged
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
10 changes: 10 additions & 0 deletions doc/sql.extensions/README.create_table_as_query.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ query expression.
String literals use the connection character set, so columns created from string literals inherit the connection
character set.

## Bulk insert

With `WITH DATA`, the rows are loaded using the same bulk insert mechanism used by `gbak` restore. Data pages are
filled directly instead of storing each row through the regular record store path, which is considerably faster for
large results. This applies to regular, global temporary and local temporary tables.

Bulk insert does not evaluate domain `CHECK` constraints. When a new column references a named domain that has a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should have an option to control how table columns are created? Specifically, two options: inheriting the domain versus inheriting only the column type. This would allow the user to choose between creating tables with data at maximum speed or creating a more precise metadata copy at the cost of slower performance.

`CHECK` constraint, the rows are loaded with a regular `INSERT` instead, so the constraint is still enforced. `NOT NULL`
is always enforced. Domains without a `CHECK` constraint do not prevent the use of bulk insert.

## Examples

```sql
Expand Down
32 changes: 31 additions & 1 deletion src/dsql/DdlNodes.epp
Original file line number Diff line number Diff line change
Expand Up @@ -10132,6 +10132,25 @@ void CreateRelationNode::defineQueryColumns(thread_db* tdbb, DsqlCompilerScratch
dsqlScratch->derivedContext.clear();
}

// Check whether a domain has a CHECK constraint (validation).
static bool domainHasCheck(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& domainName)
{
AUTO_HANDLE(request);
bool hasCheck = false;

FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
FIRST 1
FLD IN RDB$FIELDS
WITH FLD.RDB$SCHEMA_NAME EQ domainName.schema.c_str() AND
FLD.RDB$FIELD_NAME EQ domainName.object.c_str()
{
hasCheck = !FLD.RDB$VALIDATION_BLR.NULL;
}
END_FOR

return hasCheck;
}

void CreateRelationNode::executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
jrd_tra* transaction)
{
Expand All @@ -10140,13 +10159,23 @@ void CreateRelationNode::executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsq

bool first = true;

// Bulk insert does not run domain CHECK constraints, so use it only when no column
// references a domain that has one.
bool useBulkInsert = true;

for (const auto& clause : clauses)
{
if (clause->type != Clause::TYPE_ADD_COLUMN)
continue;

const auto addColumnClause = static_cast<const AddColumnClause*>(clause.getObject());

if (useBulkInsert && addColumnClause->field->typeOfName.object.hasData() &&
domainHasCheck(tdbb, transaction, addColumnClause->field->typeOfName))
{
useBulkInsert = false;
}

if (!first)
sql += ", ";

Expand All @@ -10168,7 +10197,8 @@ void CreateRelationNode::executeInsert(thread_db* tdbb, DsqlCompilerScratch* dsq
AutoSetRestoreFlag<ULONG> noDbPageSpace(&tdbb->tdbb_flags, TDBB_use_db_page_space, false);

DSQL_execute_immediate(tdbb, attachment, &traHandle, sql.length(), sql.c_str(),
dsqlScratch->clientDialect, nullptr, nullptr, nullptr, nullptr, true);
dsqlScratch->clientDialect, nullptr, nullptr, nullptr, nullptr, true,
(useBulkInsert ? DsqlCompilerScratch::FLAG_BULK_INSERT : 0));

fb_assert(traHandle == transaction);
}
Expand Down
1 change: 1 addition & 0 deletions src/dsql/DsqlCompilerScratch.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class DsqlCompilerScratch : public BlrDebugWriter
static const unsigned FLAG_ALLOW_CREATED_LTT_REFERENCE = 0x020000;
static const unsigned FLAG_USING_STATEMENT = 0x040000;
static const unsigned FLAG_ACTUAL_LTT_DDL = 0x080000;
static const unsigned FLAG_BULK_INSERT = 0x100000;

static const unsigned MAX_NESTING = 512;

Expand Down
10 changes: 10 additions & 0 deletions src/dsql/StmtNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10247,6 +10247,16 @@ string StoreNode::internalPrint(NodePrinter& printer) const

void StoreNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
if ((dsqlScratch->flags & DsqlCompilerScratch::FLAG_BULK_INSERT) &&
dsqlRse && !dsqlReturning && !overrideClause.has_value())
{
dsqlScratch->appendUChar(blr_bulk_insert);
GEN_expr(dsqlScratch, dsqlRse);
GEN_expr(dsqlScratch, target);
statement->genBlr(dsqlScratch);
return;
}

const bool useInternalAutoTrans =
(dsqlScratch->flags & DsqlCompilerScratch::FLAG_IN_AUTO_TRANS_BLOCK) &&
dsqlReturning && !dsqlScratch->isPsql() && dsqlReturningLocalTableNumber.has_value() &&
Expand Down
34 changes: 22 additions & 12 deletions src/dsql/dsql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ using namespace Firebird;

static ULONG get_request_info(thread_db*, DsqlRequest*, ULONG, UCHAR*);
static dsql_dbb* init(Jrd::thread_db*, Jrd::Attachment*);
static DsqlRequest* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, unsigned, bool);
static DsqlRequest* safePrepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, unsigned, bool);
static DsqlRequest* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, unsigned, bool,
unsigned scratchFlags = 0);
static DsqlRequest* safePrepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, unsigned, bool,
unsigned scratchFlags = 0);
static RefPtr<DsqlStatement> prepareStatement(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT,
unsigned, bool, ntrace_result_t* traceResult);
unsigned, bool, ntrace_result_t* traceResult, unsigned scratchFlags = 0);
static UCHAR* put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const);
static void sql_info(thread_db*, DsqlRequest*, ULONG, const UCHAR*, ULONG, UCHAR*);
static UCHAR* var_info(const dsql_msg*, const UCHAR*, const UCHAR* const, UCHAR*,
Expand Down Expand Up @@ -339,7 +341,7 @@ void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tr
ULONG length, const TEXT* string, USHORT dialect,
IMessageMetadata* in_meta, const UCHAR* in_msg,
IMessageMetadata* out_meta, UCHAR* out_msg,
bool isInternalRequest)
bool isInternalRequest, unsigned scratchFlags)
{
SET_TDBB(tdbb);

Expand All @@ -349,7 +351,7 @@ void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tr
try
{
dsqlRequest = safePrepareRequest(tdbb, database, *tra_handle, length, string, dialect,
0, isInternalRequest);
0, isInternalRequest, scratchFlags);

const auto dsqlStatement = dsqlRequest->getDsqlStatement();

Expand Down Expand Up @@ -454,10 +456,14 @@ static dsql_dbb* init(thread_db* tdbb, Jrd::Attachment* attachment)

// Use SEH frame when preparing user requests to catch possible stack overflows
static DsqlRequest* safePrepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest)
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest,
unsigned scratchFlags)
{
if (isInternalRequest)
return prepareRequest(tdbb, database, transaction, textLength, text, clientDialect, prepareFlags, true);
{
return prepareRequest(tdbb, database, transaction, textLength, text, clientDialect, prepareFlags, true,
scratchFlags);
}

//#define SQL_LOG_PRINT
#ifdef SQL_LOG_PRINT // for debugging
Expand All @@ -467,7 +473,8 @@ static DsqlRequest* safePrepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_
#ifdef WIN_NT
START_CHECK_FOR_EXCEPTIONS(NULL);
#endif
return prepareRequest(tdbb, database, transaction, textLength, text, clientDialect, prepareFlags, false);
return prepareRequest(tdbb, database, transaction, textLength, text, clientDialect, prepareFlags, false,
scratchFlags);

#ifdef WIN_NT
END_CHECK_FOR_EXCEPTIONS(NULL);
Expand All @@ -478,15 +485,16 @@ static DsqlRequest* safePrepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_
// Prepare a request for execution.
// Note: caller is responsible for pool handling.
static DsqlRequest* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest)
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest,
unsigned scratchFlags)
{
TraceDSQLPrepare trace(database->dbb_attachment, transaction, textLength, text, isInternalRequest);

ntrace_result_t traceResult = ITracePlugin::RESULT_SUCCESS;
try
{
auto statement = prepareStatement(tdbb, database, transaction, textLength, text,
clientDialect, prepareFlags, isInternalRequest, &traceResult);
clientDialect, prepareFlags, isInternalRequest, &traceResult, scratchFlags);

auto dsqlRequest = statement->createRequest(tdbb, database);

Expand Down Expand Up @@ -518,7 +526,7 @@ static DsqlRequest* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra*
// Note: caller is responsible for pool handling.
static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
ULONG textLength, const TEXT* text, USHORT clientDialect, unsigned prepareFlags, bool isInternalRequest,
ntrace_result_t* traceResult)
ntrace_result_t* traceResult, unsigned scratchFlags)
{
Database* const dbb = tdbb->getDatabase();

Expand Down Expand Up @@ -562,7 +570,7 @@ static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* databas
}

string textStr(text, textLength);
const bool isStatementCacheActive = database->dbb_statement_cache->isActive() &&
const bool isStatementCacheActive = database->dbb_statement_cache->isActive() && !scratchFlags &&
(transaction ? (!transaction->isDdl()) : true);

RefPtr<DsqlStatement> dsqlStatement;
Expand Down Expand Up @@ -605,6 +613,8 @@ static RefPtr<DsqlStatement> prepareStatement(thread_db* tdbb, dsql_dbb* databas
if (isInternalRequest)
scratch->flags |= DsqlCompilerScratch::FLAG_INTERNAL_REQUEST;

scratch->flags |= scratchFlags;

Parser parser(tdbb, *scratchPool, statementPool, scratch, clientDialect,
dbDialect,
(prepareFlags & IStatement::PREPARE_REQUIRE_SEMICOLON),
Expand Down
2 changes: 1 addition & 1 deletion src/dsql/dsql_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void DSQL_execute(Jrd::thread_db*, Jrd::jrd_tra**, Jrd::DsqlRequest*,
Firebird::IMessageMetadata*, const UCHAR*, Firebird::IMessageMetadata*, UCHAR*);
void DSQL_execute_immediate(Jrd::thread_db*, Jrd::Attachment*, Jrd::jrd_tra**,
ULONG, const TEXT*, USHORT, Firebird::IMessageMetadata*, const UCHAR*,
Firebird::IMessageMetadata*, UCHAR*, bool);
Firebird::IMessageMetadata*, UCHAR*, bool, unsigned scratchFlags = 0);
void DSQL_free_statement(Jrd::thread_db*, Jrd::DsqlRequest*, USHORT);
Jrd::DsqlRequest* DSQL_prepare(Jrd::thread_db*, Jrd::Attachment*, Jrd::jrd_tra*, ULONG, const TEXT*,
USHORT, unsigned, Firebird::Array<UCHAR>*, Firebird::Array<UCHAR>*, bool);
Expand Down
Loading