8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 12:43:02 +01:00
firebird-mirror/src/dsql/DdlNodes.epp

2763 lines
71 KiB
Plaintext

/*
* The contents of this file are subject to the Interbase Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy
* of the License at http://www.Inprise.com/IPL.html
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
* or implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code was created by Inprise Corporation
* and its predecessors. Portions created by Inprise Corporation are
* Copyright (C) Inprise Corporation.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
* Adriano dos Santos Fernandes - refactored from pass1.cpp, ddl.cpp, dyn*.epp
*/
#include "firebird.h"
#include "../jrd/common.h"
#include "../dsql/DdlNodes.h"
#include "../dsql/node.h"
#include "../jrd/blr.h"
#include "../jrd/dyn.h"
#include "../jrd/flags.h"
#include "../jrd/intl.h"
#include "../jrd/jrd.h"
#include "../jrd/obj.h"
#include "../jrd/tra.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dyn_dl_proto.h"
#include "../jrd/dyn_ut_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/intl_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/vio_proto.h"
#include "../dsql/ddl_proto.h"
#include "../dsql/errd_proto.h"
#include "../dsql/gen_proto.h"
#include "../dsql/make_proto.h"
#include "../dsql/metd_proto.h"
#include "../dsql/pass1_proto.h"
#include "../common/StatusArg.h"
using namespace Firebird;
namespace Jrd {
using namespace Firebird;
using namespace Dsql;
DATABASE DB = STATIC "ODS.RDB";
//----------------------
void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction,
DdlTriggerWhen when, int action, const Firebird::MetaName& objectName,
const Firebird::string& sqlText)
{
Attachment* attachment = transaction->tra_attachment;
// do nothing if user doesn't want database triggers
if (attachment->att_flags & ATT_no_db_triggers)
return;
fb_assert(action > 0); // first element is NULL
DdlTriggerContext context;
context.ddlEvent = DDL_TRIGGER_ACTION_NAMES[action];
context.objectName = objectName;
context.sqlText = sqlText;
Stack<DdlTriggerContext>::AutoPushPop autoContext(attachment->ddlTriggersContext, context);
AutoSavePoint savePoint(tdbb, transaction);
EXE_execute_ddl_triggers(tdbb, transaction, when == DTW_BEFORE, action);
savePoint.release(); // everything is ok
}
void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction,
DdlNode::DdlTriggerWhen when, int action, const MetaName& objectName)
{
executeDdlTrigger(tdbb, transaction, when, action, objectName, sqlText);
}
void DdlNode::putType(const TypeClause& type, bool useSubType)
{
#ifdef DEV_BUILD
// Check if the field describes a known datatype
if (type.type > FB_NELEM(blr_dtypes) || !blr_dtypes[type.type])
{
SCHAR buffer[100];
sprintf(buffer, "Invalid dtype %d in put_dtype", type.type);
ERRD_bugcheck(buffer);
}
#endif
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
if (type.notNull)
statement->append_uchar(blr_not_nullable);
if (type.typeOfName.hasData())
{
if (type.typeOfTable.hasData())
{
if (type.collateSpecified)
{
statement->append_uchar(blr_column_name2);
statement->append_uchar(type.fullDomain ? blr_domain_full : blr_domain_type_of);
statement->append_meta_string(type.typeOfTable.c_str());
statement->append_meta_string(type.typeOfName.c_str());
statement->append_ushort(type.textType);
}
else
{
statement->append_uchar(blr_column_name);
statement->append_uchar(type.fullDomain ? blr_domain_full : blr_domain_type_of);
statement->append_meta_string(type.typeOfTable.c_str());
statement->append_meta_string(type.typeOfName.c_str());
}
}
else
{
if (type.collateSpecified)
{
statement->append_uchar(blr_domain_name2);
statement->append_uchar(type.fullDomain ? blr_domain_full : blr_domain_type_of);
statement->append_meta_string(type.typeOfName.c_str());
statement->append_ushort(type.textType);
}
else
{
statement->append_uchar(blr_domain_name);
statement->append_uchar(type.fullDomain ? blr_domain_full : blr_domain_type_of);
statement->append_meta_string(type.typeOfName.c_str());
}
}
return;
}
switch (type.type)
{
case dtype_cstring:
case dtype_text:
case dtype_varying:
case dtype_blob:
if (!useSubType)
statement->append_uchar(blr_dtypes[type.type]);
else if (type.type == dtype_varying)
{
statement->append_uchar(blr_varying2);
statement->append_ushort(type.textType);
}
else if (type.type == dtype_cstring)
{
statement->append_uchar(blr_cstring2);
statement->append_ushort(type.textType);
}
else if (type.type == dtype_blob)
{
statement->append_uchar(blr_blob2);
statement->append_ushort(type.subType);
statement->append_ushort(type.textType);
}
else
{
statement->append_uchar(blr_text2);
statement->append_ushort(type.textType);
}
if (type.type == dtype_varying)
statement->append_ushort(type.length - sizeof(USHORT));
else if (type.type != dtype_blob)
statement->append_ushort(type.length);
break;
default:
statement->append_uchar(blr_dtypes[type.type]);
if (DTYPE_IS_EXACT(type.type) || dtype_quad == type.type)
statement->append_uchar(type.scale);
break;
}
}
void DdlNode::resetContextStack()
{
dsqlScratch->context->clear();
dsqlScratch->contextNumber = 0;
}
Firebird::MetaName DdlNode::storeGlobalField(thread_db* tdbb, jrd_tra* transaction,
const TypeClause& parameter)
{
Firebird::MetaName name;
AutoCacheRequest requestHandle(tdbb, drq_s_fld_src, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
{
FLD.RDB$FIELD_SUB_TYPE.NULL = TRUE;
FLD.RDB$FIELD_SCALE.NULL = TRUE;
FLD.RDB$CHARACTER_SET_ID.NULL = TRUE;
FLD.RDB$CHARACTER_LENGTH.NULL = TRUE;
FLD.RDB$FIELD_PRECISION.NULL = TRUE;
FLD.RDB$COLLATION_ID.NULL = TRUE;
FLD.RDB$SEGMENT_LENGTH.NULL = TRUE;
FLD.RDB$SYSTEM_FLAG = 0;
DYN_UTIL_generate_field_name(tdbb, NULL, name);
strcpy(FLD.RDB$FIELD_NAME, name.c_str());
if (parameter.type == dtype_blob)
{
FLD.RDB$FIELD_SUB_TYPE.NULL = FALSE;
FLD.RDB$FIELD_SUB_TYPE = parameter.subType;
FLD.RDB$FIELD_SCALE.NULL = FALSE;
FLD.RDB$FIELD_SCALE = 0;
if (parameter.subType == isc_blob_text)
{
FLD.RDB$CHARACTER_SET_ID.NULL = FALSE;
FLD.RDB$CHARACTER_SET_ID = parameter.charSetId;
FLD.RDB$COLLATION_ID.NULL = !parameter.collateSpecified;
FLD.RDB$COLLATION_ID = parameter.collationId;
}
if (parameter.segLength != 0)
{
FLD.RDB$SEGMENT_LENGTH.NULL = FALSE;
FLD.RDB$SEGMENT_LENGTH = parameter.segLength;
}
}
else if (parameter.type <= dtype_any_text)
{
FLD.RDB$FIELD_SUB_TYPE.NULL = FALSE;
FLD.RDB$FIELD_SUB_TYPE = parameter.subType;
FLD.RDB$FIELD_SCALE.NULL = FALSE;
FLD.RDB$FIELD_SCALE = 0;
FLD.RDB$CHARACTER_LENGTH.NULL = FALSE;
FLD.RDB$CHARACTER_LENGTH = parameter.charLength;
FLD.RDB$CHARACTER_SET_ID.NULL = FALSE;
FLD.RDB$CHARACTER_SET_ID = parameter.charSetId;
FLD.RDB$COLLATION_ID.NULL = !parameter.collateSpecified;
FLD.RDB$COLLATION_ID = parameter.collationId;
}
else
{
FLD.RDB$FIELD_SCALE.NULL = FALSE;
FLD.RDB$FIELD_SCALE = parameter.scale;
if (DTYPE_IS_EXACT(parameter.type))
{
FLD.RDB$FIELD_PRECISION.NULL = FALSE;
FLD.RDB$FIELD_PRECISION = parameter.precision;
FLD.RDB$FIELD_SUB_TYPE.NULL = FALSE;
FLD.RDB$FIELD_SUB_TYPE = parameter.subType;
}
}
if (parameter.type == dtype_varying)
{
fb_assert(parameter.length <= MAX_SSHORT);
FLD.RDB$FIELD_LENGTH = (SSHORT) (parameter.length - sizeof(USHORT));
}
else
FLD.RDB$FIELD_LENGTH = parameter.length;
FLD.RDB$FIELD_TYPE = blr_dtypes[parameter.type];
}
END_STORE
return name;
}
//----------------------
TypeClause::TypeClause(dsql_fld* aLegacyField, const MetaName& aCollate)
: legacyField(aLegacyField),
collate(aCollate)
{
}
void TypeClause::resolve(DsqlCompilerScratch* dsqlScratch)
{
DDL_resolve_intl_type(dsqlScratch, legacyField,
(collate.isEmpty() ? NULL : MAKE_cstring(collate.c_str())));
type = legacyField->fld_dtype;
length = legacyField->fld_length;
scale = legacyField->fld_scale;
subType = legacyField->fld_sub_type;
segLength = legacyField->fld_seg_length;
precision = legacyField->fld_precision;
charLength = legacyField->fld_character_length;
charSetId = legacyField->fld_character_set_id;
collationId = legacyField->fld_collation_id;
collateSpecified = collate.hasData();
textType = legacyField->fld_ttype;
fullDomain = legacyField->fld_full_domain;
notNull = legacyField->fld_not_nullable;
fieldSource = legacyField->fld_source;
if (legacyField->fld_type_of_table)
typeOfTable = legacyField->fld_type_of_table->str_data;
typeOfName = legacyField->fld_type_of_name;
}
void TypeClause::print(string& text) const
{
text.printf("typeOfTable: '%s' typeOfName: '%s' notNull: %d fieldSource: '%s'",
typeOfTable.c_str(), typeOfName.c_str(), notNull, fieldSource.c_str());
}
//----------------------
ParameterClause::ParameterClause(dsql_fld* field, const MetaName& aCollate, dsql_nod* dflt)
: TypeClause(field, aCollate),
name(field->fld_name),
legacyDefault(dflt)
{
}
void ParameterClause::print(string& text) const
{
string s;
TypeClause::print(s);
text.printf("name: '%s' %s", name.c_str(), s.c_str());
}
//----------------------
void AlterCharSetNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"AlterCharSetNode\n"
" charSet: %s\n"
" defaultCollation: %s\n",
charSet.c_str(), defaultCollation.c_str());
}
void AlterCharSetNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
METD_drop_charset(transaction, charSet);
MET_dsql_cache_release(tdbb, SYM_intlsym_charset, charSet);
bool charSetFound = false;
bool collationFound = false;
AutoCacheRequest requestHandle1(tdbb, drq_m_charset, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle1 TRANSACTION_HANDLE transaction)
CS IN RDB$CHARACTER_SETS
WITH CS.RDB$CHARACTER_SET_NAME EQ charSet.c_str()
{
charSetFound = true;
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_CHARACTER_SET, charSet);
AutoCacheRequest requestHandle2(tdbb, drq_l_collation, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
COLL IN RDB$COLLATIONS
WITH COLL.RDB$CHARACTER_SET_ID EQ CS.RDB$CHARACTER_SET_ID AND
COLL.RDB$COLLATION_NAME EQ defaultCollation.c_str()
{
collationFound = true;
}
END_FOR
if (collationFound)
{
MODIFY CS
CS.RDB$DEFAULT_COLLATE_NAME.NULL = FALSE;
strcpy(CS.RDB$DEFAULT_COLLATE_NAME, defaultCollation.c_str());
END_MODIFY
}
}
END_FOR
if (!charSetFound)
{
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_charset_not_found) << Arg::Str(charSet));
}
if (!collationFound)
{
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_collation_not_found) << Arg::Str(defaultCollation) <<
Arg::Str(charSet));
}
executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_CHARACTER_SET, charSet);
}
//----------------------
void CommentOnNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"CommentOnNode\n"
" objType: %s\n"
" objName: %s\n"
" text: %s\n",
objType, objName.c_str(), text.c_str());
}
// select rdb$relation_name from rdb$relation_fields where rdb$field_name = 'RDB$DESCRIPTION';
// gives the list of objects that accept descriptions. At FB2 time, the only
// subobjects with descriptions are relation's fields and procedure's parameters.
void CommentOnNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
Attachment* attachment = transaction->tra_attachment;
string table;
string column;
string subColumn;
string addWhere;
Arg::StatusVector status;
switch (objType)
{
case ddl_database:
table = "rdb$database";
break;
case ddl_domain:
table = "rdb$fields";
column = "rdb$field_name";
status << Arg::Gds(isc_dyn_domain_not_found);
break;
case ddl_relation:
if (subName.hasData())
{
table = "rdb$relation_fields";
subColumn = "rdb$field_name";
status << Arg::Gds(isc_dyn_column_does_not_exist) <<
Arg::Str(subName) << Arg::Str(objName);
}
else
{
table = "rdb$relations";
addWhere = "rdb$view_blr is null";
status << Arg::Gds(isc_dyn_table_not_found) << Arg::Str(objName);
}
column = "rdb$relation_name";
break;
case ddl_view:
table = "rdb$relations";
column = "rdb$relation_name";
status << Arg::Gds(isc_dyn_view_not_found) << Arg::Str(objName);
addWhere = "rdb$view_blr is not null";
break;
case ddl_procedure:
if (subName.hasData())
{
table = "rdb$procedure_parameters";
subColumn = "rdb$parameter_name";
status << Arg::Gds(isc_dyn_proc_param_not_found) <<
Arg::Str(subName) << Arg::Str(objName);
}
else
{
table = "rdb$procedures";
status << Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(objName);
}
addWhere = "rdb$package_name is null";
column = "rdb$procedure_name";
break;
case ddl_trigger:
table = "rdb$triggers";
column = "rdb$trigger_name";
status << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(objName);
break;
case ddl_udf:
table = "rdb$functions";
column = "rdb$function_name";
addWhere = "rdb$package_name is null";
status << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(objName);
break;
case ddl_blob_filter:
table = "rdb$filters";
column = "rdb$function_name";
status << Arg::Gds(isc_dyn_filter_not_found) << Arg::Str(objName);
break;
case ddl_exception:
table = "rdb$exceptions";
column = "rdb$exception_name";
status << Arg::Gds(isc_dyn_exception_not_found) << Arg::Str(objName);
break;
case ddl_generator:
table = "rdb$generators";
column = "rdb$generator_name";
status << Arg::Gds(isc_dyn_gen_not_found) << Arg::Str(objName);
break;
case ddl_index:
table = "rdb$indices";
column = "rdb$index_name";
status << Arg::Gds(isc_dyn_index_not_found) << Arg::Str(objName);
break;
case ddl_role:
table = "rdb$roles";
column = "rdb$role_name";
status << Arg::Gds(isc_dyn_role_not_found) << Arg::Str(objName);
break;
case ddl_charset:
table = "rdb$character_sets";
column = "rdb$character_set_name";
status << Arg::Gds(isc_dyn_charset_not_found) << Arg::Str(objName);
break;
case ddl_collation:
table = "rdb$collations";
column = "rdb$collation_name";
status << Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(objName);
break;
case ddl_package:
table = "rdb$packages";
column = "rdb$package_name";
status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objName);
break;
case ddl_schema:
table = "rdb$schemas";
column = "rdb$schema_name";
status << Arg::Gds(isc_dyn_schema_not_found) << Arg::Str(objName);
break;
}
fb_assert(table.hasData());
if (table.hasData())
{
string sqlStmt("update " + table + " set rdb$description = ?");
if (column.hasData())
{
sqlStmt += " where " + column + " = ?";
if (subColumn.hasData())
sqlStmt += " and " + subColumn + " = ?";
}
if (addWhere.hasData())
sqlStmt += " and " + addWhere;
AutoPtr<PreparedStatement> ps(attachment->prepareStatement(tdbb,
*tdbb->getDefaultPool(), transaction, sqlStmt));
int n = 0;
if (text.isEmpty())
++n;
else
ps->setString(tdbb, ++n, attachment->stringToMetaCharSet(tdbb, text, textCharSet));
if (column.hasData())
{
ps->setString(tdbb, ++n, objName);
if (subColumn.hasData())
ps->setString(tdbb, ++n, subName);
}
if (ps->executeUpdate(tdbb, transaction) == 0)
status_exception::raise(status);
}
}
//----------------------
void FunctionNode::genReturn()
{
GEN_return(dsqlScratch, getCreateAlterNode()->outputVariables, false, false);
}
dsql_nod* FunctionNode::resolveVariable(const dsql_str* varName)
{
// try to resolve variable name against parameters and local variables
CreateAlterFunctionNode* node = getCreateAlterNode();
return PASS1_resolve_variable_name(node->variables, varName);
}
//----------------------
void CreateAlterFunctionNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"CreateAlterFunctionNode\n"
" name: '%s' create: %d alter: %d\n",
name.c_str(), create, alter);
if (external)
{
string s;
s.printf(" external -> name: '%s' engine: '%s'\n",
external->name.c_str(), external->engine.c_str());
text += s;
}
text += " Parameters:\n";
for (size_t i = 0; i < parameters.getCount(); ++i)
{
const ParameterClause& parameter = parameters[i];
string s;
parameter.print(s);
text += " " + s + "\n";
}
}
DdlNode* CreateAlterFunctionNode::internalDsqlPass()
{
DsqlCompiledStatement* const statement = dsqlScratch->getStatement();
statement->setBlockNode(this);
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);
const dsql_nod* variables = localDeclList;
if (variables)
{
// insure that variable names do not duplicate parameter names
SortedArray<MetaName> names;
for (size_t i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
names.add(parameter.name);
}
const dsql_nod* const* ptr = variables->nod_arg;
for (const dsql_nod* const* const end = ptr + variables->nod_count; ptr < end; ptr++)
{
if ((*ptr)->nod_type == nod_def_field)
{
const dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field];
DEV_BLKCHK(field, dsql_type_fld);
if (names.exist(field->fld_name))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
Arg::Gds(isc_dsql_var_conflict) <<
Arg::Str(field->fld_name));
}
}
}
}
source.ltrim("\n\r\t ");
// compile default expressions
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
if (parameter.legacyDefault)
{
parameter.legacyDefault->nod_arg[e_dft_default] =
PASS1_node(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
}
}
for (unsigned i = 0; i < parameters.getCount(); ++i)
parameters[i].resolve(dsqlScratch);
returnType.resolve(dsqlScratch);
return DdlNode::internalDsqlPass();
}
void CreateAlterFunctionNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
fb_assert(create || alter);
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
bool altered = false;
// first pass
if (alter)
{
if (executeAlter(tdbb, transaction, false, true))
{
altered = true;
}
else
{
if (create) // create or alter
{
executeCreate(tdbb, transaction);
}
else
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name));
}
}
}
else
{
executeCreate(tdbb, transaction);
}
compile(tdbb, transaction);
executeAlter(tdbb, transaction, true, false); // second pass
if (package.isEmpty())
{
executeDdlTrigger(tdbb, transaction, DTW_AFTER,
(altered ? DDL_TRIGGER_ALTER_FUNCTION : DDL_TRIGGER_CREATE_FUNCTION), name);
}
savePoint.release(); // everything is ok
if (alter)
{
// Update DSQL cache
AutoPtr<dsql_str> str(MAKE_string(name.c_str(), name.length()));
METD_drop_function(transaction, str, package);
MET_dsql_cache_release(tdbb, SYM_udf, str->str_data, package);
}
}
void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, jrd_tra* transaction)
{
Attachment* attachment = transaction->getAttachment();
if (package.isEmpty())
{
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_FUNCTION, name);
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_udf);
}
AutoCacheRequest requestHandle(tdbb, drq_s_funcs2, DYN_REQUESTS);
int faults = 0;
while (true)
{
try
{
SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_fun_id, "RDB$FUNCTIONS");
id %= (MAX_SSHORT + 1);
if (!id)
continue;
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
FUN IN RDB$FUNCTIONS
{
FUN.RDB$FUNCTION_ID = id;
FUN.RDB$SYSTEM_FLAG = 0;
strcpy(FUN.RDB$FUNCTION_NAME, name.c_str());
FUN.RDB$LEGACY_FLAG.NULL = FALSE;
FUN.RDB$LEGACY_FLAG = 0;
FUN.RDB$INVARIANT_FLAG.NULL = FALSE;
FUN.RDB$INVARIANT_FLAG = invariant ? TRUE : FALSE;
FUN.RDB$RETURN_ARGUMENT.NULL = FALSE;
FUN.RDB$RETURN_ARGUMENT = 0;
if (package.hasData())
{
FUN.RDB$PACKAGE_NAME.NULL = FALSE;
strcpy(FUN.RDB$PACKAGE_NAME, package.c_str());
FUN.RDB$PRIVATE_FLAG.NULL = FALSE;
FUN.RDB$PRIVATE_FLAG = privateScope;
FUN.RDB$OWNER_NAME.NULL = FALSE;
strcpy(FUN.RDB$OWNER_NAME, packageOwner.c_str());
}
else
{
FUN.RDB$PACKAGE_NAME.NULL = TRUE;
FUN.RDB$PRIVATE_FLAG.NULL = TRUE;
FUN.RDB$OWNER_NAME.NULL = FALSE;
strcpy(FUN.RDB$OWNER_NAME, attachment->att_user->usr_user_name.c_str());
}
}
END_STORE
break;
}
catch (const Firebird::status_exception& ex)
{
if (ex.value()[1] != isc_no_dup)
throw;
if (++faults > MAX_SSHORT)
throw;
fb_utils::init_status(tdbb->tdbb_status_vector);
}
}
if (package.isEmpty())
{
for (const TEXT* p = ALL_PROC_PRIVILEGES; *p; p++)
{
requestHandle.reset(tdbb, drq_s_fun_usr_prvs, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
X IN RDB$USER_PRIVILEGES
{
strcpy(X.RDB$RELATION_NAME, name.c_str());
strcpy(X.RDB$USER, attachment->att_user->usr_user_name.c_str());
X.RDB$USER_TYPE = obj_user;
X.RDB$OBJECT_TYPE = obj_udf;
X.RDB$PRIVILEGE[0] = *p;
X.RDB$PRIVILEGE[1] = 0;
}
END_STORE
}
}
executeAlter(tdbb, transaction, false, false);
}
bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, jrd_tra* transaction,
bool secondPass, bool runTriggers)
{
Attachment* attachment = transaction->getAttachment();
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
bool modified = false;
AutoCacheRequest requestHandle(tdbb, drq_m_funcs2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
FUN IN RDB$FUNCTIONS
WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND
FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
{
if (!FUN.RDB$SYSTEM_FLAG.NULL && FUN.RDB$SYSTEM_FLAG)
{
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cannot_mod_sysfunc) <<
FUN.RDB$FUNCTION_NAME);
}
if (!secondPass && runTriggers && package.isEmpty())
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_FUNCTION, name);
MODIFY FUN
if (secondPass)
{
FUN.RDB$FUNCTION_BLR.NULL = TRUE;
FUN.RDB$DEBUG_INFO.NULL = TRUE;
}
else
{
FUN.RDB$ENGINE_NAME.NULL = TRUE;
FUN.RDB$ENTRYPOINT.NULL = TRUE;
FUN.RDB$FUNCTION_SOURCE.NULL = TRUE;
FUN.RDB$VALID_BLR.NULL = FALSE;
FUN.RDB$VALID_BLR = TRUE;
FUN.RDB$FUNCTION_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty()));
if (!FUN.RDB$FUNCTION_SOURCE.NULL)
attachment->storeMetaDataBlob(tdbb, transaction, &FUN.RDB$FUNCTION_SOURCE, source);
}
if (external)
{
if (!secondPass)
{
FUN.RDB$ENGINE_NAME.NULL = FALSE;
strcpy(FUN.RDB$ENGINE_NAME, external->engine.c_str());
if (external->name.length() >= sizeof(FUN.RDB$ENTRYPOINT))
{
status_exception::raise(
Arg::Gds(isc_arith_except) <<
Arg::Gds(isc_string_truncation));
}
FUN.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
strcpy(FUN.RDB$ENTRYPOINT, external->name.c_str());
}
}
else if (body)
{
if (secondPass)
{
FUN.RDB$FUNCTION_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$FUNCTION_BLR,
statement->getBlrData().begin(),
statement->getBlrData().getCount());
FUN.RDB$DEBUG_INFO.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$DEBUG_INFO,
statement->getDebugData().begin(),
statement->getDebugData().getCount());
}
}
if (package.hasData())
{
FUN.RDB$PRIVATE_FLAG.NULL = FALSE;
FUN.RDB$PRIVATE_FLAG = privateScope;
}
else
{
FUN.RDB$PRIVATE_FLAG.NULL = TRUE;
}
END_MODIFY
modified = true;
}
END_FOR
if (!secondPass && modified)
{
// delete all old arguments
DropFunctionNode::dropArguments(tdbb, transaction, name, package);
// and insert the new ones
ParameterClause returnParameter(returnType.legacyField, returnType.collate, NULL);
returnParameter.resolve(dsqlScratch);
storeArgument(tdbb, transaction, 0, returnParameter);
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
storeArgument(tdbb, transaction, i + 1, parameter);
}
}
return modified;
}
void CreateAlterFunctionNode::storeArgument(thread_db* tdbb, jrd_tra* transaction,
unsigned pos, const ParameterClause& parameter)
{
Attachment* attachment = transaction->getAttachment();
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
AutoCacheRequest requestHandle(tdbb, drq_s_func_args2, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
ARG IN RDB$FUNCTION_ARGUMENTS
{
ARG.RDB$FUNCTION_NAME.NULL = FALSE;
strcpy(ARG.RDB$FUNCTION_NAME, name.c_str());
// Avoid the return with name INTERNAL_FIELD_NAME.
if (parameter.name.hasData() && pos != 0)
{
ARG.RDB$ARGUMENT_NAME.NULL = FALSE;
strcpy(ARG.RDB$ARGUMENT_NAME, parameter.name.c_str());
}
else
ARG.RDB$ARGUMENT_NAME.NULL = TRUE;
if (package.hasData())
{
ARG.RDB$PACKAGE_NAME.NULL = FALSE;
strcpy(ARG.RDB$PACKAGE_NAME, package.c_str());
}
ARG.RDB$ARGUMENT_POSITION.NULL = FALSE;
ARG.RDB$ARGUMENT_POSITION = pos;
ARG.RDB$ARGUMENT_MECHANISM.NULL = TRUE;
ARG.RDB$NULL_FLAG.NULL = TRUE;
ARG.RDB$RELATION_NAME.NULL = TRUE;
ARG.RDB$FIELD_NAME.NULL = TRUE;
ARG.RDB$FIELD_SOURCE.NULL = TRUE;
ARG.RDB$DEFAULT_VALUE.NULL = TRUE;
ARG.RDB$DEFAULT_SOURCE.NULL = TRUE;
ARG.RDB$MECHANISM.NULL = TRUE;
ARG.RDB$FIELD_TYPE.NULL = TRUE;
ARG.RDB$FIELD_LENGTH.NULL = TRUE;
ARG.RDB$FIELD_PRECISION.NULL = TRUE;
ARG.RDB$FIELD_SCALE.NULL = TRUE;
ARG.RDB$CHARACTER_SET_ID.NULL = TRUE;
ARG.RDB$COLLATION_ID.NULL = TRUE;
ARG.RDB$ARGUMENT_MECHANISM.NULL = FALSE;
ARG.RDB$ARGUMENT_MECHANISM =
(USHORT) (parameter.fullDomain ? prm_mech_normal : prm_mech_type_of);
if (parameter.notNull)
{
ARG.RDB$NULL_FLAG.NULL = FALSE;
ARG.RDB$NULL_FLAG = TRUE;
}
ARG.RDB$FIELD_SOURCE.NULL = FALSE;
if (parameter.typeOfTable.isEmpty())
{
if (parameter.typeOfName.hasData())
{
strcpy(ARG.RDB$FIELD_SOURCE, parameter.typeOfName.c_str());
}
else
{
const MetaName fieldName = storeGlobalField(tdbb, transaction, parameter);
strcpy(ARG.RDB$FIELD_SOURCE, fieldName.c_str());
}
}
else
{
ARG.RDB$RELATION_NAME.NULL = FALSE;
strcpy(ARG.RDB$RELATION_NAME, parameter.typeOfTable.c_str());
ARG.RDB$FIELD_NAME.NULL = FALSE;
strcpy(ARG.RDB$FIELD_NAME, parameter.typeOfName.c_str());
strcpy(ARG.RDB$FIELD_SOURCE, parameter.fieldSource.c_str());
}
// ASF: If we used a collate with a domain or table.column type, write it
// into RDB$FUNCTION_ARGUMENTS.
if (parameter.collateSpecified && parameter.typeOfName.hasData())
{
ARG.RDB$COLLATION_ID.NULL = FALSE;
ARG.RDB$COLLATION_ID = parameter.collationId;
}
// ASF: I moved this block to write defaults on RDB$FUNCTION_ARGUMENTS.
// It was writing in RDB$FIELDS, but that would require special support
// for packaged functions signature verification.
if (parameter.legacyDefault)
{
ARG.RDB$DEFAULT_VALUE.NULL = FALSE;
ARG.RDB$DEFAULT_SOURCE.NULL = FALSE;
dsql_str* defaultString =
(dsql_str*) parameter.legacyDefault->nod_arg[e_dft_default_source];
string defaultSource = string(defaultString->str_data, defaultString->str_length);
attachment->storeMetaDataBlob(tdbb, transaction, &ARG.RDB$DEFAULT_SOURCE, defaultSource);
statement->getBlrData().clear();
if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4)
statement->append_uchar(blr_version4);
else
statement->append_uchar(blr_version5);
GEN_expr(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
statement->append_uchar(blr_eoc);
attachment->storeBinaryBlob(tdbb, transaction, &ARG.RDB$DEFAULT_VALUE,
statement->getBlrData().begin(),
statement->getBlrData().getCount());
}
}
END_STORE
}
void CreateAlterFunctionNode::compile(thread_db* tdbb, jrd_tra* /*transaction*/)
{
if (invalid)
{
//// TODO: localize
status_exception::raise(Arg::Gds(isc_random) << Arg::Str("Invalid DDL statement"));
}
if (compiled)
return;
compiled = true;
invalid = true;
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
if (body)
{
statement->beginDebug();
statement->getBlrData().clear();
if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4)
statement->append_uchar(blr_version4);
else
statement->append_uchar(blr_version5);
statement->append_uchar(blr_begin);
if (parameters.getCount() != 0)
{
fb_assert(parameters.getCount() < size_t(MAX_USHORT / 2));
statement->append_uchar(blr_message);
statement->append_uchar(0);
statement->append_ushort(2 * parameters.getCount());
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
statement->put_debug_argument(fb_dbg_arg_input, i,
parameter.name.c_str());
putType(parameter, true);
// add slot for null flag (parameter2)
statement->append_uchar(blr_short);
statement->append_uchar(0);
variables.add(MAKE_variable(parameter.legacyField,
parameter.name.c_str(), VAR_input, 0, (USHORT) (2 * i), 0));
}
}
statement->append_uchar(blr_message);
statement->append_uchar(1);
statement->append_ushort(2);
statement->put_debug_argument(fb_dbg_arg_output, 0, "");
putType(returnType, true);
// add slot for null flag (parameter2)
statement->append_uchar(blr_short);
statement->append_uchar(0);
dsql_nod* const var = MAKE_variable(returnType.legacyField, "", VAR_output, 1, 0, 0);
variables.add(var);
outputVariables.add(var);
if (parameters.getCount() != 0)
{
statement->append_uchar(blr_receive);
statement->append_uchar(0);
}
statement->append_uchar(blr_begin);
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
if (parameter.fullDomain || parameter.notNull)
{
// ASF: To validate input parameters we need only to read its value.
// Assigning it to null is an easy way to do this.
statement->append_uchar(blr_assignment);
statement->append_uchar(blr_parameter2);
statement->append_uchar(0); // input
statement->append_ushort(i * 2);
statement->append_ushort(i * 2 + 1);
statement->append_uchar(blr_null);
}
}
dsql_var* const variable = (dsql_var*) outputVariables[0]->nod_arg[Dsql::e_var_variable];
DDL_put_local_variable(dsqlScratch, variable, 0, NULL);
// ASF: This is here to not change the old logic (proc_flag)
// of previous calls to PASS1_node and PASS1_statement.
dsqlScratch->setPsql(true);
DDL_put_local_variables(dsqlScratch, localDeclList, 1, variables);
statement->append_uchar(blr_stall);
// put a label before body of procedure,
// so that any EXIT statement can get out
statement->append_uchar(blr_label);
statement->append_uchar(0);
dsqlScratch->loopLevel = 0;
dsqlScratch->cursorNumber = 0;
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body));
statement->setType(DsqlCompiledStatement::TYPE_DDL);
statement->append_uchar(blr_end);
GEN_return(dsqlScratch, outputVariables, false, false);
statement->append_uchar(blr_end);
statement->append_uchar(blr_eoc);
statement->endDebug();
}
invalid = false;
}
void DropFunctionNode::dropArguments(thread_db* tdbb, jrd_tra* transaction,
const Firebird::MetaName& functionName, const Firebird::MetaName& packageName)
{
AutoCacheRequest requestHandle(tdbb, drq_e_func_args, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
ARG IN RDB$FUNCTION_ARGUMENTS
WITH ARG.RDB$FUNCTION_NAME EQ functionName.c_str() AND
ARG.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
{
// get rid of arguments in rdb$fields
if (!ARG.RDB$FIELD_SOURCE.NULL)
{
AutoCacheRequest requestHandle2(tdbb, drq_e_arg_gfld, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ ARG.RDB$FIELD_SOURCE AND
FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX
{
bool erase = true;
AutoCacheRequest requestHandle3(tdbb, drq_e_arg_gfld2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle3 TRANSACTION_HANDLE transaction)
ARG2 IN RDB$FUNCTION_ARGUMENTS
WITH ARG2.RDB$FUNCTION_NAME = ARG.RDB$FUNCTION_NAME AND
ARG2.RDB$PACKAGE_NAME EQUIV
NULLIF(packageName.c_str(), '') AND
ARG2.RDB$ARGUMENT_NAME = ARG.RDB$ARGUMENT_NAME
{
if (!ARG2.RDB$RELATION_NAME.NULL && !ARG2.RDB$FIELD_NAME.NULL)
erase = false;
}
END_FOR
if (erase)
ERASE FLD;
}
END_FOR
}
ERASE ARG;
}
END_FOR
}
void DropFunctionNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"DropFunctionNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* DropFunctionNode::internalDsqlPass()
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);
return DdlNode::internalDsqlPass();
}
void DropFunctionNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
bool found = false;
dropArguments(tdbb, transaction, name, package);
AutoCacheRequest requestHandle(tdbb, drq_e_funcs, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
FUN IN RDB$FUNCTIONS
WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND
FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
{
if (!FUN.RDB$SYSTEM_FLAG.NULL && FUN.RDB$SYSTEM_FLAG)
{
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cannot_mod_sysfunc) << FUN.RDB$FUNCTION_NAME);
}
if (package.isEmpty())
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_FUNCTION, name);
ERASE FUN;
if (!FUN.RDB$SECURITY_CLASS.NULL)
DYN_delete_security_class2(transaction, FUN.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
if (!found && !silent)
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name));
}
if (package.isEmpty())
{
requestHandle.reset(tdbb, drq_e_fun_prvs, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str()
AND PRIV.RDB$OBJECT_TYPE = obj_udf
{
ERASE PRIV;
}
END_FOR
requestHandle.reset(tdbb, drq_e_fun_prv, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str()
AND PRIV.RDB$USER_TYPE = obj_udf
{
ERASE PRIV;
}
END_FOR
}
if (found && package.isEmpty())
executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_DROP_FUNCTION, name);
savePoint.release(); // everything is ok
// Update DSQL cache
AutoPtr<dsql_str> str(MAKE_string(name.c_str(), name.length()));
METD_drop_function(transaction, str, package);
MET_dsql_cache_release(tdbb, SYM_udf, str->str_data, package);
}
//----------------------
void RecreateFunctionNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf("RecreateFunctionNode\n");
}
DdlNode* RecreateFunctionNode::internalDsqlPass()
{
createNode->dsqlPass(dsqlScratch);
dropNode.dsqlPass(dsqlScratch);
return DdlNode::internalDsqlPass();
}
void RecreateFunctionNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
dropNode.execute(tdbb, transaction);
createNode->execute(tdbb, transaction);
savePoint.release(); // everything is ok
}
//----------------------
void ProcedureNode::genReturn()
{
GEN_return(dsqlScratch, getCreateAlterNode()->outputVariables, true, false);
}
dsql_nod* ProcedureNode::resolveVariable(const dsql_str* varName)
{
// try to resolve variable name against input and output parameters and local variables
CreateAlterProcedureNode* node = getCreateAlterNode();
return PASS1_resolve_variable_name(node->variables, varName);
}
//----------------------
void CreateAlterProcedureNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"CreateAlterProcedureNode\n"
" name: '%s' create: %d alter: %d\n",
name.c_str(), create, alter);
if (external)
{
string s;
s.printf(" external -> name: '%s' engine: '%s'\n",
external->name.c_str(), external->engine.c_str());
text += s;
}
text += " Parameters:\n";
for (size_t i = 0; i < parameters.getCount(); ++i)
{
const ParameterClause& parameter = parameters[i];
string s;
parameter.print(s);
text += " " + s + "\n";
}
text += " Returns:\n";
for (size_t i = 0; i < returns.getCount(); ++i)
{
const ParameterClause& parameter = returns[i];
string s;
parameter.print(s);
text += " " + s + "\n";
}
}
DdlNode* CreateAlterProcedureNode::internalDsqlPass()
{
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
statement->setBlockNode(this);
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);
const dsql_nod* variables = localDeclList;
if (variables)
{
// insure that variable names do not duplicate parameter names
SortedArray<MetaName> names;
for (size_t i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
names.add(parameter.name);
}
for (size_t i = 0; i < returns.getCount(); ++i)
{
ParameterClause& parameter = returns[i];
names.add(parameter.name);
}
const dsql_nod* const* ptr = variables->nod_arg;
for (const dsql_nod* const* const end = ptr + variables->nod_count; ptr < end; ptr++)
{
if ((*ptr)->nod_type == nod_def_field)
{
const dsql_fld* field = (dsql_fld*) (*ptr)->nod_arg[e_dfl_field];
DEV_BLKCHK(field, dsql_type_fld);
if (names.exist(field->fld_name))
{
status_exception::raise(
Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
Arg::Gds(isc_dsql_var_conflict) << Arg::Str(field->fld_name));
}
}
}
}
source.ltrim("\n\r\t ");
// compile default expressions
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
if (parameter.legacyDefault)
{
parameter.legacyDefault->nod_arg[e_dft_default] =
PASS1_node(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
}
}
for (unsigned i = 0; i < parameters.getCount(); ++i)
parameters[i].resolve(dsqlScratch);
for (unsigned i = 0; i < returns.getCount(); ++i)
returns[i].resolve(dsqlScratch);
return DdlNode::internalDsqlPass();
}
void CreateAlterProcedureNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
fb_assert(create || alter);
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
bool altered = false;
// first pass
if (alter)
{
if (executeAlter(tdbb, transaction, false, true))
altered = true;
else
{
if (create) // create or alter
executeCreate(tdbb, transaction);
else
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name));
}
}
}
else
executeCreate(tdbb, transaction);
compile(tdbb, transaction);
executeAlter(tdbb, transaction, true, false); // second pass
if (package.isEmpty())
{
executeDdlTrigger(tdbb, transaction, DTW_AFTER,
(altered ? DDL_TRIGGER_ALTER_PROCEDURE : DDL_TRIGGER_CREATE_PROCEDURE), name);
}
savePoint.release(); // everything is ok
if (alter)
{
// Update DSQL cache
AutoPtr<dsql_str> str(MAKE_string(name.c_str(), name.length()));
METD_drop_procedure(transaction, str, package);
MET_dsql_cache_release(tdbb, SYM_procedure, str->str_data, package);
}
}
void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, jrd_tra* transaction)
{
Attachment* attachment = transaction->getAttachment();
if (package.isEmpty())
{
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_PROCEDURE, name);
DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_procedure);
}
AutoCacheRequest requestHandle(tdbb, drq_s_prcs2, DYN_REQUESTS);
int faults = 0;
while (true)
{
try
{
SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_prc_id, "RDB$PROCEDURES");
id %= (MAX_SSHORT + 1);
if (!id)
continue;
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
P IN RDB$PROCEDURES
{
P.RDB$PROCEDURE_ID = id;
P.RDB$SYSTEM_FLAG = 0;
strcpy(P.RDB$PROCEDURE_NAME, name.c_str());
if (package.hasData())
{
P.RDB$PACKAGE_NAME.NULL = FALSE;
strcpy(P.RDB$PACKAGE_NAME, package.c_str());
P.RDB$PRIVATE_FLAG.NULL = FALSE;
P.RDB$PRIVATE_FLAG = privateScope;
strcpy(P.RDB$OWNER_NAME, packageOwner.c_str());
}
else
{
P.RDB$PACKAGE_NAME.NULL = TRUE;
P.RDB$PRIVATE_FLAG.NULL = TRUE;
strcpy(P.RDB$OWNER_NAME, attachment->att_user->usr_user_name.c_str());
}
}
END_STORE
break;
}
catch (const Firebird::status_exception& ex)
{
if (ex.value()[1] != isc_no_dup)
throw;
if (++faults > MAX_SSHORT)
throw;
fb_utils::init_status(tdbb->tdbb_status_vector);
}
}
if (package.isEmpty())
{
for (const TEXT* p = ALL_PROC_PRIVILEGES; *p; p++)
{
requestHandle.reset(tdbb, drq_s_prc_usr_prvs, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
X IN RDB$USER_PRIVILEGES
{
strcpy(X.RDB$RELATION_NAME, name.c_str());
strcpy(X.RDB$USER, attachment->att_user->usr_user_name.c_str());
X.RDB$USER_TYPE = obj_user;
X.RDB$OBJECT_TYPE = obj_procedure;
X.RDB$PRIVILEGE[0] = *p;
X.RDB$PRIVILEGE[1] = 0;
}
END_STORE
}
}
executeAlter(tdbb, transaction, false, false);
}
bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, jrd_tra* transaction,
bool secondPass, bool runTriggers)
{
Attachment* attachment = transaction->getAttachment();
AutoCacheRequest requestHandle(tdbb, drq_m_prcs2, DYN_REQUESTS);
bool modified = false;
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
P IN RDB$PROCEDURES
WITH P.RDB$PROCEDURE_NAME EQ name.c_str() AND
P.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
{
if (!P.RDB$SYSTEM_FLAG.NULL && P.RDB$SYSTEM_FLAG)
{
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cannot_mod_sysproc) << P.RDB$PROCEDURE_NAME);
}
if (!secondPass && runTriggers && package.isEmpty())
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_PROCEDURE, name);
MODIFY P
if (secondPass)
{
P.RDB$PROCEDURE_BLR.NULL = TRUE;
P.RDB$DEBUG_INFO.NULL = TRUE;
P.RDB$PROCEDURE_TYPE.NULL = TRUE;
P.RDB$PROCEDURE_INPUTS = (USHORT) parameters.getCount();
P.RDB$PROCEDURE_OUTPUTS = (USHORT) returns.getCount();
}
else
{
P.RDB$ENGINE_NAME.NULL = TRUE;
P.RDB$ENTRYPOINT.NULL = TRUE;
P.RDB$PROCEDURE_SOURCE.NULL = TRUE;
P.RDB$VALID_BLR = TRUE;
P.RDB$PROCEDURE_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty()));
if (!P.RDB$PROCEDURE_SOURCE.NULL)
attachment->storeMetaDataBlob(tdbb, transaction, &P.RDB$PROCEDURE_SOURCE, source);
if (package.hasData())
{
P.RDB$PRIVATE_FLAG.NULL = FALSE;
P.RDB$PRIVATE_FLAG = privateScope;
}
else
P.RDB$PRIVATE_FLAG.NULL = TRUE;
}
if (external)
{
if (secondPass)
{
P.RDB$PROCEDURE_TYPE.NULL = FALSE;
P.RDB$PROCEDURE_TYPE = (USHORT) prc_selectable;
}
else
{
P.RDB$ENGINE_NAME.NULL = FALSE;
strcpy(P.RDB$ENGINE_NAME, external->engine.c_str());
if (external->name.length() >= sizeof(P.RDB$ENTRYPOINT))
{
status_exception::raise(
Arg::Gds(isc_arith_except) <<
Arg::Gds(isc_string_truncation));
}
P.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
strcpy(P.RDB$ENTRYPOINT, external->name.c_str());
}
}
else if (body)
{
if (secondPass)
{
P.RDB$PROCEDURE_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$PROCEDURE_BLR,
statement->getBlrData().begin(),
statement->getBlrData().getCount());
P.RDB$DEBUG_INFO.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$DEBUG_INFO,
statement->getDebugData().begin(),
statement->getDebugData().getCount());
P.RDB$PROCEDURE_TYPE.NULL = FALSE;
P.RDB$PROCEDURE_TYPE = (USHORT)
(statement->getFlags() & DsqlCompiledStatement::FLAG_SELECTABLE ?
prc_selectable : prc_executable);
}
}
END_MODIFY
modified = true;
}
END_FOR
if (!secondPass && modified)
{
// delete all old input and output parameters
DropProcedureNode::dropParameters(tdbb, transaction, name, package);
// and insert the new ones
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
storeParameter(tdbb, transaction, 0, i, parameter);
}
for (unsigned i = 0; i < returns.getCount(); ++i)
{
ParameterClause& parameter = returns[i];
storeParameter(tdbb, transaction, 1, i, parameter);
}
}
return modified;
}
void CreateAlterProcedureNode::storeParameter(thread_db* tdbb, jrd_tra* transaction,
USHORT type, unsigned pos, const ParameterClause& parameter)
{
Attachment* attachment = transaction->getAttachment();
AutoCacheRequest requestHandle(tdbb, drq_s_prms4, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRM IN RDB$PROCEDURE_PARAMETERS
{
PRM.RDB$PARAMETER_NAME.NULL = FALSE;
strcpy(PRM.RDB$PARAMETER_NAME, parameter.name.c_str());
PRM.RDB$PROCEDURE_NAME.NULL = FALSE;
strcpy(PRM.RDB$PROCEDURE_NAME, name.c_str());
if (package.hasData())
{
PRM.RDB$PACKAGE_NAME.NULL = FALSE;
strcpy(PRM.RDB$PACKAGE_NAME, package.c_str());
}
else
PRM.RDB$PACKAGE_NAME.NULL = TRUE;
PRM.RDB$SYSTEM_FLAG = 0;
PRM.RDB$SYSTEM_FLAG.NULL = FALSE;
PRM.RDB$PARAMETER_NUMBER.NULL = FALSE;
PRM.RDB$PARAMETER_NUMBER = pos;
PRM.RDB$PARAMETER_TYPE.NULL = FALSE;
PRM.RDB$PARAMETER_TYPE = type;
PRM.RDB$PARAMETER_MECHANISM.NULL = FALSE;
PRM.RDB$PARAMETER_MECHANISM =
(USHORT) (parameter.fullDomain ? prm_mech_normal : prm_mech_type_of);
PRM.RDB$NULL_FLAG.NULL = !parameter.notNull;
PRM.RDB$NULL_FLAG = parameter.notNull;
PRM.RDB$RELATION_NAME.NULL = parameter.typeOfTable.isEmpty();
PRM.RDB$FIELD_NAME.NULL = PRM.RDB$RELATION_NAME.NULL || parameter.typeOfName.isEmpty();
PRM.RDB$FIELD_SOURCE.NULL = FALSE;
if (PRM.RDB$RELATION_NAME.NULL)
{
if (parameter.typeOfName.hasData())
{
strcpy(PRM.RDB$FIELD_SOURCE, parameter.typeOfName.c_str());
}
else
{
const MetaName fieldName = storeGlobalField(tdbb, transaction, parameter);
strcpy(PRM.RDB$FIELD_SOURCE, fieldName.c_str());
}
}
else
{
strcpy(PRM.RDB$RELATION_NAME, parameter.typeOfTable.c_str());
strcpy(PRM.RDB$FIELD_NAME, parameter.typeOfName.c_str());
strcpy(PRM.RDB$FIELD_SOURCE, parameter.fieldSource.c_str());
}
// ASF: If we used a collate with a domain or table.column type, write it
// in RDB$PROCEDURE_PARAMETERS.
PRM.RDB$COLLATION_ID.NULL = !(parameter.collateSpecified && parameter.typeOfName.hasData());
if (!PRM.RDB$COLLATION_ID.NULL)
PRM.RDB$COLLATION_ID = parameter.collationId;
// ASF: I moved this block to write defaults on RDB$PROCEDURE_PARAMETERS.
// It was writing in RDB$FIELDS, but that would require special support
// for packaged procedures signature verification.
PRM.RDB$DEFAULT_VALUE.NULL = !parameter.legacyDefault;
PRM.RDB$DEFAULT_SOURCE.NULL = !parameter.legacyDefault;
if (parameter.legacyDefault)
{
dsql_str* defaultString =
(dsql_str*) parameter.legacyDefault->nod_arg[e_dft_default_source];
string defaultSource = string(defaultString->str_data, defaultString->str_length);
attachment->storeMetaDataBlob(tdbb, transaction, &PRM.RDB$DEFAULT_SOURCE, defaultSource);
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
statement->getBlrData().clear();
if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4)
statement->append_uchar(blr_version4);
else
statement->append_uchar(blr_version5);
GEN_expr(dsqlScratch, parameter.legacyDefault->nod_arg[e_dft_default]);
statement->append_uchar(blr_eoc);
attachment->storeBinaryBlob(tdbb, transaction, &PRM.RDB$DEFAULT_VALUE,
statement->getBlrData().begin(),
statement->getBlrData().getCount());
}
}
END_STORE
}
void CreateAlterProcedureNode::compile(thread_db* tdbb, jrd_tra* /*transaction*/)
{
if (invalid)
status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_proc) << name);
if (compiled)
return;
compiled = true;
if (!body)
return;
invalid = true;
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
statement->beginDebug();
statement->getBlrData().clear();
if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4)
statement->append_uchar(blr_version4);
else
statement->append_uchar(blr_version5);
statement->append_uchar(blr_begin);
if (parameters.getCount() != 0)
{
fb_assert(parameters.getCount() < MAX_USHORT / 2);
statement->append_uchar(blr_message);
statement->append_uchar(0);
statement->append_ushort(2 * parameters.getCount());
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
statement->put_debug_argument(fb_dbg_arg_input, i, parameter.name.c_str());
putType(parameter, true);
// add slot for null flag (parameter2)
statement->append_uchar(blr_short);
statement->append_uchar(0);
variables.add(MAKE_variable(parameter.legacyField,
parameter.name.c_str(), VAR_input, 0, (USHORT) (2 * i), 0));
}
}
fb_assert(returns.getCount() < MAX_USHORT / 2);
statement->append_uchar(blr_message);
statement->append_uchar(1);
statement->append_ushort(2 * returns.getCount() + 1);
if (returns.getCount() != 0)
{
for (unsigned i = 0; i < returns.getCount(); ++i)
{
ParameterClause& parameter = returns[i];
statement->put_debug_argument(fb_dbg_arg_output, i, parameter.name.c_str());
putType(parameter, true);
// add slot for null flag (parameter2)
statement->append_uchar(blr_short);
statement->append_uchar(0);
dsql_nod* const var = MAKE_variable(parameter.legacyField,
parameter.name.c_str(), VAR_output, 1, (USHORT) (2 * i), i);
variables.add(var);
outputVariables.add(var);
}
}
// add slot for EOS
statement->append_uchar(blr_short);
statement->append_uchar(0);
if (parameters.getCount() != 0)
{
statement->append_uchar(blr_receive);
statement->append_uchar(0);
}
statement->append_uchar(blr_begin);
for (unsigned i = 0; i < parameters.getCount(); ++i)
{
ParameterClause& parameter = parameters[i];
if (parameter.fullDomain || parameter.notNull)
{
// ASF: To validate an input parameter we need only to read its value.
// Assigning it to null is an easy way to do this.
statement->append_uchar(blr_assignment);
statement->append_uchar(blr_parameter2);
statement->append_uchar(0); // input
statement->append_ushort(i * 2);
statement->append_ushort(i * 2 + 1);
statement->append_uchar(blr_null);
}
}
for (Array<dsql_nod*>::const_iterator i = outputVariables.begin(); i != outputVariables.end(); ++i)
{
dsql_nod* parameter = *i;
dsql_var* const variable = (dsql_var*) parameter->nod_arg[Dsql::e_var_variable];
DDL_put_local_variable(dsqlScratch, variable, 0, NULL);
}
// ASF: This is here to not change the old logic (proc_flag)
// of previous calls to PASS1_node and PASS1_statement.
dsqlScratch->setPsql(true);
DDL_put_local_variables(dsqlScratch, localDeclList, returns.getCount(), variables);
statement->append_uchar(blr_stall);
// put a label before body of procedure,
// so that any EXIT statement can get out
statement->append_uchar(blr_label);
statement->append_uchar(0);
dsqlScratch->loopLevel = 0;
dsqlScratch->cursorNumber = 0;
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body));
statement->setType(DsqlCompiledStatement::TYPE_DDL);
statement->append_uchar(blr_end);
GEN_return(dsqlScratch, outputVariables, true, true);
statement->append_uchar(blr_end);
statement->append_uchar(blr_eoc);
statement->endDebug();
invalid = false;
}
//----------------------
void DropProcedureNode::dropParameters(thread_db* tdbb, jrd_tra* transaction,
const Firebird::MetaName& procedureName, const Firebird::MetaName& packageName)
{
AutoCacheRequest requestHandle(tdbb, drq_e_prms2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRM IN RDB$PROCEDURE_PARAMETERS
WITH PRM.RDB$PROCEDURE_NAME EQ procedureName.c_str() AND
PRM.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
{
// get rid of parameters in rdb$fields
if (!PRM.RDB$FIELD_SOURCE.NULL)
{
AutoCacheRequest requestHandle2(tdbb, drq_e_prm_gfld, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
FLD IN RDB$FIELDS
WITH FLD.RDB$FIELD_NAME EQ PRM.RDB$FIELD_SOURCE AND
FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND
(FLD.RDB$SYSTEM_FLAG EQ 0 OR FLD.RDB$SYSTEM_FLAG MISSING)
{
bool erase = true;
// Can this loop be merged with the previous?
{ // scope
AutoCacheRequest requestHandle3(tdbb, drq_e_prm_gfld2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle3 TRANSACTION_HANDLE transaction)
PRM2 IN RDB$PROCEDURE_PARAMETERS
WITH PRM2.RDB$PROCEDURE_NAME = PRM.RDB$PROCEDURE_NAME AND
PRM2.RDB$PACKAGE_NAME EQUIV
NULLIF(packageName.c_str(), '') AND
PRM2.RDB$PARAMETER_NAME = PRM.RDB$PARAMETER_NAME
{
if (!PRM2.RDB$RELATION_NAME.NULL && !PRM2.RDB$FIELD_NAME.NULL)
erase = false;
}
END_FOR
} // end scope
if (erase)
ERASE FLD;
}
END_FOR
}
ERASE PRM;
}
END_FOR
}
void DropProcedureNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"DropProcedureNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* DropProcedureNode::internalDsqlPass()
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);
return DdlNode::internalDsqlPass();
}
void DropProcedureNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
bool found = false;
dropParameters(tdbb, transaction, name, package);
AutoCacheRequest requestHandle(tdbb, drq_e_prcs2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRC IN RDB$PROCEDURES
WITH PRC.RDB$PROCEDURE_NAME EQ name.c_str() AND
PRC.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
{
if (!PRC.RDB$SYSTEM_FLAG.NULL && PRC.RDB$SYSTEM_FLAG)
{
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cannot_mod_sysproc) << PRC.RDB$PROCEDURE_NAME);
}
if (package.isEmpty())
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_PROCEDURE, name);
ERASE PRC;
if (!PRC.RDB$SECURITY_CLASS.NULL)
DYN_delete_security_class2(transaction, PRC.RDB$SECURITY_CLASS);
found = true;
}
END_FOR
if (!found && !silent)
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name));
}
if (package.isEmpty())
{
requestHandle.reset(tdbb, drq_e_prc_prvs, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str()
AND PRIV.RDB$OBJECT_TYPE = obj_procedure
{
ERASE PRIV;
}
END_FOR
requestHandle.reset(tdbb, drq_e_prc_prv, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str()
AND PRIV.RDB$USER_TYPE = obj_procedure
{
ERASE PRIV;
}
END_FOR
}
if (found && package.isEmpty())
executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_DROP_PROCEDURE, name);
savePoint.release(); // everything is ok
// Update DSQL cache
AutoPtr<dsql_str> str(MAKE_string(name.c_str(), name.length()));
METD_drop_procedure(transaction, str, package);
MET_dsql_cache_release(tdbb, SYM_procedure, str->str_data, package);
}
//----------------------
void RecreateProcedureNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf("RecreateProcedureNode\n");
}
DdlNode* RecreateProcedureNode::internalDsqlPass()
{
dropNode.dsqlPass(dsqlScratch);
createNode->dsqlPass(dsqlScratch);
return DdlNode::internalDsqlPass();
}
void RecreateProcedureNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
dropNode.execute(tdbb, transaction);
createNode->execute(tdbb, transaction);
savePoint.release(); // everything is ok
}
//----------------------
dsql_nod* TriggerNode::resolveVariable(const dsql_str* varName)
{
// try to resolve variable name against local variables
CreateAlterTriggerNode* node = getCreateAlterNode();
return PASS1_resolve_variable_name(node->variables, varName);
}
//----------------------
void CreateAlterTriggerNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"CreateAlterTriggerNode\n"
" name: '%s' create: %d alter: %d relationName: '%s'\n"
" type: %d, %d active: %d, %d position: %d, %d\n",
name.c_str(), create, alter, relationName.c_str(),
type.specified, type.value, active.specified, active.value,
position.specified, position.value);
if (external)
{
string s;
s.printf(" external -> name: '%s' engine: '%s'\n",
external->name.c_str(), external->engine.c_str());
text += s;
}
}
DdlNode* CreateAlterTriggerNode::internalDsqlPass()
{
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
statement->setBlockNode(this);
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER);
if (type.specified)
{
if (create && // ALTER TRIGGER doesn't accept table name
((relationName.hasData() &&
(type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DML) ||
(relationName.isEmpty() &&
(type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DB &&
(type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DDL)))
{
status_exception::raise(
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_incompatible_trigger_type));
}
}
return DdlNode::internalDsqlPass();
}
void CreateAlterTriggerNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
fb_assert(create || alter);
Attachment* attachment = transaction->getAttachment();
if (relationName.isEmpty() && !attachment->locksmith())
status_exception::raise(Arg::Gds(isc_adm_task_denied));
source.ltrim("\n\r\t ");
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
if (!create)
{
jrd_req* requestHandle = NULL;
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
TRG IN RDB$TRIGGERS
WITH TRG.RDB$TRIGGER_NAME EQ name.c_str()
{
if (!type.specified && !TRG.RDB$TRIGGER_TYPE.NULL)
type = TRG.RDB$TRIGGER_TYPE;
if (relationName.isEmpty() && !TRG.RDB$RELATION_NAME.NULL)
relationName = TRG.RDB$RELATION_NAME;
}
END_FOR
CMP_release(tdbb, requestHandle);
if (!type.specified)
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
}
}
compile(tdbb, transaction);
if (alter)
{
if (!executeAlter(tdbb, transaction, true))
{
if (create) // create or alter
executeCreate(tdbb, transaction);
else
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
}
}
}
else
executeCreate(tdbb, transaction);
savePoint.release(); // everything is ok
}
void CreateAlterTriggerNode::executeCreate(thread_db* tdbb, jrd_tra* transaction)
{
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TRIGGER, name);
AutoCacheRequest requestHandle(tdbb, drq_s_triggers2, DYN_REQUESTS);
STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
TRG IN RDB$TRIGGERS
{
TRG.RDB$SYSTEM_FLAG = 0;
TRG.RDB$FLAGS = TRG_sql; // ASF: For FK triggers, TRG_ignore_perm will also be needed.
strcpy(TRG.RDB$TRIGGER_NAME, name.c_str());
TRG.RDB$RELATION_NAME.NULL = relationName.isEmpty();
strcpy(TRG.RDB$RELATION_NAME, relationName.c_str());
fb_assert(type.specified);
TRG.RDB$TRIGGER_TYPE = type.value;
TRG.RDB$TRIGGER_SEQUENCE = (!position.specified ? 0 : position.value);
TRG.RDB$TRIGGER_INACTIVE = (!active.specified ? 0 : (USHORT) !active.value);
}
END_STORE
executeAlter(tdbb, transaction, false);
executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TRIGGER, name);
}
bool CreateAlterTriggerNode::executeAlter(thread_db* tdbb, jrd_tra* transaction, bool runTriggers)
{
Attachment* attachment = transaction->getAttachment();
bool modified = false;
// ASF: Unregistered bug (2.0, 2.1, 2.5, 3.0): CREATE OR ALTER TRIGGER accepts different table
// than one used in already created trigger.
AutoCacheRequest requestHandle(tdbb, drq_m_trigger2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
TRG IN RDB$TRIGGERS
WITH TRG.RDB$TRIGGER_NAME EQ name.c_str()
{
if (type.specified && type.value != (FB_UINT64) TRG.RDB$TRIGGER_TYPE &&
((create && relationName.isEmpty()) || TRG.RDB$RELATION_NAME.NULL))
{
status_exception::raise(
Arg::Gds(isc_dsql_command_err) <<
Arg::Gds(isc_dsql_db_trigger_type_cant_change));
}
if (!TRG.RDB$SYSTEM_FLAG.NULL)
{
switch (TRG.RDB$SYSTEM_FLAG)
{
case fb_sysflag_check_constraint:
case fb_sysflag_referential_constraint:
case fb_sysflag_view_check:
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cant_modify_auto_trig));
break;
case fb_sysflag_system:
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cannot_mod_systrig) << TRG.RDB$TRIGGER_NAME);
break;
default:
break;
}
}
if (runTriggers)
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_TRIGGER, name);
MODIFY TRG
if (body || external)
{
fb_assert(!(body && external));
TRG.RDB$ENGINE_NAME.NULL = TRUE;
TRG.RDB$ENTRYPOINT.NULL = TRUE;
TRG.RDB$TRIGGER_SOURCE.NULL = TRUE;
TRG.RDB$TRIGGER_BLR.NULL = TRUE;
TRG.RDB$DEBUG_INFO.NULL = TRUE;
}
if (type.specified)
TRG.RDB$TRIGGER_TYPE = type.value;
if (position.specified)
TRG.RDB$TRIGGER_SEQUENCE = position.value;
if (active.specified)
TRG.RDB$TRIGGER_INACTIVE = (USHORT) !active.value;
if (external)
{
TRG.RDB$ENGINE_NAME.NULL = FALSE;
strcpy(TRG.RDB$ENGINE_NAME, external->engine.c_str());
if (external->name.length() >= sizeof(TRG.RDB$ENTRYPOINT))
{
status_exception::raise(
Arg::Gds(isc_arith_except) <<
Arg::Gds(isc_string_truncation));
}
TRG.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
strcpy(TRG.RDB$ENTRYPOINT, external->name.c_str());
}
else if (body)
{
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
TRG.RDB$TRIGGER_BLR.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$TRIGGER_BLR,
statement->getBlrData().begin(),
statement->getBlrData().getCount());
TRG.RDB$DEBUG_INFO.NULL = FALSE;
attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$DEBUG_INFO,
statement->getDebugData().begin(),
statement->getDebugData().getCount());
}
if (source.hasData())
{
TRG.RDB$TRIGGER_SOURCE.NULL = FALSE;
attachment->storeMetaDataBlob(tdbb, transaction, &TRG.RDB$TRIGGER_SOURCE, source);
}
TRG.RDB$VALID_BLR = TRUE;
END_MODIFY
modified = true;
}
END_FOR
if (modified && runTriggers)
executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_TRIGGER, name);
return modified;
}
void CreateAlterTriggerNode::compile(thread_db* tdbb, jrd_tra* /*transaction*/)
{
if (invalid)
status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_trig) << name);
if (compiled)
return;
compiled = true;
invalid = true;
if (body)
{
DsqlCompiledStatement* statement = dsqlScratch->getStatement();
statement->beginDebug();
statement->getBlrData().clear();
// Create the "OLD" and "NEW" contexts for the trigger --
// the new one could be a dummy place holder to avoid resolving
// fields to that context but prevent relations referenced in
// the trigger actions from referencing the predefined "1" context.
if (dsqlScratch->contextNumber)
resetContextStack();
if (relationName.hasData())
{
dsql_nod* relationNode = FB_NEW_RPT(getPool(), e_rln_count) dsql_nod;
///trigger_node->nod_arg[e_trg_table] = relationNode;
relationNode->nod_type = nod_relation_name;
relationNode->nod_count = e_rln_count;
relationNode->nod_arg[e_rln_name] = (dsql_nod*)
MAKE_string(relationName.c_str(), relationName.length());
dsql_nod* const temp = relationNode->nod_arg[e_rln_alias];
if (hasOldContext(type.value))
{
relationNode->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(OLD_CONTEXT);
dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode);
oldContext->ctx_flags |= CTX_system;
}
else
dsqlScratch->contextNumber++;
if (hasNewContext(type.value))
{
relationNode->nod_arg[e_rln_alias] = (dsql_nod*) MAKE_cstring(NEW_CONTEXT);
dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode);
newContext->ctx_flags |= CTX_system;
}
else
dsqlScratch->contextNumber++;
relationNode->nod_arg[e_rln_alias] = temp;
}
// generate the trigger blr
if (statement->getFlags() & DsqlCompiledStatement::FLAG_BLR_VERSION4)
statement->append_uchar(blr_version4);
else
statement->append_uchar(blr_version5);
statement->append_uchar(blr_begin);
dsqlScratch->setPsql(true);
DDL_put_local_variables(dsqlScratch, localDeclList, 0, variables);
dsqlScratch->scopeLevel++;
// dimitr: I see no reason to deny EXIT command in triggers,
// hence I've added zero label at the beginning.
// My first suspicion regarding an obvious conflict
// with trigger messages (nod_abort) is wrong,
// although the fact that they use the same BLR code
// is still a potential danger and must be fixed.
// Hopefully, system triggers are never recompiled.
statement->append_uchar(blr_label);
statement->append_uchar(0);
dsqlScratch->loopLevel = 0;
dsqlScratch->cursorNumber = 0;
GEN_statement(dsqlScratch, PASS1_statement(dsqlScratch, body));
dsqlScratch->scopeLevel--;
statement->append_uchar(blr_end);
statement->append_uchar(blr_eoc);
statement->endDebug();
// The statement type may have been set incorrectly when parsing
// the trigger actions, so reset it to reflect the fact that this
// is a data definition statement; also reset the ddl node.
statement->setType(DsqlCompiledStatement::TYPE_DDL);
}
invalid = false;
}
//----------------------
void DropTriggerNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf(
"DropTriggerNode\n"
" name: '%s'\n",
name.c_str());
}
DdlNode* DropTriggerNode::internalDsqlPass()
{
dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER);
return DdlNode::internalDsqlPass();
}
void DropTriggerNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
bool found = false;
MetaName relationName;
AutoCacheRequest requestHandle(tdbb, drq_e_trigger3, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
X IN RDB$TRIGGERS
WITH X.RDB$TRIGGER_NAME EQ name.c_str()
{
if (!X.RDB$SYSTEM_FLAG.NULL)
{
switch (X.RDB$SYSTEM_FLAG)
{
case fb_sysflag_check_constraint:
case fb_sysflag_referential_constraint:
case fb_sysflag_view_check:
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cant_modify_auto_trig));
break;
case fb_sysflag_system:
status_exception::raise(Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_cannot_mod_systrig) << X.RDB$TRIGGER_NAME);
break;
default:
break;
}
}
if (X.RDB$RELATION_NAME.NULL && !transaction->getAttachment()->locksmith())
status_exception::raise(Arg::Gds(isc_adm_task_denied));
executeDdlTrigger(tdbb, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_TRIGGER, name);
relationName = X.RDB$RELATION_NAME;
ERASE X;
found = true;
}
END_FOR
if (!found && !silent)
{
status_exception::raise(
Arg::Gds(isc_no_meta_update) <<
Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
}
requestHandle.reset(tdbb, drq_e_trg_msgs3, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
TM IN RDB$TRIGGER_MESSAGES
WITH TM.RDB$TRIGGER_NAME EQ name.c_str()
{
ERASE TM;
}
END_FOR
requestHandle.reset(tdbb, drq_e_trg_prv2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
PRIV IN RDB$USER_PRIVILEGES
WITH PRIV.RDB$USER EQ name.c_str() AND
PRIV.RDB$USER_TYPE = obj_trigger
{
ERASE PRIV;
}
END_FOR
// Clear the update flags on the fields if this is the last remaining
// trigger that changes a view.
bool viewFound = false;
requestHandle.reset(tdbb, drq_e_trg_prv3, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
FIRST 1 V IN RDB$VIEW_RELATIONS
CROSS F IN RDB$RELATION_FIELDS
CROSS T IN RDB$TRIGGERS
WITH V.RDB$VIEW_NAME EQ relationName.c_str() AND
F.RDB$RELATION_NAME EQ V.RDB$VIEW_NAME AND
F.RDB$RELATION_NAME EQ T.RDB$RELATION_NAME
{
viewFound = true;
}
END_FOR
if (!viewFound)
{
requestHandle.reset(tdbb, drq_m_rel_flds2, DYN_REQUESTS);
FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
F IN RDB$RELATION_FIELDS
WITH F.RDB$RELATION_NAME EQ relationName.c_str()
{
MODIFY F USING
F.RDB$UPDATE_FLAG = FALSE;
END_MODIFY
}
END_FOR
}
if (found)
executeDdlTrigger(tdbb, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TRIGGER, name);
savePoint.release(); // everything is ok
}
//----------------------
void RecreateTriggerNode::print(string& text, Array<dsql_nod*>& /*nodes*/) const
{
text.printf("RecreateTriggerNode\n");
}
DdlNode* RecreateTriggerNode::internalDsqlPass()
{
dropNode.dsqlPass(dsqlScratch);
createNode->dsqlPass(dsqlScratch);
return DdlNode::internalDsqlPass();
}
void RecreateTriggerNode::execute(thread_db* tdbb, jrd_tra* transaction)
{
// run all statements under savepoint control
AutoSavePoint savePoint(tdbb, transaction);
dropNode.execute(tdbb, transaction);
createNode->execute(tdbb, transaction);
savePoint.release(); // everything is ok
}
} // namespace Jrd